Modern light/dark/system theme for bitcointalk.org with separate customisable Main and Accent presets, plus an OFF mode. v2.9.2: Classic-first light palette and theme-aware light-thread post alternation.
// ==UserScript==
// @name Bitcointalk Modern Theme
// @namespace bitcointalk-modern-theme
// @version 2.9.2
// @description Modern light/dark/system theme for bitcointalk.org with separate customisable Main and Accent presets, plus an OFF mode. v2.9.2: Classic-first light palette and theme-aware light-thread post alternation.
// @license MIT
// @match *://*.bitcointalk.org/*
// @match *://bitcointalk.org/*
// @run-at document-start
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function () {
'use strict';
// ===========================================================================
// Configuration
// ===========================================================================
const MAIN_PRESETS_DARK = {
slate: { name: 'Slate (default)', base: '#15171D' },
midnight: { name: 'Midnight', base: '#0F1424' },
forest: { name: 'Forest', base: '#0E1A18' },
plum: { name: 'Plum', base: '#1A0F1F' },
charcoal: { name: 'Charcoal', base: '#141414' },
};
const MAIN_PRESETS_LIGHT = {
// v2.8.3: Soft Sky remains the first/default light preset and is named
// Classic. The old Pearl palette remains the second option.
sky: { name: 'Classic (default)', base: '#EEF3F7' },
pearl: { name: 'Pearl', base: '#F4F5F8' },
cream: { name: 'Soft Cream', base: '#F4EFE6' },
mint: { name: 'Soft Mint', base: '#EEF3EF' },
rose: { name: 'Soft Rose', base: '#F5EEEE' },
};
const AUTO_ACCENT_MAP = {
light: {
sky: '#47677F', // classic muted steel blue
pearl: '#3A3DAA', // old pearl indigo
cream: '#9A6842', // muted rust
mint: '#4C735D', // muted pine
rose: '#7B5662', // muted plum
},
dark: {
slate: '#7F84D8', // muted periwinkle
midnight: '#8E84CF', // muted lavender
forest: '#55B7A9', // muted aqua
plum: '#B779C2', // muted orchid
charcoal: '#CC8E45', // muted amber
},
};
const ACCENTS_DARK = {
orange: { name: 'Bitcoin Orange', base: '#F7931A' },
};
const ACCENTS_LIGHT = {
orange: { name: 'Bitcoin Orange', base: '#D97706' },
};
const DEFAULT_THEME = 'dark';
const DEFAULT_ACCENT_DARK_KEY = 'orange';
const DEFAULT_ACCENT_LIGHT_KEY = 'auto';
const DEFAULT_MAIN_DARK_KEY = 'slate';
const DEFAULT_MAIN_LIGHT_KEY = 'sky';
const DEFAULT_CUSTOM_MAIN_DARK = '#15171D';
const DEFAULT_CUSTOM_MAIN_LIGHT = '#F4F5F8';
const DEFAULT_CUSTOM_ACCENT_DARK = '#F7931A';
const DEFAULT_CUSTOM_ACCENT_LIGHT = '#5B7FA0';
const KEY_THEME = 'bt-modern-theme';
const KEY_THEME_PRIOR = 'bt-modern-theme-prior';
const KEY_MAIN_DARK = 'bt-modern-main-dark';
const KEY_MAIN_LIGHT = 'bt-modern-main-light';
const KEY_ACCENT_DARK = 'bt-modern-accent-dark';
const KEY_ACCENT_LIGHT = 'bt-modern-accent-light';
const KEY_MAIN_DARK_CUSTOM = 'bt-modern-main-dark-custom';
const KEY_MAIN_LIGHT_CUSTOM = 'bt-modern-main-light-custom';
const KEY_ACCENT_DARK_CUSTOM = 'bt-modern-accent-dark-custom';
const KEY_ACCENT_LIGHT_CUSTOM = 'bt-modern-accent-light-custom';
const ALL_KEYS = [
KEY_THEME,
KEY_THEME_PRIOR,
KEY_MAIN_DARK, KEY_MAIN_LIGHT,
KEY_ACCENT_DARK, KEY_ACCENT_LIGHT,
KEY_MAIN_DARK_CUSTOM, KEY_MAIN_LIGHT_CUSTOM,
KEY_ACCENT_DARK_CUSTOM, KEY_ACCENT_LIGHT_CUSTOM,
];
// Safari flash guard constants (integrated extreme-fast variant)
const NAV_SELECTOR = '[class*="maintab_"] a, #main_menu a, .dropmenu a';
const INCOMING_HOLD_STYLE_ID = 'bt-paint-hold';
const OUTGOING_HOLD_STYLE_ID = 'bt-pre-nav-hold';
const HOLD_REVEALED_ATTR = 'data-bt-revealed';
const NAV_UNTIL_SESSION_KEY = 'bt-modern-nav-hold-until';
const NAV_BG_SESSION_KEY = 'bt-modern-nav-hold-bg';
const OUTGOING_CANCEL_RESTORE_MS = 120;
const NAV_HOLD_SESSION_MS = 800;
// ===========================================================================
// Storage layer
// ===========================================================================
const hasGM = typeof GM_getValue === 'function' && typeof GM_setValue === 'function';
function lsGet(key) {
try { return localStorage.getItem(key); } catch (e) { return null; }
}
function lsSet(key, value) {
try { localStorage.setItem(key, value); return true; } catch (e) { return false; }
}
function gmGet(key) {
if (!hasGM) return null;
try {
const v = GM_getValue(key, null);
return v == null ? null : String(v);
} catch (e) { return null; }
}
function gmSet(key, value) {
if (!hasGM) return false;
try { GM_setValue(key, String(value)); return true; } catch (e) { return false; }
}
function ssGet(key) {
try {
const v = sessionStorage.getItem(key);
return v == null || v === '' ? null : v;
} catch (e) { return null; }
}
function ssSet(key, value) {
try { sessionStorage.setItem(key, String(value)); } catch (e) {}
}
function ssRemove(key) {
try { sessionStorage.removeItem(key); } catch (e) {}
}
function getPref(key, fallback) {
let v = gmGet(key);
if (v == null || v === '') v = lsGet(key);
if (v == null || v === '') return fallback;
return v;
}
function setPref(key, value) {
const lsOk = lsSet(key, value);
const gmOk = gmSet(key, value);
return lsOk || gmOk;
}
function syncStores() {
ALL_KEYS.forEach(k => {
const ls = lsGet(k);
const gm = gmGet(k);
if (gm && (!ls || ls === '')) lsSet(k, gm);
else if (ls && (!gm || gm === '') && hasGM) gmSet(k, ls);
});
}
// ===========================================================================
// Color helpers
// ===========================================================================
function hexToRgb(hex) {
const c = String(hex || '').replace('#', '');
const full = c.length === 3 ? c.split('').map(x => x + x).join('') : c;
const n = parseInt(full, 16);
return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
}
function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }
function rgbToHex(r, g, b) {
return '#' + [r, g, b]
.map(x => clamp(Math.round(x), 0, 255).toString(16).padStart(2, '0'))
.join('');
}
function lighten(hex, amt) {
const { r, g, b } = hexToRgb(hex);
return rgbToHex(r + (255 - r) * amt, g + (255 - g) * amt, b + (255 - b) * amt);
}
function darken(hex, amt) {
const { r, g, b } = hexToRgb(hex);
return rgbToHex(r * (1 - amt), g * (1 - amt), b * (1 - amt));
}
function rgbToHsl(r, g, b) {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s; const l = (max + min) / 2;
if (max === min) {
h = 0; s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0));
else if (max === g) h = (b - r) / d + 2;
else h = (r - g) / d + 4;
h /= 6;
}
return { h, s, l };
}
function hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return { r: r * 255, g: g * 255, b: b * 255 };
}
function adjustHsl(hex, deltaL, deltaS) {
const { r, g, b } = hexToRgb(hex);
const hsl = rgbToHsl(r, g, b);
const newL = clamp(hsl.l + deltaL, 0, 1);
const newS = clamp(hsl.s + deltaS, 0, 1);
const rgb = hslToRgb(hsl.h, newS, newL);
return rgbToHex(rgb.r, rgb.g, rgb.b);
}
function relativeLuminance(hex) {
const { r, g, b } = hexToRgb(hex);
const [R, G, B] = [r, g, b].map(v => {
v /= 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
}
function deriveAccent(hex) {
return {
base: hex,
hover: lighten(hex, 0.15),
active: darken(hex, 0.10),
onText: relativeLuminance(hex) > 0.45 ? '#1A1D21' : '#FFFFFF',
};
}
function deriveMainPalette(hex, theme) {
if (theme === 'dark') {
return {
bg: hex,
bgEnd: darken(hex, 0.06),
surface1: lighten(hex, 0.06),
surface2: lighten(hex, 0.12),
surface3: lighten(hex, 0.20),
postBg: lighten(hex, 0.055),
postAltBg: lighten(hex, 0.085),
posterBg: lighten(hex, 0.050),
posterAltBg: lighten(hex, 0.075),
inputBg: darken(hex, 0.06),
quoteBg: lighten(hex, 0.04),
codeBg: darken(hex, 0.06),
border: 'rgba(255,255,255,0.07)',
border2: 'rgba(255,255,255,0.14)',
text: '#E3E3E8',
text2: '#A8B0BA',
text3: '#6B7480',
iconFilter: 'invert(1) opacity(0.75)',
shadow: '0 1px 0 rgba(0,0,0,0.2)',
shadowLg: '0 12px 32px rgba(0,0,0,0.35)',
};
}
// Light theme: all surfaces derive from the chosen main hex with hue
// preserved (HSL adjust). v2.8.3 also derives post alternation colours
// from the selected light theme, rather than hardcoding Classic/Sky.
const navActiveBg = adjustHsl(hex, -0.38, 0.36);
return {
bg: hex,
bgEnd: adjustHsl(hex, -0.035, 0.06),
surface1: adjustHsl(hex, -0.055, 0.12),
surface2: adjustHsl(hex, -0.12, 0.20),
surface3: adjustHsl(hex, -0.20, 0.28),
// Theme-aware thread alternation: Classic, Pearl, Cream, Mint and Rose
// each receive their own two matching post tones.
postBg: adjustHsl(hex, -0.010, 0.015),
postAltBg: adjustHsl(hex, -0.045, 0.060),
posterBg: adjustHsl(hex, -0.035, 0.045),
posterAltBg: adjustHsl(hex, -0.070, 0.090),
navActiveBg,
navActiveText: relativeLuminance(navActiveBg) > 0.45 ? '#1A1D21' : '#FFFFFF',
inputBg: '#FFFFFF',
quoteBg: adjustHsl(hex, -0.02, 0.04),
codeBg: adjustHsl(hex, -0.09, 0.18),
border: 'rgba(0,0,0,0.08)',
border2: 'rgba(0,0,0,0.16)',
text: '#1A1D21',
text2: '#4A5058',
text3: '#7A8088',
iconFilter: 'opacity(0.7)',
shadow: '0 1px 0 rgba(0,0,0,0.04)',
shadowLg: '0 12px 32px rgba(0,0,0,0.12)',
};
}
function deriveAutoAccent(mainHex, theme, mainKey) {
if (mainKey && AUTO_ACCENT_MAP[theme] && AUTO_ACCENT_MAP[theme][mainKey]) {
return AUTO_ACCENT_MAP[theme][mainKey];
}
const { r, g, b } = hexToRgb(mainHex);
const hsl = rgbToHsl(r, g, b);
if (hsl.s < 0.05) {
return theme === 'dark' ? '#E8A04A' : '#B5552B';
}
const newH = hsl.h;
const newS = Math.min(0.65, Math.max(0.50, hsl.s + 0.25));
const newL = theme === 'dark' ? 0.62 : 0.38;
const rgb = hslToRgb(newH, newS, newL);
return rgbToHex(rgb.r, rgb.g, rgb.b);
}
function toCssVar(name) {
return '--bt-' + name
.replace(/([A-Z]|\d+)/g, '-$1')
.toLowerCase()
.replace(/^-/, '');
}
// ===========================================================================
// Theme application
// ===========================================================================
function getEffectiveTheme(pref) {
if (pref === 'off') return 'off';
if (pref === 'system') {
return window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark' : 'light';
}
return pref === 'light' ? 'light' : 'dark';
}
function getMainKey(theme) {
const def = theme === 'dark' ? DEFAULT_MAIN_DARK_KEY : DEFAULT_MAIN_LIGHT_KEY;
return getPref(theme === 'dark' ? KEY_MAIN_DARK : KEY_MAIN_LIGHT, def);
}
function getAccentKey(theme) {
const def = theme === 'dark' ? DEFAULT_ACCENT_DARK_KEY : DEFAULT_ACCENT_LIGHT_KEY;
return getPref(theme === 'dark' ? KEY_ACCENT_DARK : KEY_ACCENT_LIGHT, def);
}
function getActiveMainHex(theme) {
const presets = theme === 'dark' ? MAIN_PRESETS_DARK : MAIN_PRESETS_LIGHT;
const key = getMainKey(theme);
if (key === 'custom') {
const def = theme === 'dark' ? DEFAULT_CUSTOM_MAIN_DARK : DEFAULT_CUSTOM_MAIN_LIGHT;
return getPref(theme === 'dark' ? KEY_MAIN_DARK_CUSTOM : KEY_MAIN_LIGHT_CUSTOM, def);
}
const fallbackKey = theme === 'dark' ? DEFAULT_MAIN_DARK_KEY : DEFAULT_MAIN_LIGHT_KEY;
return (presets[key] || presets[fallbackKey]).base;
}
function getActiveAccentHex(theme) {
const presets = theme === 'dark' ? ACCENTS_DARK : ACCENTS_LIGHT;
const key = getAccentKey(theme);
if (key === 'auto') {
return deriveAutoAccent(getActiveMainHex(theme), theme, getMainKey(theme));
}
if (key === 'custom') {
const def = theme === 'dark' ? DEFAULT_CUSTOM_ACCENT_DARK : DEFAULT_CUSTOM_ACCENT_LIGHT;
return getPref(theme === 'dark' ? KEY_ACCENT_DARK_CUSTOM : KEY_ACCENT_LIGHT_CUSTOM, def);
}
if (presets[key]) {
return presets[key].base;
}
return deriveAutoAccent(getActiveMainHex(theme), theme, getMainKey(theme));
}
function applyTheme() {
const themePref = getPref(KEY_THEME, DEFAULT_THEME);
const effective = getEffectiveTheme(themePref);
const root = document.documentElement;
if (effective === 'off') {
root.setAttribute('data-bt-active', 'false');
root.setAttribute('data-bt-theme-pref', themePref);
if (themeStyleEl) themeStyleEl.disabled = true;
root.style.colorScheme = '';
updateSwitcherUI(themePref, effective, null, null, null, null);
return;
}
if (themeStyleEl) themeStyleEl.disabled = false;
const mainKey = getMainKey(effective);
const accentKey = getAccentKey(effective);
const mainHex = getActiveMainHex(effective);
const accentHex = getActiveAccentHex(effective);
const palette = deriveMainPalette(mainHex, effective);
const accent = deriveAccent(accentHex);
root.setAttribute('data-bt-active', 'true');
root.setAttribute('data-bt-theme', effective);
root.setAttribute('data-bt-theme-pref', themePref);
root.setAttribute('data-bt-main', mainKey);
root.setAttribute('data-bt-accent', accentKey);
root.style.colorScheme = effective;
Object.entries(palette).forEach(([k, v]) => root.style.setProperty(toCssVar(k), v));
root.style.setProperty('--bt-accent', accent.base);
root.style.setProperty('--bt-accent-h', accent.hover);
root.style.setProperty('--bt-accent-a', accent.active);
root.style.setProperty('--bt-on-accent', accent.onText);
updateSwitcherUI(themePref, effective, mainKey, accentKey, mainHex, accentHex);
}
function updateSwitcherUI(themePref, effective, mainKey, accentKey, mainHex, accentHex) {
const sw = document.getElementById('bt-switcher');
if (!sw) return;
const mainBtn = sw.querySelector('#bt-switcher-button');
if (mainBtn) {
if (themePref === 'off') {
mainBtn.innerHTML = '<span class="bt-sw-on-text">ON</span>';
mainBtn.title = 'Turn the theme back on';
mainBtn.setAttribute('aria-label', 'Turn the theme back on');
} else {
mainBtn.innerHTML = ICONS.cog;
mainBtn.title = 'Theme settings';
mainBtn.setAttribute('aria-label', 'Theme settings');
}
}
const customizeEl = sw.querySelector('.bt-sw-customize');
if (customizeEl) {
const hide = themePref === 'system' || themePref === 'off';
customizeEl.style.display = hide ? 'none' : '';
}
const titleEl = sw.querySelector('#bt-sw-customize-title');
if (titleEl && effective !== 'off') {
titleEl.textContent = `Customise ${effective === 'dark' ? 'Dark' : 'Light'} Colours`;
}
sw.querySelectorAll('[data-theme]').forEach(b => {
b.classList.toggle('selected', b.dataset.theme === themePref);
});
const offBtn = sw.querySelector('[data-theme="off"]');
if (offBtn) {
const lbl = offBtn.querySelector('.bt-sw-theme-label');
if (lbl) lbl.textContent = themePref === 'off' ? 'ON' : 'OFF';
offBtn.title = themePref === 'off' ? 'Turn the theme back on' : 'Turn the theme off';
}
if (effective !== 'off') {
rebuildSwatches(sw, 'main', effective, mainKey, mainHex);
rebuildSwatches(sw, 'accent', effective, accentKey, accentHex);
}
}
function rebuildSwatches(sw, kind, effective, selectedKey, currentHex) {
const row = sw.querySelector(`#bt-sw-${kind}-row`);
if (!row) return;
const presets = kind === 'main'
? (effective === 'dark' ? MAIN_PRESETS_DARK : MAIN_PRESETS_LIGHT)
: (effective === 'dark' ? ACCENTS_DARK : ACCENTS_LIGHT);
const customDef = kind === 'main'
? (effective === 'dark' ? DEFAULT_CUSTOM_MAIN_DARK : DEFAULT_CUSTOM_MAIN_LIGHT)
: (effective === 'dark' ? DEFAULT_CUSTOM_ACCENT_DARK : DEFAULT_CUSTOM_ACCENT_LIGHT);
const customStorageKey = kind === 'main'
? (effective === 'dark' ? KEY_MAIN_DARK_CUSTOM : KEY_MAIN_LIGHT_CUSTOM)
: (effective === 'dark' ? KEY_ACCENT_DARK_CUSTOM : KEY_ACCENT_LIGHT_CUSTOM);
const customHex = getPref(customStorageKey, customDef);
const isCustomSelected = selectedKey === 'custom';
const isAutoSelected = selectedKey === 'auto';
const presetSwatches = Object.entries(presets).map(([key, p]) =>
`<button type="button" class="bt-sw-swatch${key === selectedKey ? ' selected' : ''}" data-${kind}="${key}" title="${p.name}" style="background:${p.base}"></button>`
).join('');
const autoSwatch = kind === 'accent'
? `<button type="button" class="bt-sw-swatch bt-sw-auto${isAutoSelected ? ' selected' : ''}" data-${kind}="auto" title="Auto (matches your Main)"></button>`
: '';
row.innerHTML = autoSwatch + presetSwatches + `
<span class="bt-sw-custom-wrap${isCustomSelected ? ' selected' : ''}" data-target="${kind}" title="Custom ${kind} colour">
<span class="bt-sw-custom-display"${isCustomSelected ? ` style="background:${currentHex}"` : ''}></span>
<input type="color" data-target="${kind}" value="${customHex}" />
</span>
`;
}
// ===========================================================================
// Maintab fix
// ===========================================================================
function fixMaintabCaps() {
document.querySelectorAll('[class*="maintab"], [class*="mirrortab"]').forEach(el => {
const text = (el.textContent || '').replace(/\s+/g, '');
if (!text) el.style.cssText = 'display: none !important;';
});
}
// ===========================================================================
// BBCode icons
// ===========================================================================
const BBCODE_SVGS = {
bold: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4h7a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/></svg>',
italic: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg>',
underline: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3v8a6 6 0 0 0 12 0V3"/><line x1="4" y1="21" x2="20" y2="21"/></svg>',
strike: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4H9a3 3 0 0 0-2.83 4M14 12a4 4 0 0 1 0 8H6"/><line x1="4" y1="12" x2="20" y2="12"/></svg>',
pre: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>',
code: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>',
quote: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.75-2-2-2H4c-1.25 0-2 .75-2 2v6c0 1.25.75 2 2 2h1v1c0 1-1 2-2 2v3c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.75-2-2-2h-4c-1.25 0-2 .75-2 2v6c0 1.25.75 2 2 2h1v1c0 1-1 2-2 2v3c0 1 0 1 1 1z"/></svg>',
img: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>',
url: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>',
email: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><polyline points="22,6 12,13 2,6"/></svg>',
ftp: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>',
flash: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>',
youtube: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z"/><polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02"/></svg>',
list: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><circle cx="3.5" cy="6" r="1"/><circle cx="3.5" cy="12" r="1"/><circle cx="3.5" cy="18" r="1"/></svg>',
orderlist: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/></svg>',
left: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="17" y1="10" x2="3" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="17" y1="18" x2="3" y2="18"/></svg>',
center: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="10" x2="6" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="18" y1="18" x2="6" y2="18"/></svg>',
right: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="21" y1="10" x2="7" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="21" y1="18" x2="7" y2="18"/></svg>',
hr: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"/></svg>',
sub: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5l8 11"/><path d="M12 5L4 16"/><text x="14" y="22" font-size="9" fill="currentColor" stroke="none">2</text></svg>',
sup: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 8l8 11"/><path d="M12 8L4 19"/><text x="14" y="9" font-size="9" fill="currentColor" stroke="none">2</text></svg>',
tt: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" y1="20" x2="15" y2="20"/><line x1="12" y1="4" x2="12" y2="20"/></svg>',
font: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" y1="20" x2="15" y2="20"/><line x1="12" y1="4" x2="12" y2="20"/></svg>',
size: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7V4h18v3"/><path d="M8 20h8"/><path d="M12 4v16"/><path d="M18 14v6"/><path d="M15 14h6"/></svg>',
color: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10c.93 0 1.69-.76 1.69-1.69 0-.43-.16-.83-.43-1.13-.27-.31-.43-.71-.43-1.13a1.69 1.69 0 0 1 1.69-1.69h1.99c3.04 0 5.49-2.45 5.49-5.49C22 6.04 17.52 2 12 2z"/><circle cx="6.5" cy="11.5" r="1.5"/><circle cx="9.5" cy="7.5" r="1.5"/><circle cx="14.5" cy="7.5" r="1.5"/><circle cx="17.5" cy="11.5" r="1.5"/></svg>',
table: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/></svg>',
glow: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>',
shadow: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 1 0 18z" fill="currentColor"/></svg>',
spoiler: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>',
abbr: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>',
bitcoin: '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M16.43 11.36c.5-.4.85-1 .85-1.74 0-1.6-1.27-2.66-3.06-2.84V5h-1.5v1.7H11V5H9.5v1.7H7v1.5h1.2V16H7v1.5h2.5V19H11v-1.5h1.72V19h1.5v-1.5c2.05 0 3.34-1.18 3.34-3 0-.93-.42-1.74-1.13-2.14zm-5.93-3.16h2.74c.74 0 1.3.42 1.3 1.2 0 .76-.56 1.2-1.3 1.2H10.5V8.2zm3.04 7.6H10.5v-2.6h3.04c.83 0 1.46.5 1.46 1.3 0 .8-.63 1.3-1.46 1.3z"/></svg>',
trow: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><rect x="3" y="9" width="18" height="6" fill="currentColor" stroke="none" opacity="0.35"/></svg>',
tcol: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/><rect x="9" y="3" width="6" height="18" fill="currentColor" stroke="none" opacity="0.35"/></svg>',
trash: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"/></svg>',
pencil: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>',
};
const BBCODE_ALIASES = {
italic: ['italicize'],
bold: ['bolden'],
strike: ['strikethrough', 'linethrough'],
tt: ['teletype', 'monospace', 'tele'],
font: ['fontface', 'fontname', 'face'],
color: ['colour', 'fontcolour', 'fontcolor'],
size: ['fontsize'],
left: ['alignleft'],
center: ['aligncenter'],
right: ['alignright'],
list: ['bullist'],
orderlist: ['numlist', 'numberedlist'],
img: ['image', 'picture'],
url: ['link', 'hyperlink'],
email: ['mail'],
youtube: ['video', 'youtu'],
hr: ['horizontalrule'],
sub: ['subscript'],
sup: ['superscript'],
bitcoin: ['btc'],
trow: ['tr'],
tcol: ['td'],
trash: ['delete', 'remove', 'del'],
pencil: ['modify', 'edit', 'pen'],
};
Object.entries(BBCODE_ALIASES).forEach(([canon, aliases]) => {
aliases.forEach(a => { BBCODE_SVGS[a] = BBCODE_SVGS[canon]; });
});
function modernizeBBCodeIcons() {
const imgs = document.querySelectorAll(
'#bbcBox_message img, .bbc_buttons img, [id^="bbc_"] img, ' +
'img[src*="/bbc/"], img[src*="bbc_"], ' +
'img[src*="/frostee/"], img[src*="frostee_"], ' +
'img.reply_button, img.remove_button, img.modify_button, ' +
'img[class$="_button"]'
);
imgs.forEach(img => {
if (img.dataset.btReplaced === '1') return;
const src = img.getAttribute('src') || '';
const m = src.match(/\/(\w+)\.(?:gif|png)/i);
const name = m ? m[1].toLowerCase().replace(/^(bbc|frostee|theme)_/, '') : '';
const isPostAction = /\/frostee\//i.test(src) ||
img.classList.contains('reply_button') ||
img.classList.contains('modify_button') ||
img.classList.contains('remove_button');
if (isPostAction) {
const POST_ACTION_LABELS = {
quote: 'Quote',
edit: 'Edit',
modify: 'Edit',
delete: 'Delete',
remove: 'Delete',
};
const label = POST_ACTION_LABELS[name];
if (label) {
const span = document.createElement('span');
span.className = 'bt-post-action';
span.title = img.alt || img.title || label;
span.textContent = label;
span.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
img.click();
});
img.dataset.btReplaced = '1';
img.style.cssText = 'display: none !important;';
img.parentNode.insertBefore(span, img);
return;
}
}
const svg = BBCODE_SVGS[name];
if (!svg) return;
const span = document.createElement('span');
span.className = 'bt-bbc-icon';
span.title = img.alt || img.title || name;
span.setAttribute('aria-label', span.title);
span.innerHTML = svg;
span.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
img.click();
});
img.dataset.btReplaced = '1';
img.style.cssText = 'display: none !important;';
img.parentNode.insertBefore(span, img);
});
}
// ===========================================================================
// Smiley modernisation
// ===========================================================================
const SMILEYS = {
smiley: '🙂', happy: '🙂',
wink: '😉',
cheesy: '😄', grin: '😀',
sad: '🙁', cry: '😢',
shocked: '😮', surprised: '😮',
cool: '😎', sunglasses: '😎',
huh: '🤔', confused: '😕', undecided: '😕',
tongue: '😛',
embarrassed: '😳',
lipsrsealed: '🤐',
angry: '😠', mad: '😡',
kiss: '😘',
rolleyes: '🙄',
evil: '😈',
azn: '😏',
afro: '🕺',
laugh: '😆', lol: '😂',
police: '👮',
wassat: '🤨',
chocked: '😱',
skeptical: '🤨',
facepalm: '🤦',
thumbsup: '👍', thumbup: '👍',
thumbsdown: '👎', thumbdown: '👎',
heart: '❤️',
star: '⭐',
fire: '🔥',
check: '✅', tick: '✅',
cross: '❌',
};
function stylePostMetaButtons() {
document.querySelectorAll('a').forEach(a => {
if (a.dataset.btPostMeta === '1') return;
const text = (a.textContent || '').trim();
const href = a.getAttribute('href') || '';
const isMerit = /^\+\s*merit\b/i.test(text);
const isMsgNum = /^#\d+$/.test(text) &&
/(topic=|#msg|action=display|action=profile)/i.test(href);
if (isMerit || isMsgNum) {
a.classList.add('bt-post-action');
a.dataset.btPostMeta = '1';
}
});
}
function modernizeSmileys() {
const imgs = document.querySelectorAll(
'img[src*="Smileys/"], img[src*="/smileys/"], img.smiley'
);
imgs.forEach(img => {
if (img.dataset.btSmiley === '1') return;
const src = img.getAttribute('src') || '';
if (!/(Smileys|smileys|smiley)/i.test(src) && !img.classList.contains('smiley')) return;
const m = src.match(/\/(\w+)\.(?:gif|png|jpg)/i);
const name = m ? m[1].toLowerCase() : '';
const emoji = SMILEYS[name];
if (!emoji) return;
const span = document.createElement('span');
span.className = 'bt-smiley';
span.textContent = emoji;
span.title = img.alt || img.title || name;
img.dataset.btSmiley = '1';
img.style.cssText = 'display: none !important;';
img.parentNode.insertBefore(span, img);
});
}
// ===========================================================================
// CSS
// ===========================================================================
const css = `
:root[data-bt-theme="dark"] {
--bt-bg: #1A1D21;
--bt-surface-1: #22262B;
--bt-surface-2: #2A2F35;
--bt-surface-3: #343A41;
--bt-border: rgba(255,255,255,0.07);
--bt-border-2: rgba(255,255,255,0.14);
--bt-text: #E3E3E8;
--bt-text-2: #A8B0BA;
--bt-text-3: #6B7480;
--bt-input-bg: #14171B;
--bt-quote-bg: #1F2328;
--bt-code-bg: #14171B;
--bt-bg-end: #17191D;
--bt-icon-filter: invert(1) opacity(0.75);
--bt-shadow: 0 1px 0 rgba(0,0,0,0.2);
--bt-shadow-lg: 0 12px 32px rgba(0,0,0,0.35);
}
:root[data-bt-theme="light"] {
--bt-bg: #F2F4F7;
--bt-surface-1: #E3E6EA;
--bt-surface-2: #D7DAE0;
--bt-surface-3: #C5C8CD;
--bt-post-bg: #EEF3F7;
--bt-post-alt-bg: #E3EBF1;
--bt-poster-bg: #E8EEF3;
--bt-poster-alt-bg: #DCE7EE;
--bt-border: rgba(0,0,0,0.08);
--bt-border-2: rgba(0,0,0,0.16);
--bt-text: #1A1D21;
--bt-text-2: #4A5058;
--bt-text-3: #7A8088;
--bt-input-bg: #FFFFFF;
--bt-quote-bg: #E1E4E8;
--bt-code-bg: #DBDEE3;
--bt-bg-end: #E9ECEF;
--bt-icon-filter: opacity(0.7);
--bt-shadow: 0 1px 0 rgba(0,0,0,0.04);
--bt-shadow-lg: 0 12px 32px rgba(0,0,0,0.12);
}
:root {
--bt-radius: 10px;
--bt-radius-sm: 6px;
--bt-radius-lg: 14px;
--bt-font: 'Source Sans 3', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--bt-mono: 'Cascadia Code', 'JetBrains Mono', 'SF Mono', Menlo, Consolas, monospace;
--bt-trans: 150ms ease;
--bt-danger: #EF4444;
--bt-accent: #F7931A;
--bt-accent-h: #FFA733;
--bt-accent-a: #E08410;
--bt-on-accent: #1A1D21;
}
/*-- BT_ALWAYS_ON_START --*/
:root[data-bt-active="false"] .bt-bbc-icon,
:root[data-bt-active="false"] .bt-smiley,
:root[data-bt-active="false"] .bt-post-action {
display: none !important;
}
:root[data-bt-active="false"] img[data-bt-replaced="1"],
:root[data-bt-active="false"] img[data-bt-smiley="1"] {
display: inline !important;
}
[data-bt-no-transitions] *,
[data-bt-no-transitions] *::before,
[data-bt-no-transitions] *::after {
transition: none !important;
animation-duration: 0s !important;
}
/*-- BT_ALWAYS_ON_END --*/
html,
body {
background: var(--bt-bg) !important;
color: var(--bt-text) !important;
font-family: var(--bt-font) !important;
font-size: 14px !important;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
body {
background:
radial-gradient(140% 100% at 15% -10%, color-mix(in srgb, var(--bt-accent) 6%, transparent) 0%, transparent 55%),
radial-gradient(120% 100% at 90% -5%, color-mix(in srgb, var(--bt-accent) 3%, transparent) 0%, transparent 60%),
linear-gradient(180deg, var(--bt-bg) 0%, var(--bt-bg-end) 100%) !important;
background-attachment: fixed !important;
}
body, td, th, tr { color: var(--bt-text) !important; }
h1, h2, h3, h4, h5, h6 {
color: var(--bt-text) !important;
font-weight: 600 !important;
letter-spacing: -0.005em;
}
a, a:link, a:visited {
color: var(--bt-accent) !important;
text-decoration: none !important;
transition: color var(--bt-trans) !important;
}
a:hover, a:active, a:focus {
color: var(--bt-accent-h) !important;
text-decoration: none !important;
}
#upper_section, #header, #wrapper, #content_section, #main_content_section,
#footer_section, .frame, #bodyarea, .navigate_section, .roundframe,
#content {
background: transparent !important;
color: var(--bt-text) !important;
}
table.bordercolor,
table.table_list,
table.table_grid,
.tborder {
background: var(--bt-surface-1) !important;
border: 1px solid var(--bt-border-2) !important;
border-radius: var(--bt-radius) !important;
overflow: hidden !important;
border-spacing: 0 !important;
box-shadow: var(--bt-shadow);
}
[class*="windowbg"],
td[class*="windowbg"],
tr[class*="windowbg"] > td {
background-color: var(--bt-surface-1) !important;
background-image: none !important;
color: var(--bt-text) !important;
border-color: var(--bt-border) !important;
border-bottom: 1px solid var(--bt-border) !important;
}
tr.windowbg:hover > td,
tr.windowbg2:hover > td,
tr.windowbg3:hover > td {
background-color: var(--bt-surface-2) !important;
}
/* v2.8.1: restore the classic-looking post alternation on light-theme
thread pages. The previous attempt only caught a narrow header row on
some thread layouts (the "Report to moderator" strip), because the
actual post body/sidebar wrappers are often .td_headerandpost, .post,
.poster and .poster_info rather than the row cell that carries
windowbg/windowbg2 itself.
We therefore paint BOTH the row cells and the common wrappers inside
them, so the entire post block alternates again like classic
bitcointalk/SMF. */
:root[data-bt-theme="light"] tr.windowbg > td,
:root[data-bt-theme="light"] td.windowbg,
:root[data-bt-theme="light"] tr.windowbg td.td_headerandpost,
:root[data-bt-theme="light"] td.windowbg.td_headerandpost,
:root[data-bt-theme="light"] tr.windowbg td.post,
:root[data-bt-theme="light"] td.windowbg.post,
:root[data-bt-theme="light"] tr.windowbg td.poster,
:root[data-bt-theme="light"] tr.windowbg td.poster_info,
:root[data-bt-theme="light"] td.windowbg.poster,
:root[data-bt-theme="light"] td.windowbg.poster_info {
background-color: #e9f0f7 !important;
background-image: none !important;
}
:root[data-bt-theme="light"] tr.windowbg2 > td,
:root[data-bt-theme="light"] td.windowbg2,
:root[data-bt-theme="light"] tr.windowbg2 td.td_headerandpost,
:root[data-bt-theme="light"] td.windowbg2.td_headerandpost,
:root[data-bt-theme="light"] tr.windowbg2 td.post,
:root[data-bt-theme="light"] td.windowbg2.post,
:root[data-bt-theme="light"] tr.windowbg2 td.poster,
:root[data-bt-theme="light"] tr.windowbg2 td.poster_info,
:root[data-bt-theme="light"] td.windowbg2.poster,
:root[data-bt-theme="light"] td.windowbg2.poster_info {
background-color: #dfe9f3 !important;
background-image: none !important;
}
:root[data-bt-theme="light"] tr.windowbg3 > td,
:root[data-bt-theme="light"] td.windowbg3,
:root[data-bt-theme="light"] tr.windowbg3 td.td_headerandpost,
:root[data-bt-theme="light"] td.windowbg3.td_headerandpost,
:root[data-bt-theme="light"] tr.windowbg3 td.post,
:root[data-bt-theme="light"] td.windowbg3.post,
:root[data-bt-theme="light"] tr.windowbg3 td.poster,
:root[data-bt-theme="light"] tr.windowbg3 td.poster_info,
:root[data-bt-theme="light"] td.windowbg3.poster,
:root[data-bt-theme="light"] td.windowbg3.poster_info {
background-color: #e9f0f7 !important;
background-image: none !important;
}
:root[data-bt-theme="light"] tr.windowbg:hover > td,
:root[data-bt-theme="light"] tr.windowbg:hover td.td_headerandpost,
:root[data-bt-theme="light"] tr.windowbg:hover td.post,
:root[data-bt-theme="light"] tr.windowbg:hover td.poster,
:root[data-bt-theme="light"] tr.windowbg:hover td.poster_info,
:root[data-bt-theme="light"] tr.windowbg2:hover > td,
:root[data-bt-theme="light"] tr.windowbg2:hover td.td_headerandpost,
:root[data-bt-theme="light"] tr.windowbg2:hover td.post,
:root[data-bt-theme="light"] tr.windowbg2:hover td.poster,
:root[data-bt-theme="light"] tr.windowbg2:hover td.poster_info,
:root[data-bt-theme="light"] tr.windowbg3:hover > td,
:root[data-bt-theme="light"] tr.windowbg3:hover td.td_headerandpost,
:root[data-bt-theme="light"] tr.windowbg3:hover td.post,
:root[data-bt-theme="light"] tr.windowbg3:hover td.poster,
:root[data-bt-theme="light"] tr.windowbg3:hover td.poster_info {
background-color: #d7e3ef !important;
background-image: none !important;
}
.poster, td.poster, .poster_info, td.poster_info, [id*="poster"] {
background-color: var(--bt-surface-1) !important;
background-image: none !important;
color: var(--bt-text) !important;
}
:root[data-bt-theme="dark"] td.poster_info *,
:root[data-bt-theme="dark"] td.poster *,
:root[data-bt-theme="dark"] .poster_info * {
color: var(--bt-text-2) !important;
}
:root[data-bt-theme="dark"] td.poster_info b a,
:root[data-bt-theme="dark"] td.poster_info h4 a,
:root[data-bt-theme="dark"] .poster_info b a,
:root[data-bt-theme="dark"] .poster_info h4 a {
color: var(--bt-text) !important;
font-weight: 600 !important;
}
:root[data-bt-theme="dark"] td.poster_info b a:hover,
:root[data-bt-theme="dark"] td.poster_info h4 a:hover,
:root[data-bt-theme="dark"] .poster_info b a:hover,
:root[data-bt-theme="dark"] .poster_info h4 a:hover {
color: var(--bt-accent) !important;
}
:root[data-bt-theme="dark"] td.poster_info a,
:root[data-bt-theme="dark"] .poster_info a {
color: var(--bt-accent) !important;
}
:root[data-bt-theme="dark"] td.poster_info a:hover,
:root[data-bt-theme="dark"] .poster_info a:hover {
color: var(--bt-accent-h) !important;
}
:root[data-bt-theme="dark"] td.poster_info img,
:root[data-bt-theme="dark"] .poster_info img { color: initial !important; }
table.bordercolor tr[style] > td,
table.bordercolor td[style],
.tborder tr[style] > td,
.tborder td[style] {
background-color: var(--bt-surface-1) !important;
background-image: none !important;
}
[class*="catbg"], [class*="titlebg"], td[class*="catbg"], td[class*="titlebg"],
th[class*="catbg"], th[class*="titlebg"], tr[class*="catbg"] > td,
tr[class*="catbg"] > th, tr[class*="catbg"] td, tr[class*="catbg"] th,
tr[class*="titlebg"] > td, tr[class*="titlebg"] > th, tr[class*="titlebg"] td,
tr[class*="titlebg"] th, h3[class*="catbg"], h4[class*="catbg"],
h3[class*="titlebg"], h4[class*="titlebg"] {
background-color: var(--bt-surface-2) !important;
background-image: none !important;
color: var(--bt-text) !important;
border-bottom: 1px solid var(--bt-border-2) !important;
font-weight: 600 !important;
letter-spacing: 0.01em;
text-shadow: none !important;
}
[class*="catbg"] a, [class*="titlebg"] a, tr[class*="catbg"] a, tr[class*="titlebg"] a {
color: var(--bt-text) !important;
}
[class*="catbg"] a:hover, [class*="titlebg"] a:hover, tr[class*="catbg"] a:hover, tr[class*="titlebg"] a:hover {
color: var(--bt-accent) !important;
}
th {
background-color: var(--bt-surface-2) !important;
background-image: none !important;
color: var(--bt-text) !important;
}
.post, td.post, #preview_body, #preview_section, #preview_section td,
#preview_section table, .personalmessage, td.personalmessage {
background-color: var(--bt-surface-1) !important;
background-image: none !important;
color: var(--bt-text) !important;
}
.post { line-height: 1.6 !important; font-size: 14px !important; }
.signature {
color: var(--bt-text-3) !important;
border-top: 1px solid var(--bt-border) !important;
padding-top: 8px !important;
margin-top: 12px !important;
font-size: 12px !important;
}
.smalltext, .middletext, .small_header, .nav, a.nav, .navPages, .prevnext,
.lastpostcol, .ignored_topic, .utusers, .utusers a {
color: var(--bt-text-2) !important;
}
.smalltext a, .middletext a, .small_header a, .lastpostcol a {
color: var(--bt-accent) !important;
}
.quoteheader, .codeheader {
background: var(--bt-surface-3) !important;
color: var(--bt-text-2) !important;
border: 1px solid var(--bt-border) !important;
border-bottom: none !important;
border-radius: var(--bt-radius-sm) var(--bt-radius-sm) 0 0 !important;
padding: 6px 10px !important;
font-size: 12px !important;
font-weight: 500 !important;
}
.quote {
background: var(--bt-quote-bg) !important;
color: var(--bt-text-2) !important;
border: 1px solid var(--bt-border) !important;
border-left: 3px solid var(--bt-accent) !important;
border-radius: 0 var(--bt-radius-sm) var(--bt-radius-sm) var(--bt-radius-sm) !important;
padding: 10px 14px !important;
margin: 6px 0 12px !important;
}
.code {
background: var(--bt-code-bg) !important;
color: var(--bt-text) !important;
font-family: var(--bt-mono) !important;
border: 1px solid var(--bt-border) !important;
border-radius: 0 0 var(--bt-radius-sm) var(--bt-radius-sm) !important;
padding: 10px 14px !important;
}
.button_submit, .button_reset, input[type="submit"], input[type="button"],
input[type="reset"], button:not([id^="bt-"]):not([class^="bt-sw-"]),
.button:not([id^="bt-"]) {
background: var(--bt-accent) !important;
color: var(--bt-on-accent) !important;
border: 1px solid var(--bt-accent) !important;
border-radius: var(--bt-radius-sm) !important;
padding: 8px 14px !important;
font-family: var(--bt-font) !important;
font-weight: 600 !important;
font-size: 13px !important;
cursor: pointer !important;
transition: background var(--bt-trans), transform var(--bt-trans), box-shadow var(--bt-trans) !important;
box-shadow: none !important;
text-shadow: none !important;
}
.button_submit:hover, .button_reset:hover, input[type="submit"]:hover,
input[type="button"]:hover, input[type="reset"]:hover,
button:not([id^="bt-"]):not([class^="bt-sw-"]):hover,
.button:not([id^="bt-"]):hover {
background: var(--bt-accent-h) !important;
border-color: var(--bt-accent-h) !important;
box-shadow: 0 2px 12px color-mix(in srgb, var(--bt-accent) 30%, transparent) !important;
transform: translateY(-1px);
color: var(--bt-on-accent) !important;
}
.button_submit:active, input[type="submit"]:active {
background: var(--bt-accent-a) !important;
transform: translateY(0) !important;
}
a.button {
background: transparent !important;
color: var(--bt-text) !important;
border: 1px solid var(--bt-border-2) !important;
}
a.button:hover {
border-color: var(--bt-accent) !important;
color: var(--bt-accent) !important;
background: color-mix(in srgb, var(--bt-accent) 10%, transparent) !important;
box-shadow: none !important;
transform: none !important;
}
a.quotebutton, a.replybutton, a.modifybutton, a.removebutton, a.reportbutton,
a.splitbutton, a.notifybutton, a.markreadbutton, a.unwatchbutton,
a.printbutton, a.archivebutton {
display: inline-block !important;
background: transparent !important;
color: var(--bt-text-2) !important;
border: 1px solid var(--bt-border-2) !important;
border-radius: var(--bt-radius-sm) !important;
padding: 4px 10px !important;
margin: 0 4px 0 0 !important;
font-family: var(--bt-font) !important;
font-weight: 500 !important;
font-size: 11px !important;
text-transform: uppercase !important;
letter-spacing: 0.04em !important;
text-decoration: none !important;
box-shadow: none !important;
text-shadow: none !important;
transition: background var(--bt-trans), color var(--bt-trans), border-color var(--bt-trans) !important;
cursor: pointer !important;
}
a.quotebutton:hover, a.replybutton:hover, a.modifybutton:hover,
a.removebutton:hover, a.reportbutton:hover, a.splitbutton:hover,
a.notifybutton:hover, a.markreadbutton:hover, a.unwatchbutton:hover,
a.printbutton:hover, a.archivebutton:hover {
background: var(--bt-accent) !important;
color: var(--bt-on-accent) !important;
border-color: var(--bt-accent) !important;
transform: none !important;
box-shadow: none !important;
}
.bt-post-action {
display: inline-block !important;
padding: 4px 10px !important;
margin: 0 4px 0 0 !important;
background: transparent !important;
color: var(--bt-text-2) !important;
border: 1px solid var(--bt-border-2) !important;
border-radius: var(--bt-radius-sm) !important;
font-family: var(--bt-font) !important;
font-weight: 500 !important;
font-size: 11px !important;
text-transform: uppercase !important;
letter-spacing: 0.04em !important;
cursor: pointer !important;
vertical-align: middle !important;
transition: background var(--bt-trans), color var(--bt-trans), border-color var(--bt-trans) !important;
}
.bt-post-action:hover {
background: var(--bt-accent) !important;
color: var(--bt-on-accent) !important;
border-color: var(--bt-accent) !important;
text-decoration: none !important;
}
a.bt-post-action, a.bt-post-action:link, a.bt-post-action:visited {
color: var(--bt-text-2) !important;
}
a.bt-post-action:hover { color: var(--bt-on-accent) !important; }
span[title="Quote to clipboard"] { display: none !important; }
input[type="text"], input[type="password"], input[type="email"],
input[type="search"], input[type="url"], input[type="number"],
textarea, select {
background: var(--bt-input-bg) !important;
color: var(--bt-text) !important;
border: 1px solid var(--bt-border-2) !important;
border-radius: var(--bt-radius-sm) !important;
padding: 8px 10px !important;
font-family: var(--bt-font) !important;
font-size: 13px !important;
transition: border-color var(--bt-trans), box-shadow var(--bt-trans) !important;
outline: none !important;
}
input[type="text"]:focus, input[type="password"]:focus,
input[type="email"]:focus, input[type="search"]:focus,
textarea:focus, select:focus {
border-color: var(--bt-accent) !important;
box-shadow: 0 0 0 3px color-mix(in srgb, var(--bt-accent) 20%, transparent) !important;
}
textarea {
min-height: 120px !important;
line-height: 1.5 !important;
font-family: var(--bt-mono) !important;
}
#bbcBox_message, .bbc_buttons, #post_modify, .post_buttons {
background: var(--bt-surface-2) !important;
border: 1px solid var(--bt-border) !important;
border-radius: var(--bt-radius-sm) !important;
padding: 6px !important;
}
#bbcBox_message img, .bbc_buttons img {
filter: var(--bt-icon-filter) !important;
transition: filter var(--bt-trans) !important;
}
#bbcBox_message img:hover, .bbc_buttons img:hover { filter: none !important; }
.bt-bbc-icon {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
width: 26px !important;
height: 26px !important;
margin: 1px !important;
padding: 4px !important;
border-radius: var(--bt-radius-sm) !important;
color: var(--bt-text-2) !important;
background: transparent !important;
cursor: pointer !important;
vertical-align: middle !important;
transition: background var(--bt-trans), color var(--bt-trans) !important;
box-sizing: border-box !important;
}
.bt-bbc-icon svg {
width: 100% !important;
height: 100% !important;
display: block !important;
}
.bt-bbc-icon:hover {
background: color-mix(in srgb, var(--bt-accent) 18%, transparent) !important;
color: var(--bt-accent) !important;
}
.bt-smiley {
display: inline-block !important;
font-size: 1.15em !important;
line-height: 1 !important;
vertical-align: -0.12em !important;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji",
"Twemoji Mozilla", sans-serif !important;
}
[class*="maintab_"],
[class*="mirrortab_"] {
display: inline-block !important;
margin: 0 !important;
padding: 0 !important;
border-radius: 0 !important;
background-color: var(--bt-surface-2) !important;
background-image: none !important;
border: 1px solid var(--bt-border) !important;
border-left: none !important;
box-shadow: none !important;
text-shadow: none !important;
transition: background-color var(--bt-trans), border-color var(--bt-trans) !important;
vertical-align: middle !important;
line-height: 1 !important;
width: auto !important;
min-width: 0 !important;
height: auto !important;
}
.maintab_first,
.mirrortab_first,
.maintab_active_first + .maintab_active_back,
.mirrortab_active_first + .mirrortab_active_back {
border-left: 1px solid var(--bt-border) !important;
border-top-left-radius: var(--bt-radius-sm) !important;
border-bottom-left-radius: var(--bt-radius-sm) !important;
}
.maintab_last,
.mirrortab_last,
.maintab_active_back + .maintab_active_last,
.mirrortab_active_back + .mirrortab_active_last {
border-top-right-radius: var(--bt-radius-sm) !important;
border-bottom-right-radius: var(--bt-radius-sm) !important;
}
[class*="maintab_"] a,
[class*="maintab_"] a:link,
[class*="maintab_"] a:visited,
[class*="mirrortab_"] a,
[class*="mirrortab_"] a:link,
[class*="mirrortab_"] a:visited {
display: inline-block !important;
padding: 6px 14px !important;
margin: 0 !important;
color: var(--bt-text-2) !important;
font-family: var(--bt-font) !important;
font-weight: 500 !important;
font-size: 12px !important;
text-transform: uppercase !important;
letter-spacing: 0.05em !important;
transition: color var(--bt-trans) !important;
background: transparent !important;
background-image: none !important;
border: none !important;
box-shadow: none !important;
text-shadow: none !important;
line-height: 1 !important;
}
.maintab_first:hover, .maintab_back:hover, .maintab_last:hover {
background-color: var(--bt-surface-3) !important;
border-color: var(--bt-border-2) !important;
}
.maintab_first:hover a, .maintab_back:hover a, .maintab_last:hover a {
color: var(--bt-text) !important;
}
.maintab_active_back,
.mirrortab_active_back {
background-color: var(--bt-accent) !important;
border-color: var(--bt-accent) !important;
}
.maintab_active_back a,
.maintab_active_back a:hover,
.mirrortab_active_back a,
.mirrortab_active_back a:hover {
color: var(--bt-on-accent) !important;
font-weight: 600 !important;
}
/* v2.7.15: In LIGHT themes, selected main/mirror tabs use a
theme-derived extrapolated colour: it continues from Main -> surface3
by the same interval again. In other words, if surface3 is the
"intermediate/illuminated" tone, the active tab doubles that HSL move. */
:root[data-bt-theme="light"] .maintab_active_back,
:root[data-bt-theme="light"] .mirrortab_active_back {
background-color: var(--bt-nav-active-bg, var(--bt-surface-3)) !important;
border-color: var(--bt-nav-active-bg, var(--bt-border-2)) !important;
}
:root[data-bt-theme="light"] .maintab_active_back a,
:root[data-bt-theme="light"] .maintab_active_back a:hover,
:root[data-bt-theme="light"] .mirrortab_active_back a,
:root[data-bt-theme="light"] .mirrortab_active_back a:hover {
color: var(--bt-nav-active-text, var(--bt-text)) !important;
font-weight: 600 !important;
}
.maintab_active_first.maintab_active_first,
.maintab_active_last.maintab_active_last,
.mirrortab_active_first.mirrortab_active_first,
.mirrortab_active_last.mirrortab_active_last {
display: none !important;
}
#main_menu, .dropmenu {
background: transparent !important;
border: none !important;
}
#main_menu li, .dropmenu li {
background: var(--bt-surface-2) !important;
border: 1px solid var(--bt-border) !important;
border-radius: var(--bt-radius-sm) !important;
margin-right: 4px !important;
}
#main_menu li.chosen, .dropmenu li.chosen,
#main_menu li.active, .dropmenu li.active {
background: var(--bt-accent) !important;
border-color: var(--bt-accent) !important;
}
#main_menu li a, .dropmenu li a {
color: var(--bt-text-2) !important;
padding: 6px 14px !important;
font-size: 12px !important;
text-transform: uppercase !important;
letter-spacing: 0.05em !important;
}
#main_menu li.chosen a, .dropmenu li.chosen a,
#main_menu li.active a, .dropmenu li.active a {
color: var(--bt-on-accent) !important;
font-weight: 600 !important;
}
#upper_section {
border-bottom: 1px solid var(--bt-border) !important;
position: relative !important;
}
#top_section, #upshrink_header, .navigate_section ul, .navigate_section {
background: transparent !important;
color: var(--bt-text-2) !important;
}
.navigate_section a, .navigate_section a:link, .navigate_section a:visited {
color: var(--bt-text-2) !important;
}
.navigate_section a:hover { color: var(--bt-accent) !important; }
.navigate_section .last a, .navigate_section li:last-child a {
color: var(--bt-text) !important;
font-weight: 600 !important;
}
#header img, #logo { filter: brightness(1.05) !important; }
hr {
border: none !important;
border-top: 1px solid var(--bt-border) !important;
margin: 16px 0 !important;
}
#footer_section, .copywrite {
background: transparent !important;
color: var(--bt-text-3) !important;
border-top: 1px solid var(--bt-border) !important;
}
#footer_section a, .copywrite a { color: var(--bt-text-2) !important; }
#footer_section a:hover, .copywrite a:hover { color: var(--bt-accent) !important; }
.information, .descbox, .infobox {
background: var(--bt-surface-2) !important;
color: var(--bt-text-2) !important;
border: 1px solid var(--bt-border) !important;
border-radius: var(--bt-radius-sm) !important;
padding: 10px 12px !important;
}
.errorbox, .noticebox {
background: color-mix(in srgb, var(--bt-danger) 12%, transparent) !important;
border: 1px solid color-mix(in srgb, var(--bt-danger) 40%, transparent) !important;
color: var(--bt-danger) !important;
border-radius: var(--bt-radius-sm) !important;
padding: 10px 12px !important;
}
.pagelinks { color: var(--bt-text-2) !important; }
.pagelinks a, .pagelinks a:link, .pagelinks a:visited {
color: var(--bt-text-2) !important;
padding: 4px 8px !important;
border-radius: var(--bt-radius-sm) !important;
transition: background var(--bt-trans), color var(--bt-trans) !important;
}
.pagelinks a:hover {
background: color-mix(in srgb, var(--bt-accent) 12%, transparent) !important;
color: var(--bt-accent) !important;
}
.pagelinks strong {
background: var(--bt-accent) !important;
color: var(--bt-on-accent) !important;
padding: 4px 8px !important;
border-radius: var(--bt-radius-sm) !important;
}
.poster h4 a, .poster h4 a:link, .poster h4 a:visited {
color: var(--bt-text) !important;
font-weight: 600 !important;
}
.poster h4 a:hover { color: var(--bt-accent) !important; }
.poster ul, .poster li {
color: var(--bt-text-3) !important;
font-size: 12px !important;
}
.avatar, img.avatar {
border-radius: var(--bt-radius-sm) !important;
border: 1px solid var(--bt-border) !important;
}
* { scrollbar-width: thin; scrollbar-color: var(--bt-surface-3) var(--bt-bg); }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: var(--bt-bg); }
*::-webkit-scrollbar-thumb {
background: var(--bt-surface-3);
border-radius: 5px;
border: 2px solid var(--bt-bg);
}
*::-webkit-scrollbar-thumb:hover { background: var(--bt-accent); }
::selection {
background: color-mix(in srgb, var(--bt-accent) 35%, transparent);
color: var(--bt-text);
}
img[src*="bullet"], img[src*="off"], img[src*="on"] { opacity: 0.85; }
select {
appearance: none !important;
-webkit-appearance: none !important;
background-image:
linear-gradient(45deg, transparent 50%, var(--bt-text-2) 50%),
linear-gradient(135deg, var(--bt-text-2) 50%, transparent 50%) !important;
background-position: calc(100% - 14px) 50%, calc(100% - 9px) 50% !important;
background-size: 5px 5px, 5px 5px !important;
background-repeat: no-repeat !important;
padding-right: 28px !important;
}
/*-- BT_ALWAYS_ON_START --*/
#bt-switcher {
position: absolute !important;
top: 8px !important;
right: 8px !important;
z-index: 10000 !important;
font-family: var(--bt-font) !important;
font-size: 13px !important;
color: var(--bt-text, #1A1D21) !important;
line-height: 1 !important;
}
#bt-switcher-button {
display: flex !important;
align-items: center !important;
justify-content: center !important;
width: 32px !important;
height: 32px !important;
padding: 0 !important;
margin: 0 !important;
border-radius: 50% !important;
background: var(--bt-surface-2, #2A2F35) !important;
color: var(--bt-text-2, #A8B0BA) !important;
border: 1px solid var(--bt-border-2, rgba(255,255,255,0.14)) !important;
cursor: pointer !important;
transition: background var(--bt-trans), color var(--bt-trans), transform 200ms ease !important;
box-shadow: var(--bt-shadow, 0 1px 0 rgba(0,0,0,0.2)) !important;
text-shadow: none !important;
font: inherit !important;
text-transform: none !important;
letter-spacing: 0 !important;
}
#bt-switcher-button:hover {
background: var(--bt-surface-3, #343A41) !important;
color: var(--bt-text, #E3E3E8) !important;
transform: rotate(20deg);
}
#bt-switcher-button svg { width: 18px; height: 18px; }
:root[data-bt-active="false"] #bt-switcher-button {
width: auto !important;
min-width: 44px !important;
padding: 0 12px !important;
border-radius: 14px !important;
background: #2A2F35 !important;
color: #FFFFFF !important;
border: 1px solid #4ADE80 !important;
font-weight: 700 !important;
font-size: 11px !important;
text-transform: uppercase !important;
letter-spacing: 0.08em !important;
box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.4) !important;
animation: bt-pulse-on 2.4s ease-in-out infinite;
}
:root[data-bt-active="false"] #bt-switcher-button:hover {
background: #4ADE80 !important;
color: #1A1D21 !important;
transform: none !important;
animation: none;
}
.bt-sw-on-text { display: inline-block; line-height: 1; }
@keyframes bt-pulse-on {
0%, 100% { box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.0); }
50% { box-shadow: 0 0 0 4px rgba(74, 222, 128, 0.18); }
}
#bt-switcher-panel {
position: absolute !important;
top: 40px !important;
right: 0 !important;
min-width: 300px !important;
background: var(--bt-surface-1, #22262B) !important;
border: 1px solid var(--bt-border-2, rgba(255,255,255,0.14)) !important;
border-radius: var(--bt-radius, 10px) !important;
padding: 14px !important;
box-shadow: var(--bt-shadow-lg, 0 12px 32px rgba(0,0,0,0.35)) !important;
display: none !important;
color: var(--bt-text, #E3E3E8) !important;
}
#bt-switcher.open #bt-switcher-panel { display: block !important; }
.bt-sw-section { margin-bottom: 14px; }
.bt-sw-section:last-child { margin-bottom: 0; }
.bt-sw-label {
display: block !important;
font-size: 10px !important;
font-weight: 600 !important;
text-transform: uppercase !important;
letter-spacing: 0.08em !important;
color: var(--bt-text-3, #6B7480) !important;
margin-bottom: 8px !important;
}
.bt-sw-row {
display: flex !important;
gap: 6px !important;
align-items: center !important;
flex-wrap: wrap !important;
}
.bt-sw-theme-btn {
flex: 1 !important;
min-width: 0 !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
gap: 5px !important;
padding: 8px 6px !important;
margin: 0 !important;
background: var(--bt-surface-2, #2A2F35) !important;
color: var(--bt-text-2, #A8B0BA) !important;
border: 1px solid var(--bt-border, rgba(255,255,255,0.07)) !important;
border-radius: var(--bt-radius-sm, 6px) !important;
cursor: pointer !important;
font-family: var(--bt-font) !important;
font-size: 11px !important;
font-weight: 500 !important;
text-transform: none !important;
letter-spacing: 0 !important;
text-shadow: none !important;
box-shadow: none !important;
transition: background var(--bt-trans), color var(--bt-trans), border-color var(--bt-trans) !important;
line-height: 1 !important;
}
.bt-sw-theme-btn:hover {
background: var(--bt-surface-3, #343A41) !important;
color: var(--bt-text, #E3E3E8) !important;
transform: none !important;
}
.bt-sw-theme-btn.selected {
background: var(--bt-accent, #F7931A) !important;
color: var(--bt-on-accent, #1A1D21) !important;
border-color: var(--bt-accent, #F7931A) !important;
}
.bt-sw-theme-btn svg { width: 14px; height: 14px; }
.bt-sw-theme-btn[data-theme="off"].selected {
background: var(--bt-danger, #EF4444) !important;
color: #FFFFFF !important;
border-color: var(--bt-danger, #EF4444) !important;
}
.bt-sw-customize {
margin-top: 14px;
padding-top: 14px;
border-top: 1px solid var(--bt-border, rgba(255,255,255,0.07));
}
.bt-sw-customize-title {
display: block;
text-align: center;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.05em;
color: var(--bt-text, #E3E3E8);
margin-bottom: 12px;
}
.bt-sw-swatch {
width: 26px !important;
height: 26px !important;
padding: 0 !important;
margin: 0 !important;
border-radius: 50% !important;
border: 2px solid transparent !important;
cursor: pointer !important;
transition: transform var(--bt-trans), border-color var(--bt-trans) !important;
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15) !important;
font-size: 0 !important;
text-shadow: none !important;
}
.bt-sw-swatch:hover { transform: scale(1.12) !important; }
.bt-sw-swatch.selected { border-color: var(--bt-text, #E3E3E8) !important; }
.bt-sw-auto {
width: auto !important;
min-width: 44px !important;
padding: 0 8px !important;
height: 26px !important;
border-radius: 13px !important;
background: conic-gradient(#ef4444, #f59e0b, #10b981, #3b82f6, #8b5cf6, #ef4444) !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
color: #FFFFFF !important;
font-weight: 700 !important;
font-size: 9px !important;
letter-spacing: 0.06em !important;
line-height: 1 !important;
text-shadow: 0 1px 2px rgba(0,0,0,0.6) !important;
font-family: var(--bt-font) !important;
}
.bt-sw-auto::before { content: 'AUTO'; }
.bt-sw-custom-wrap {
position: relative;
display: inline-block;
width: 26px;
height: 26px;
}
.bt-sw-custom-wrap input[type="color"] {
position: absolute !important;
inset: 0 !important;
width: 100% !important;
height: 100% !important;
padding: 0 !important;
margin: 0 !important;
border: 0 !important;
border-radius: 50% !important;
background: transparent !important;
cursor: pointer !important;
opacity: 0 !important;
box-shadow: none !important;
}
.bt-sw-custom-display {
width: 26px;
height: 26px;
border-radius: 50%;
border: 2px solid transparent;
pointer-events: none;
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15);
transition: border-color var(--bt-trans);
background: conic-gradient(#ef4444, #f59e0b, #10b981, #3b82f6, #8b5cf6, #ef4444);
display: flex;
align-items: center;
justify-content: center;
color: #FFFFFF;
font-weight: 700;
font-size: 14px;
line-height: 1;
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
}
.bt-sw-custom-display::before { content: '+'; }
.bt-sw-custom-wrap.selected .bt-sw-custom-display::before { content: ''; }
.bt-sw-custom-wrap.selected .bt-sw-custom-display {
border-color: var(--bt-text, #E3E3E8);
}
/*-- BT_ALWAYS_ON_END --*/
/* ============================================================
v2.8.3 — LIGHT THREAD POST ALTERNATION, FINAL OVERRIDE
Theme-aware version. The post colours come from the active light
palette, not from Classic hardcoded blue:
--bt-post-bg
--bt-post-alt-bg
--bt-poster-bg
--bt-poster-alt-bg
So Classic, Pearl, Cream, Mint and Rose each get their own matching
alternation. This block deliberately sits at the END of the theme CSS
so it wins over broad rules like .post, .poster_info and td[style].
============================================================ */
:root[data-bt-theme="light"] tr.windowbg,
:root[data-bt-theme="light"] td.windowbg,
:root[data-bt-theme="light"] tr.windowbg > td,
:root[data-bt-theme="light"] table.bordercolor tr.windowbg > td,
:root[data-bt-theme="light"] table.bordercolor td.windowbg,
:root[data-bt-theme="light"] .tborder tr.windowbg > td,
:root[data-bt-theme="light"] .tborder td.windowbg {
background-color: var(--bt-post-bg, var(--bt-surface-1)) !important;
background-image: none !important;
}
:root[data-bt-theme="light"] tr.windowbg2,
:root[data-bt-theme="light"] td.windowbg2,
:root[data-bt-theme="light"] tr.windowbg2 > td,
:root[data-bt-theme="light"] table.bordercolor tr.windowbg2 > td,
:root[data-bt-theme="light"] table.bordercolor td.windowbg2,
:root[data-bt-theme="light"] .tborder tr.windowbg2 > td,
:root[data-bt-theme="light"] .tborder td.windowbg2 {
background-color: var(--bt-post-alt-bg, var(--bt-surface-2)) !important;
background-image: none !important;
}
:root[data-bt-theme="light"] tr.windowbg3,
:root[data-bt-theme="light"] td.windowbg3,
:root[data-bt-theme="light"] tr.windowbg3 > td,
:root[data-bt-theme="light"] table.bordercolor tr.windowbg3 > td,
:root[data-bt-theme="light"] table.bordercolor td.windowbg3,
:root[data-bt-theme="light"] .tborder tr.windowbg3 > td,
:root[data-bt-theme="light"] .tborder td.windowbg3 {
background-color: var(--bt-post-bg, var(--bt-surface-1)) !important;
background-image: none !important;
}
/* Visible post body/wrapper. */
:root[data-bt-theme="light"] tr.windowbg .td_headerandpost,
:root[data-bt-theme="light"] td.windowbg.td_headerandpost,
:root[data-bt-theme="light"] td.windowbg .td_headerandpost,
:root[data-bt-theme="light"] tr.windowbg .post,
:root[data-bt-theme="light"] td.windowbg .post,
:root[data-bt-theme="light"] tr.windowbg td.post,
:root[data-bt-theme="light"] td.windowbg.post,
:root[data-bt-theme="light"] tr.windowbg td[style],
:root[data-bt-theme="light"] td.windowbg td[style] {
background-color: var(--bt-post-bg, var(--bt-surface-1)) !important;
background-image: none !important;
}
:root[data-bt-theme="light"] tr.windowbg2 .td_headerandpost,
:root[data-bt-theme="light"] td.windowbg2.td_headerandpost,
:root[data-bt-theme="light"] td.windowbg2 .td_headerandpost,
:root[data-bt-theme="light"] tr.windowbg2 .post,
:root[data-bt-theme="light"] td.windowbg2 .post,
:root[data-bt-theme="light"] tr.windowbg2 td.post,
:root[data-bt-theme="light"] td.windowbg2.post,
:root[data-bt-theme="light"] tr.windowbg2 td[style],
:root[data-bt-theme="light"] td.windowbg2 td[style] {
background-color: var(--bt-post-alt-bg, var(--bt-surface-2)) !important;
background-image: none !important;
}
:root[data-bt-theme="light"] tr.windowbg3 .td_headerandpost,
:root[data-bt-theme="light"] td.windowbg3.td_headerandpost,
:root[data-bt-theme="light"] td.windowbg3 .td_headerandpost,
:root[data-bt-theme="light"] tr.windowbg3 .post,
:root[data-bt-theme="light"] td.windowbg3 .post,
:root[data-bt-theme="light"] tr.windowbg3 td.post,
:root[data-bt-theme="light"] td.windowbg3.post,
:root[data-bt-theme="light"] tr.windowbg3 td[style],
:root[data-bt-theme="light"] td.windowbg3 td[style] {
background-color: var(--bt-post-bg, var(--bt-surface-1)) !important;
background-image: none !important;
}
/* Poster/sidebar column: same theme family, slightly deeper for separation. */
:root[data-bt-theme="light"] tr.windowbg .poster,
:root[data-bt-theme="light"] tr.windowbg .poster_info,
:root[data-bt-theme="light"] td.windowbg.poster,
:root[data-bt-theme="light"] td.windowbg.poster_info {
background-color: var(--bt-poster-bg, var(--bt-post-bg)) !important;
background-image: none !important;
}
:root[data-bt-theme="light"] tr.windowbg2 .poster,
:root[data-bt-theme="light"] tr.windowbg2 .poster_info,
:root[data-bt-theme="light"] td.windowbg2.poster,
:root[data-bt-theme="light"] td.windowbg2.poster_info {
background-color: var(--bt-poster-alt-bg, var(--bt-post-alt-bg)) !important;
background-image: none !important;
}
:root[data-bt-theme="light"] tr.windowbg3 .poster,
:root[data-bt-theme="light"] tr.windowbg3 .poster_info,
:root[data-bt-theme="light"] td.windowbg3.poster,
:root[data-bt-theme="light"] td.windowbg3.poster_info {
background-color: var(--bt-poster-bg, var(--bt-post-bg)) !important;
background-image: none !important;
}
/* Keep quote/code/input blocks distinct inside alternating posts. */
:root[data-bt-theme="light"] tr.windowbg .quote,
:root[data-bt-theme="light"] tr.windowbg2 .quote,
:root[data-bt-theme="light"] tr.windowbg3 .quote,
:root[data-bt-theme="light"] td.windowbg .quote,
:root[data-bt-theme="light"] td.windowbg2 .quote,
:root[data-bt-theme="light"] td.windowbg3 .quote {
background-color: var(--bt-quote-bg) !important;
border-left-color: var(--bt-accent) !important;
}
:root[data-bt-theme="light"] tr.windowbg .code,
:root[data-bt-theme="light"] tr.windowbg2 .code,
:root[data-bt-theme="light"] tr.windowbg3 .code,
:root[data-bt-theme="light"] td.windowbg .code,
:root[data-bt-theme="light"] td.windowbg2 .code,
:root[data-bt-theme="light"] td.windowbg3 .code {
background-color: var(--bt-code-bg) !important;
}
:root[data-bt-theme="light"] tr.windowbg input,
:root[data-bt-theme="light"] tr.windowbg2 input,
:root[data-bt-theme="light"] tr.windowbg3 input,
:root[data-bt-theme="light"] tr.windowbg textarea,
:root[data-bt-theme="light"] tr.windowbg2 textarea,
:root[data-bt-theme="light"] tr.windowbg3 textarea,
:root[data-bt-theme="light"] tr.windowbg select,
:root[data-bt-theme="light"] tr.windowbg2 select,
:root[data-bt-theme="light"] tr.windowbg3 select {
background-color: var(--bt-input-bg) !important;
}
`;
// ===========================================================================
// CSS injection
// ===========================================================================
function splitCss(src) {
const startMarker = '/*-- BT_ALWAYS_ON_START --*/';
const endMarker = '/*-- BT_ALWAYS_ON_END --*/';
let theme = '';
let always = '';
let i = 0;
while (i < src.length) {
const startIdx = src.indexOf(startMarker, i);
if (startIdx === -1) {
theme += src.slice(i);
break;
}
theme += src.slice(i, startIdx);
const after = startIdx + startMarker.length;
const endIdx = src.indexOf(endMarker, after);
if (endIdx === -1) {
always += src.slice(after);
break;
}
always += src.slice(after, endIdx);
i = endIdx + endMarker.length;
}
return { theme, always };
}
let themeStyleEl = null;
let alwaysStyleEl = null;
function injectCSS() {
const { theme, always } = splitCss(css);
themeStyleEl = document.createElement('style');
themeStyleEl.id = 'bt-theme-css';
themeStyleEl.textContent = theme;
(document.head || document.documentElement).appendChild(themeStyleEl);
alwaysStyleEl = document.createElement('style');
alwaysStyleEl.id = 'bt-always-css';
alwaysStyleEl.textContent = always;
(document.head || document.documentElement).appendChild(alwaysStyleEl);
if (!document.getElementById('bt-fonts')) {
const link = document.createElement('link');
link.id = 'bt-fonts';
link.rel = 'stylesheet';
link.href =
'https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;500;600;700' +
'&family=JetBrains+Mono:wght@400;500&display=swap';
(document.head || document.documentElement).appendChild(link);
}
}
// ===========================================================================
// Switcher UI
// ===========================================================================
const ICONS = {
cog: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>',
sun: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>',
moon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>',
monitor: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>',
power: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"/><line x1="12" y1="2" x2="12" y2="12"/></svg>',
};
function buildSwitcher(host) {
if (document.getElementById('bt-switcher')) return;
const container = document.createElement('div');
container.id = 'bt-switcher';
container.innerHTML = `
<button id="bt-switcher-button" type="button" title="Theme settings" aria-label="Theme settings">
${ICONS.cog}
</button>
<div id="bt-switcher-panel" role="dialog" aria-label="Theme settings">
<div class="bt-sw-section">
<span class="bt-sw-label">Mode</span>
<div class="bt-sw-row">
<button type="button" class="bt-sw-theme-btn" data-theme="light" title="Light">
${ICONS.sun}<span class="bt-sw-theme-label">Light</span>
</button>
<button type="button" class="bt-sw-theme-btn" data-theme="dark" title="Dark">
${ICONS.moon}<span class="bt-sw-theme-label">Dark</span>
</button>
<button type="button" class="bt-sw-theme-btn" data-theme="system" title="Follow system">
${ICONS.monitor}<span class="bt-sw-theme-label">Auto</span>
</button>
<button type="button" class="bt-sw-theme-btn" data-theme="off" title="Turn the theme off">
${ICONS.power}<span class="bt-sw-theme-label">OFF</span>
</button>
</div>
</div>
<div class="bt-sw-customize">
<span class="bt-sw-customize-title" id="bt-sw-customize-title">Customise Colours</span>
<div class="bt-sw-section">
<span class="bt-sw-label">Main</span>
<div class="bt-sw-row" id="bt-sw-main-row"></div>
</div>
<div class="bt-sw-section">
<span class="bt-sw-label">Accent</span>
<div class="bt-sw-row" id="bt-sw-accent-row"></div>
</div>
</div>
</div>
`;
host.appendChild(container);
const btn = container.querySelector('#bt-switcher-button');
btn.addEventListener('click', (e) => {
e.stopPropagation();
const currentPref = getPref(KEY_THEME, DEFAULT_THEME);
if (currentPref === 'off') {
const prior = getPref(KEY_THEME_PRIOR, 'dark');
setPref(KEY_THEME, prior);
applyTheme();
return;
}
container.classList.toggle('open');
});
document.addEventListener('click', (e) => {
if (!container.contains(e.target)) container.classList.remove('open');
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') container.classList.remove('open');
});
container.querySelectorAll('.bt-sw-theme-btn').forEach(b => {
b.addEventListener('click', () => {
const choice = b.dataset.theme;
if (choice === 'off') {
const current = getPref(KEY_THEME, DEFAULT_THEME);
if (current === 'off') {
const prior = getPref(KEY_THEME_PRIOR, 'dark');
setPref(KEY_THEME, prior);
} else {
setPref(KEY_THEME_PRIOR, current);
setPref(KEY_THEME, 'off');
}
container.classList.remove('open');
} else {
setPref(KEY_THEME, choice);
}
applyTheme();
});
});
function handleSwatchClick(kind) {
return (e) => {
const swatch = e.target.closest(`[data-${kind}]`);
if (!swatch || swatch.tagName === 'INPUT') return;
const themeNow = getEffectiveTheme(getPref(KEY_THEME, DEFAULT_THEME));
if (themeNow === 'off') return;
const storageKey = kind === 'main'
? (themeNow === 'dark' ? KEY_MAIN_DARK : KEY_MAIN_LIGHT)
: (themeNow === 'dark' ? KEY_ACCENT_DARK : KEY_ACCENT_LIGHT);
setPref(storageKey, swatch.dataset[kind]);
applyTheme();
};
}
container.querySelector('#bt-sw-main-row').addEventListener('click', handleSwatchClick('main'));
container.querySelector('#bt-sw-accent-row').addEventListener('click', handleSwatchClick('accent'));
function handleColorInput(kind) {
return (e) => {
if (!e.target.matches(`input[type="color"][data-target="${kind}"]`)) return;
const themeNow = getEffectiveTheme(getPref(KEY_THEME, DEFAULT_THEME));
if (themeNow === 'off') return;
const storageKey = kind === 'main'
? (themeNow === 'dark' ? KEY_MAIN_DARK : KEY_MAIN_LIGHT)
: (themeNow === 'dark' ? KEY_ACCENT_DARK : KEY_ACCENT_LIGHT);
const customStorageKey = kind === 'main'
? (themeNow === 'dark' ? KEY_MAIN_DARK_CUSTOM : KEY_MAIN_LIGHT_CUSTOM)
: (themeNow === 'dark' ? KEY_ACCENT_DARK_CUSTOM : KEY_ACCENT_LIGHT_CUSTOM);
setPref(customStorageKey, e.target.value);
setPref(storageKey, 'custom');
applyTheme();
};
}
container.querySelector('#bt-sw-main-row').addEventListener('input', handleColorInput('main'));
container.querySelector('#bt-sw-accent-row').addEventListener('input', handleColorInput('accent'));
applyTheme();
}
function injectSwitcher() {
const host =
document.getElementById('upper_section') ||
document.getElementById('header') ||
document.body;
if (host) buildSwitcher(host);
}
// ===========================================================================
// Integrated Safari flash guard
// ===========================================================================
function getHoldBackground() {
const fromSession = ssGet(NAV_BG_SESSION_KEY);
if (fromSession) return fromSession;
const pref = getPref(KEY_THEME, DEFAULT_THEME);
const effective = getEffectiveTheme(pref);
if (effective === 'off') return null;
return getActiveMainHex(effective) || DEFAULT_CUSTOM_MAIN_DARK;
}
function getCurrentThemeBackground() {
const root = document.documentElement;
try {
const inlineVar = root && root.style ? root.style.getPropertyValue('--bt-bg').trim() : '';
if (inlineVar) return inlineVar;
} catch (e) {}
try {
const computedVar = getComputedStyle(root).getPropertyValue('--bt-bg').trim();
if (computedVar) return computedVar;
} catch (e) {}
return getHoldBackground() || DEFAULT_CUSTOM_MAIN_DARK;
}
function holdCss(bg, revealedSelector) {
const scheme = getEffectiveTheme(getPref(KEY_THEME, DEFAULT_THEME)) === 'light' ? 'light' : 'dark';
return `
html${revealedSelector} {
background: ${bg} !important;
color-scheme: ${scheme} !important;
}
html${revealedSelector}::before {
content: '' !important;
position: fixed !important;
inset: 0 !important;
width: 100vw !important;
height: 100vh !important;
background: ${bg} !important;
z-index: 2147483647 !important;
pointer-events: none !important;
}
html${revealedSelector} body {
visibility: hidden !important;
opacity: 0 !important;
background: ${bg} !important;
}
`;
}
function injectTransientStyle(id, cssText) {
let style = document.getElementById(id);
if (!style) {
style = document.createElement('style');
style.id = id;
(document.documentElement || document.head || document.body).appendChild(style);
}
style.textContent = cssText;
return style;
}
function removeTransientStyle(id) {
const style = document.getElementById(id);
if (style) style.remove();
}
function holdPaint() {
if (document.readyState === 'complete') return false;
const bg = getHoldBackground();
if (!bg) return false;
const root = document.documentElement;
const holdStyle = injectTransientStyle(
INCOMING_HOLD_STYLE_ID,
holdCss(bg, `:not([${HOLD_REVEALED_ATTR}])`)
);
if (typeof MutationObserver !== 'undefined') {
const observer = new MutationObserver(() => {
if (!document.getElementById(INCOMING_HOLD_STYLE_ID)) {
try { document.documentElement.appendChild(holdStyle); } catch (e) {}
}
});
try {
observer.observe(root, { childList: true });
holdStyle._btObserver = observer;
} catch (e) {}
}
setTimeout(() => revealNow(), 1500);
return true;
}
function revealNow() {
document.documentElement.setAttribute(HOLD_REVEALED_ATTR, '');
const holdStyle = document.getElementById(INCOMING_HOLD_STYLE_ID);
if (holdStyle && holdStyle._btObserver) {
try { holdStyle._btObserver.disconnect(); } catch (e) {}
}
ssRemove(NAV_UNTIL_SESSION_KEY);
}
function releasePaint() {
revealNow();
}
function markNavigationHold(bg) {
ssSet(NAV_UNTIL_SESSION_KEY, String(Date.now() + NAV_HOLD_SESSION_MS));
ssSet(NAV_BG_SESSION_KEY, bg);
}
function applyOutgoingHold() {
if (getPref(KEY_THEME, DEFAULT_THEME) === 'off') return;
const bg = getCurrentThemeBackground();
markNavigationHold(bg);
injectTransientStyle(OUTGOING_HOLD_STYLE_ID, holdCss(bg, ''));
setTimeout(() => removeTransientStyle(OUTGOING_HOLD_STYLE_ID), OUTGOING_CANCEL_RESTORE_MS);
}
function isPlainPrimaryActivation(e) {
if (e.defaultPrevented) return false;
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return false;
if (typeof e.button === 'number' && e.button !== 0) return false;
return true;
}
function shouldHoldForLink(a, e) {
if (!a || !isPlainPrimaryActivation(e)) return false;
if (a.target && a.target !== '_self') return false;
if (a.hasAttribute('download')) return false;
const href = a.getAttribute('href') || '';
if (!href) return false;
if (href.startsWith('#')) return false;
if (/^\s*javascript:/i.test(href)) return false;
let url;
try {
url = new URL(href, window.location.href);
} catch (e2) {
return false;
}
if (
url.origin === window.location.origin &&
url.pathname === window.location.pathname &&
url.search === window.location.search &&
url.hash
) {
return false;
}
return true;
}
function targetLinkFromEvent(e) {
const target = e.target;
return target && target.closest ? target.closest(NAV_SELECTOR) : null;
}
function installPreNavigationHold() {
['pointerdown', 'mousedown', 'touchstart', 'click'].forEach((type) => {
document.addEventListener(type, (e) => {
const a = targetLinkFromEvent(e);
if (!shouldHoldForLink(a, e)) return;
applyOutgoingHold();
}, true);
});
document.addEventListener('keydown', (e) => {
if (e.key !== 'Enter' && e.key !== ' ') return;
const active = document.activeElement;
const a = active && active.closest ? active.closest(NAV_SELECTOR) : null;
if (!shouldHoldForLink(a, e)) return;
applyOutgoingHold();
}, true);
window.addEventListener('beforeunload', () => {
if (getPref(KEY_THEME, DEFAULT_THEME) !== 'off') applyOutgoingHold();
});
window.addEventListener('pagehide', () => {
if (getPref(KEY_THEME, DEFAULT_THEME) !== 'off') applyOutgoingHold();
});
window.addEventListener('pageshow', () => {
removeTransientStyle(OUTGOING_HOLD_STYLE_ID);
document.documentElement.setAttribute(HOLD_REVEALED_ATTR, '');
});
}
// ===========================================================================
// Init
// ===========================================================================
function setupRootAttributes() {
let pref = null;
if (hasGM) {
try {
const v = GM_getValue(KEY_THEME, null);
if (v != null && v !== '') pref = String(v);
} catch (e) {}
}
if (!pref) {
try {
const v = localStorage.getItem(KEY_THEME);
if (v != null && v !== '') pref = v;
} catch (e) {}
}
if (!pref) pref = DEFAULT_THEME;
const effective = getEffectiveTheme(pref);
const root = document.documentElement;
root.setAttribute('data-bt-no-transitions', '');
if (effective === 'off') {
root.setAttribute('data-bt-active', 'false');
return;
}
root.setAttribute('data-bt-active', 'true');
root.setAttribute('data-bt-theme', effective);
root.style.colorScheme = effective;
}
const paintHeld = false; // disabled: separate early guard handles paint
setupRootAttributes();
syncStores();
injectCSS();
applyTheme();
if (paintHeld) releasePaint();
// // // // installPreNavigationHold(); // disabled: separate 000 early guard handles Safari navigation flash // disabled: separate early guard handles Safari navigation flash // disabled: readable-bootstrap guard handles Safari navigation flash // disabled: bootstrap guard handles Safari navigation flash
function enableTransitions() {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
document.documentElement.removeAttribute('data-bt-no-transitions');
});
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', enableTransitions, { once: true });
} else {
enableTransitions();
}
window.addEventListener('storage', (e) => {
if (e && e.key && ALL_KEYS.indexOf(e.key) !== -1) {
applyTheme();
}
});
let modernizeTimer = null;
function scheduleModernize() {
if (modernizeTimer) return;
modernizeTimer = setTimeout(() => {
modernizeTimer = null;
modernizeBBCodeIcons();
modernizeSmileys();
stylePostMetaButtons();
fixMaintabCaps();
}, 50);
}
function runInitialPasses() {
fixMaintabCaps();
modernizeBBCodeIcons();
modernizeSmileys();
stylePostMetaButtons();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
applyTheme();
injectSwitcher();
runInitialPasses();
}, { once: true });
} else {
injectSwitcher();
runInitialPasses();
}
if (typeof MutationObserver !== 'undefined') {
const obs = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.addedNodes && m.addedNodes.length > 0) {
scheduleModernize();
return;
}
}
});
if (document.body) {
obs.observe(document.body, { childList: true, subtree: true });
} else {
document.addEventListener('DOMContentLoaded', () => {
obs.observe(document.body, { childList: true, subtree: true });
}, { once: true });
}
}
if (window.matchMedia) {
const mq = window.matchMedia('(prefers-color-scheme: dark)');
const reapply = () => {
if (getPref(KEY_THEME, DEFAULT_THEME) === 'system') applyTheme();
};
if (mq.addEventListener) mq.addEventListener('change', reapply);
else if (mq.addListener) mq.addListener(reapply);
}
})();