markon-core 0.15.14

markon core - Mark it on.
{# Pre-paint runtime theme bootstrap, shared by the web entry points.
   Server-side `theme` is only the default. The browser owns the runtime
   preference so users can switch light/dark/auto without restarting markon. #}
<script>
    (function(){
        var STORAGE_KEY = 'markon.web.theme';
        var root = document.documentElement;
        var mql = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
        var modes = { auto: true, light: true, dark: true };
        var listenersReady = false;
        var panelBackdrop = null;
        var panel = null;
        var backdropClickHandler = null;
        var panelKeyHandler = null;

        function validMode(value) {
            return modes[value] ? value : null;
        }

        function serverDefault() {
            return validMode(root.getAttribute('data-theme-default'))
                || validMode(root.getAttribute('data-theme'))
                || 'auto';
        }

        function storedMode() {
            try {
                return validMode(window.localStorage && window.localStorage.getItem(STORAGE_KEY));
            } catch (_) {
                return null;
            }
        }

        function currentMode() {
            return storedMode() || serverDefault();
        }

        function resolve(mode) {
            if (mode === 'light' || mode === 'dark') return mode;
            return mql && mql.matches ? 'dark' : 'light';
        }

        function applyStylesheetMedia(resolved) {
            var theme = resolved || root.getAttribute('data-theme') || resolve(currentMode());
            var light = document.getElementById('markon-github-markdown-light');
            var dark = document.getElementById('markon-github-markdown-dark');
            if (light) light.media = theme === 'dark' ? 'not all' : 'all';
            if (dark) dark.media = theme === 'dark' ? 'all' : 'not all';
        }

        function themeLabel(mode) {
            var t = window.__MARKON_I18N__ && window.__MARKON_I18N__.t;
            var fallback = mode === 'light' ? 'Theme: Light' : mode === 'dark' ? 'Theme: Dark' : 'Theme: Auto';
            return t ? t('web.theme.link.' + mode) : fallback;
        }

        function optionLabel(mode) {
            var t = window.__MARKON_I18N__ && window.__MARKON_I18N__.t;
            var fallback = mode === 'light' ? 'Light' : mode === 'dark' ? 'Dark' : 'Auto';
            return t ? t('web.theme.option.' + mode) : fallback;
        }

        var LOGO_LIGHT_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><g fill="#27ae60"><path fill-rule="evenodd" clip-rule="evenodd" d="M11 4a9 9 0 0 0-9 9v6a9 9 0 0 0 9 9h10a9 9 0 0 0 9-9v-6a9 9 0 0 0-9-9zm4.707 8.293a.997.997 0 0 1 .293.698V19a1 1 0 1 1-2 0v-3.586l-2.293 2.293a1 1 0 0 1-1.414 0L8 15.414V19a1 1 0 1 1-2 0v-6a.997.997 0 0 1 1-1 .997.997 0 0 1 .707.293L11 15.586l3.293-3.293a.997.997 0 0 1 .704-.293h.006c.255 0 .51.098.704.293zM23 13a1 1 0 1 0-2 0v4.006l-1.427-.997a1 1 0 0 0-1.146 1.64l3 2.095a1 1 0 0 0 1.124.015l3.172-2.096a1 1 0 0 0-1.103-1.669L23 17.065z"/></g></svg>';
        var LOGO_DARK_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="2 2 28 28"><g fill="#ffffff"><path fill-rule="evenodd" clip-rule="evenodd" d="M11 4a9 9 0 0 0-9 9v6a9 9 0 0 0 9 9h10a9 9 0 0 0 9-9v-6a9 9 0 0 0-9-9zm4.707 8.293a.997.997 0 0 1 .293.698V19a1 1 0 1 1-2 0v-3.586l-2.293 2.293a1 1 0 0 1-1.414 0L8 15.414V19a1 1 0 1 1-2 0v-6a.997.997 0 0 1 1-1 .997.997 0 0 1 .707.293L11 15.586l3.293-3.293a.997.997 0 0 1 .704-.293h.006c.255 0 .51.098.704.293zM23 13a1 1 0 1 0-2 0v4.006l-1.427-.997a1 1 0 0 0-1.146 1.64l3 2.095a1 1 0 0 0 1.124.015l3.172-2.096a1 1 0 0 0-1.103-1.669L23 17.065z"/></g></svg>';

        function optionHtml(mode) {
            return (
                '<button type="button" class="markon-theme-option" data-markon-theme-mode="' + mode + '">' +
                    '<span class="markon-theme-preview markon-theme-preview-' + mode + '" aria-hidden="true">' +
                        '<span class="markon-theme-symbol">' +
                            '<span class="markon-theme-logo-light">' + LOGO_LIGHT_SVG + '</span>' +
                            '<span class="markon-theme-logo-dark">' + LOGO_DARK_SVG + '</span>' +
                        '</span>' +
                        '<span class="markon-theme-bars">' +
                            '<span></span><span></span>' +
                        '</span>' +
                    '</span>' +
                    '<span class="markon-theme-option-label">' + optionLabel(mode) + '</span>' +
                '</button>'
            );
        }

        function syncThemeEntry(mode) {
            var link = document.getElementById('theme-link-text');
            if (!link) return;
            link.textContent = themeLabel(mode);
            link.title = themeLabel(mode);
        }

        function syncPanel(mode) {
            if (!panel) return;
            var buttons = panel.querySelectorAll('[data-markon-theme-mode]');
            for (var i = 0; i < buttons.length; i++) {
                var active = buttons[i].getAttribute('data-markon-theme-mode') === mode;
                buttons[i].classList.toggle('is-active', active);
                buttons[i].setAttribute('aria-pressed', active ? 'true' : 'false');
            }
        }

        function apply(mode, persist) {
            mode = validMode(mode) || serverDefault();
            if (persist) {
                try { window.localStorage && window.localStorage.setItem(STORAGE_KEY, mode); } catch (_) {}
            }
            var resolved = resolve(mode);
            root.setAttribute('data-theme-mode', mode);
            root.setAttribute('data-theme', resolved);
            root.style.colorScheme = resolved;
            applyStylesheetMedia(resolved);
            syncThemeEntry(mode);
            syncPanel(mode);
        }

        function cleanupPanelListeners() {
            if (panelBackdrop && backdropClickHandler) {
                panelBackdrop.removeEventListener('pointerdown', backdropClickHandler, true);
                backdropClickHandler = null;
            }
            if (panelKeyHandler) {
                document.removeEventListener('keydown', panelKeyHandler, true);
                panelKeyHandler = null;
            }
        }

        function closePanel() {
            cleanupPanelListeners();
            if (panelBackdrop) {
                panelBackdrop.remove();
                panelBackdrop = null;
                panel = null;
                return;
            }
            if (panel) {
                panel.remove();
                panel = null;
            }
        }

        function openPanel() {
            closePanel();
            var t = window.__MARKON_I18N__ && window.__MARKON_I18N__.t;
            var title = t ? t('web.theme.title') : 'Theme';
            panelBackdrop = document.createElement('div');
            panelBackdrop.className = 'markon-theme-backdrop markon-modal-backdrop markon-modal-layer';
            panel = document.createElement('div');
            panel.className = 'markon-theme-panel markon-modal-frame';
            panel.setAttribute('role', 'dialog');
            panel.setAttribute('aria-modal', 'true');
            panel.setAttribute('aria-label', title);
            panel.innerHTML =
                '<div class="markon-theme-panel-title">' + title + '</div>' +
                '<div class="markon-theme-panel-options">' +
                    optionHtml('auto') +
                    optionHtml('light') +
                    optionHtml('dark') +
                '</div>';
            panelBackdrop.appendChild(panel);
            document.body.appendChild(panelBackdrop);
            syncPanel(currentMode());
            panel.querySelectorAll('[data-markon-theme-mode]').forEach(function(button) {
                button.addEventListener('click', function() {
                    apply(button.getAttribute('data-markon-theme-mode'), true);
                    closePanel();
                });
            });
            backdropClickHandler = function(event) {
                if (event.target !== panelBackdrop) return;
                closePanel();
            };
            panelKeyHandler = function(event) {
                if (event.key === 'Escape') {
                    event.preventDefault();
                    closePanel();
                }
            };
            setTimeout(function() {
                panelBackdrop && panelBackdrop.addEventListener('pointerdown', backdropClickHandler, true);
                document.addEventListener('keydown', panelKeyHandler, true);
                var active = panel && (panel.querySelector('button.is-active') || panel.querySelector('button'));
                if (active && active.focus) active.focus({ preventScroll: true });
            }, 0);
        }

        function togglePanel() {
            if (panel) closePanel();
            else openPanel();
        }

        function setupFooterEntry() {
            var link = document.getElementById('theme-link-text');
            if (!link || link.dataset.markonThemeBound === '1') return;
            link.dataset.markonThemeBound = '1';
            link.addEventListener('click', function(event) {
                event.preventDefault();
                togglePanel();
            });
        }

        function ensureListeners() {
            if (listenersReady) return;
            listenersReady = true;
            if (mql && mql.addEventListener) {
                mql.addEventListener('change', function() {
                    if (currentMode() === 'auto') apply('auto', false);
                });
            }
            document.addEventListener('DOMContentLoaded', function() {
                setupFooterEntry();
                apply(currentMode(), false);
            });
        }

        window.MarkonTheme = {
            storageKey: STORAGE_KEY,
            getMode: currentMode,
            getResolved: function(){ return root.getAttribute('data-theme') || resolve(currentMode()); },
            setMode: function(mode){ apply(mode, true); },
            openPanel: openPanel,
            togglePanel: togglePanel,
            closePanel: closePanel,
            apply: function(){ apply(currentMode(), false); },
            applyStylesheetMedia: function(){ applyStylesheetMedia(); }
        };

        apply(currentMode(), false);
        ensureListeners();
    })();
</script>
<style>
    .markon-theme-backdrop {
        position: fixed;
        inset: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        box-sizing: border-box;
        padding: 24px;
    }
    .markon-theme-panel {
        position: relative;
        z-index: var(--markon-z-chrome-toolbar);
        width: min(480px, 100%);
        box-sizing: border-box;
        color: var(--markon-fg-default);
        font: 14px/1.35 var(--markon-ui-font);
    }
    .markon-theme-panel-title {
        margin: 18px 18px 14px;
        color: var(--markon-fg-default);
        font-weight: 600;
        font-size: 15px;
        user-select: none;
    }
    .markon-theme-panel-options {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 14px;
        padding: 0 18px 16px;
    }
    .markon-theme-option {
        display: grid;
        justify-items: center;
        gap: 7px;
        border: 0;
        border-radius: var(--markon-radius-sm);
        background: transparent;
        color: var(--markon-fg-muted);
        font: inherit;
        padding: 6px;
        cursor: pointer;
        outline: none;
        transition: color 0.12s ease;
    }
    .markon-theme-option:hover {
        color: var(--markon-fg-default);
    }
    .markon-theme-option:focus-visible {
        box-shadow: 0 0 0 3px var(--markon-accent-muted);
    }
    .markon-theme-preview {
        position: relative;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 10px;
        width: min(116px, 100%);
        height: 82px;
        padding: 12px;
        overflow: hidden;
        box-sizing: border-box;
        isolation: isolate;
        border: 1px solid var(--markon-border-default);
        border-radius: 12px;
        background: #f8fafc;
        box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12);
        transition: border-color 0.12s ease, box-shadow 0.12s ease, transform 0.12s ease;
    }
    .markon-theme-option:hover .markon-theme-preview {
        border-color: color-mix(in srgb, var(--markon-accent) 45%, transparent);
        box-shadow:
            0 0 0 3px color-mix(in srgb, var(--markon-accent) 24%, transparent),
            0 1px 2px rgba(15, 23, 42, 0.12);
    }
    .markon-theme-option.is-active .markon-theme-preview {
        border-width: 3px;
        border-color: var(--markon-accent);
        box-shadow: none;
    }
    .markon-theme-option-label {
        color: var(--markon-fg-muted);
        font-weight: 500;
        line-height: 1.1;
    }
    .markon-theme-option.is-active .markon-theme-option-label {
        color: var(--markon-fg-default);
        font-weight: 650;
    }
    .markon-theme-preview-dark {
        background: #0f172a;
    }
    .markon-theme-preview-auto {
        background:
            linear-gradient(90deg, rgba(248, 250, 252, 0) 0 49.5%, rgba(15, 23, 42, 0.98) 50.5% 100%),
            #f8fafc;
    }
    .markon-theme-symbol {
        position: relative;
        display: block;
        width: 42px;
        height: 42px;
    }
    .markon-theme-logo-light,
    .markon-theme-logo-dark {
        position: absolute;
        inset: 0;
        display: block;
        width: 42px;
        height: 42px;
    }
    .markon-theme-symbol svg {
        display: block;
        width: 100%;
        height: 100%;
    }
    .markon-theme-logo-dark {
        display: none !important;
    }
    .markon-theme-preview-dark .markon-theme-logo-light {
        display: none !important;
    }
    .markon-theme-preview-dark .markon-theme-logo-dark {
        display: block !important;
    }
    .markon-theme-preview-auto .markon-theme-logo-light {
        clip-path: inset(0 50% 0 0);
    }
    .markon-theme-preview-auto .markon-theme-logo-dark {
        display: block !important;
        clip-path: inset(0 0 0 50%);
    }
    .markon-theme-bars {
        display: grid;
        justify-items: center;
        gap: 5px;
    }
    .markon-theme-bars span {
        display: block;
        height: 4px;
        border-radius: 999px;
        background: #1f2937;
    }
    .markon-theme-bars span:first-child {
        width: 58px;
    }
    .markon-theme-bars span:last-child {
        width: 38px;
        opacity: 0.45;
    }
    .markon-theme-preview-dark .markon-theme-bars span {
        background: #e2e8f0;
    }
    .markon-theme-preview-auto .markon-theme-bars span {
        background: linear-gradient(90deg, #1f2937 0 49.5%, #e2e8f0 50.5% 100%);
    }
    @media print {
        .markon-theme-backdrop,
        .markon-theme-panel { display: none !important; }
    }
</style>