OK Site Beautifier

Applies a modern, material design with dark mode and syntax highlighting to okmij.org.

// ==UserScript==
// @name         OK Site Beautifier
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Applies a modern, material design with dark mode and syntax highlighting to okmij.org.
// @author       Your Name
// @match        *://okmij.org/ftp/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-ocaml.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-haskell.min.js
// @resource     PRISM_CSS_LIGHT https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css
// @resource     PRISM_CSS_DARK https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css
// @grant        GM_getResourceText
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 1. CONFIGURATION ---
    const config = {
        theme: GM_getValue('theme', 'light'), // 'light' or 'dark'
        syntaxHighlighting: GM_getValue('syntaxHighlighting', true) // true or false
    };

     /**
     * Removes common leading whitespace from a code block.
     * Specifically targets the 4-space indent found on the site.
     * @param {string} code The raw code string.
     * @returns {string} The dedented code string.
     */
    function dedent(code) {
        const lines = code.split('\n');

        // Don't process empty or single-line blocks
        if (lines.length <= 1) return code;

        // Check if all content-bearing lines start with 4 spaces.
        const canDedent = lines
            .filter(line => line.trim() !== '') // Ignore empty lines
            .every(line => line.startsWith('    '));

        if (canDedent) {
            return lines.map(line => line.substring(4)).join('\n');
        }

        // If the pattern doesn't match, return the original code.
        return code;
    }

    // --- 2. STYLES ---

    // Load PrismJS syntax highlighting themes
    const prismCssLight = GM_getResourceText('PRISM_CSS_LIGHT');
    const prismCssDark = GM_getResourceText('PRISM_CSS_DARK');

    GM_addStyle(`
        /* --- Base & Variables --- */
        :root {
            --bg-color: #f9f9f9;
            --text-color: #212121;
            --primary-color: #007acc;
            --link-color: #005a99;
            --card-bg: #ffffff;
            --border-color: #e0e0e0;
            --code-bg: #f0f0f0;
            --header-color: #333;
            --hr-color: #ccc;
            --shadow-color: rgba(0, 0, 0, 0.08);
        }

        body.dark-mode {
            --bg-color: #1e1e1e;
            --text-color: #e0e0e0;
            --primary-color: #2196F3;
            --link-color: #64b5f6;
            --card-bg: #2a2a2a;
            --border-color: #424242;
            --code-bg: #333;
            --header-color: #f5f5f5;
            --hr-color: #444;
            --shadow-color: rgba(0, 0, 0, 0.3);
        }

        /* --- General Layout & Typography --- */
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
            background-color: var(--bg-color);
            color: var(--text-color);
            line-height: 1.7;
            padding: 2rem 1rem;
            max-width: 900px;
            margin: 0 auto;
            transition: background-color 0.3s, color 0.3s;
        }

        h1, h2, h3 {
            color: var(--header-color);
            font-weight: 600;
            margin-top: 2.5em;
            margin-bottom: 1em;
            border-bottom: 1px solid var(--border-color);
            padding-bottom: 0.3em;
        }

        h1 { font-size: 2.5rem; text-align: center; }
        h2 { font-size: 2rem; }
        h3 { font-size: 1.5rem; }

        a {
            color: var(--link-color);
            text-decoration: none;
            transition: color 0.2s;
        }
        a:hover {
            color: var(--primary-color);
            text-decoration: underline;
        }

        hr {
            border: none;
            border-top: 1px solid var(--hr-color);
            margin: 2rem auto;
        }

        /* --- Content Sections (using dl as cards) --- */
        dl {
            background-color: var(--card-bg);
            border: 1px solid var(--border-color);
            border-radius: 8px;
            padding: 1.5rem 2rem;
            margin: 2rem 0;
            box-shadow: 0 4px 12px var(--shadow-color);
            transition: background-color 0.3s, border-color 0.3s;
        }

        dt {
            font-weight: bold;
            color: var(--primary-color);
            margin-top: 1em;
        }

        dd {
            margin-left: 0;
            padding-bottom: 1em;
            border-bottom: 1px dashed var(--border-color);
        }
        dd:last-child {
            border-bottom: none;
            padding-bottom: 0;
        }
        dl > dd:first-of-type {
             margin-top: 0.5rem;
        }


        /* --- Lists --- */
        ul {
            padding-left: 20px;
        }
        li {
            margin-bottom: 0.5rem;
        }
        li.separator {
            list-style-type: none;
            height: 1rem;
        }

        /* --- Code Blocks --- */
        pre {
            background-color: var(--code-bg) !important;
            border: 1px solid var(--border-color);
            border-radius: 6px;
            padding: 1rem;
            overflow-x: auto;
            font-family: "Fira Code", "Consolas", "Menlo", monospace;
            font-size: 0.9rem;
            line-height: 1.5;
            transition: background-color 0.3s, border-color 0.3s;
        }

        code {
             font-family: "Fira Code", "Consolas", "Menlo", monospace;
        }

        /* Hide syntax highlighting by default, enable with a class */
        .prism-highlight-disabled pre[class*="language-"] {
             color: var(--text-color) !important; /* Override prism styles */
        }
        .prism-highlight-disabled .token {
             all: unset !important; /* Forcefully remove token styling */
        }

        /* --- Navigation Bar --- */
        #navbar {
            text-align: center;
            padding: 1rem 0;
            border-bottom: 1px solid var(--border-color);
        }

        /* --- Footer --- */
        #footer {
            margin-top: 4rem;
            text-align: center;
            font-size: 0.9rem;
            color: #888;
        }
        body.dark-mode #footer {
            color: #777;
        }

        /* --- UI Controls --- */
        #userscript-controls {
            position: fixed;
            bottom: 20px;
            right: 20px;
            display: flex;
            flex-direction: column;
            gap: 10px;
            z-index: 9999;
        }

        #userscript-controls button {
            background-color: var(--card-bg);
            color: var(--text-color);
            border: 1px solid var(--border-color);
            border-radius: 50%;
            width: 48px;
            height: 48px;
            font-size: 24px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 2px 8px var(--shadow-color);
            transition: all 0.2s ease-in-out;
        }
        #userscript-controls button:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px var(--shadow-color);
            color: var(--primary-color);
        }
    `);

    // --- 3. UI CONTROLS (BUTTONS) ---
    const controlsContainer = document.createElement('div');
    controlsContainer.id = 'userscript-controls';
    controlsContainer.innerHTML = `
        <button id="theme-switcher" title="Toggle Light/Dark Mode"></button>
        <button id="highlight-switcher" title="Toggle Syntax Highlighting"></button>
    `;
    document.body.appendChild(controlsContainer);

    const themeSwitcher = document.getElementById('theme-switcher');
    const highlightSwitcher = document.getElementById('highlight-switcher');

    // --- 4. LOGIC & EVENT HANDLERS ---

    // Function to apply syntax highlighting
    function applySyntaxHighlighting() {
        if (config.syntaxHighlighting) {
            document.body.classList.remove('prism-highlight-disabled');
            // Inject correct Prism CSS based on theme
            if (document.getElementById('prism-styles')) document.getElementById('prism-styles').remove();
            const prismStyle = document.createElement('style');
            prismStyle.id = 'prism-styles';
            prismStyle.textContent = (config.theme === 'dark') ? prismCssDark : prismCssLight;
            document.head.appendChild(prismStyle);

            // Add language classes to <pre> tags for Prism
            document.querySelectorAll('pre').forEach(pre => {
                // Heuristic to guess language if not specified
                const codeElement = pre.querySelector('code') || pre;
                codeElement.textContent = dedent(codeElement.textContent);

                const codeContent = pre.textContent;
                let lang = '';
                
                if (/(::|->|=>|data|where|do)\b/.test(codeContent)) {
                    lang = 'haskell';
                } else {
                    lang = "ocaml";

                }

                if (lang) {
                    pre.classList.add(`language-${lang}`);
                    // Prism expects a <code> tag inside <pre>
                    if (!pre.querySelector('code')) {
                        pre.innerHTML = `<code class="language-${lang}">${pre.innerHTML}</code>`;
                    } else {
                         pre.querySelector('code').classList.add(`language-${lang}`);
                    }
                }
            });
            Prism.highlightAll();
        } else {
            document.body.classList.add('prism-highlight-disabled');
        }
        updateHighlightButton();
    }

    // Function to update theme
    function updateTheme() {
        document.body.classList.toggle('dark-mode', config.theme === 'dark');
        themeSwitcher.innerHTML = config.theme === 'dark' ? '☀️' : '🌙';
        // Re-apply highlighting to get the correct theme
        applySyntaxHighlighting();
    }

    // Function to update highlight button
    function updateHighlightButton() {
        highlightSwitcher.innerHTML = '✨';
        highlightSwitcher.style.opacity = config.syntaxHighlighting ? '1' : '0.5';
    }


    // Event Listeners
    themeSwitcher.addEventListener('click', () => {
        config.theme = (config.theme === 'light') ? 'dark' : 'light';
        GM_setValue('theme', config.theme);
        updateTheme();
    });

    highlightSwitcher.addEventListener('click', () => {
        config.syntaxHighlighting = !config.syntaxHighlighting;
        GM_setValue('syntaxHighlighting', config.syntaxHighlighting);
        applySyntaxHighlighting();
    });

    // --- 5. INITIALIZATION ---
    function init() {
        console.log("Oleg Kiselyov's Site Beautifier Initialized.");
        updateTheme(); // This will also call applySyntaxHighlighting
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        // The DOM is already ready.
        init();
    }

})();