{# 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>