forked from winnie/MinePanel
first commit
This commit is contained in:
@@ -0,0 +1,583 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>MinePanel Dashboard - Themes</title>
|
||||
<link rel="stylesheet" href="/panel.css">
|
||||
<script src="/theme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-brand">MinePanel</div>
|
||||
<div id="me" class="sidebar-meta"></div>
|
||||
<nav class="side-nav">
|
||||
<a class="side-link" href="/dashboard/overview">Overview</a>
|
||||
<button class="side-category-toggle" data-category="server" type="button">Server</button>
|
||||
<div class="side-category-items" data-category-items="server">
|
||||
<a class="side-link" href="/console">Console</a>
|
||||
<a class="side-link" href="/dashboard/resources">Resources</a>
|
||||
<a class="side-link" href="/dashboard/players">Players</a>
|
||||
<a class="side-link" href="/dashboard/bans">Bans</a>
|
||||
<a class="side-link" href="/dashboard/plugins">Plugins</a>
|
||||
</div>
|
||||
<button class="side-category-toggle" data-category="panel" type="button">Panel</button>
|
||||
<div class="side-category-items" data-category-items="panel">
|
||||
<a class="side-link" href="/dashboard/users">Users</a>
|
||||
<a class="side-link" href="/dashboard/discord-webhook">Discord Webhook</a>
|
||||
<a class="side-link active" href="/dashboard/themes">Themes</a>
|
||||
</div>
|
||||
</nav>
|
||||
<button id="logout" class="secondary sidebar-logout">Logout</button>
|
||||
</aside>
|
||||
|
||||
<main class="content-area">
|
||||
<h1>Themes</h1>
|
||||
<p>Pick a preset or customize panel colors.</p>
|
||||
|
||||
<section class="card spaced-top">
|
||||
<h2>Presets</h2>
|
||||
<div class="theme-preset-row">
|
||||
<select id="themePreset">
|
||||
<option value="default">Default (MinePanel)</option>
|
||||
<option value="midnight">Midnight Blue</option>
|
||||
<option value="forest">Forest Dark</option>
|
||||
<option value="ember">Ember Dark</option>
|
||||
</select>
|
||||
<button id="applyPreset" type="button">Apply Preset</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card spaced-top">
|
||||
<h2>Custom Colors</h2>
|
||||
<div id="themeGrid" class="theme-grid"></div>
|
||||
<div class="theme-actions">
|
||||
<button id="saveTheme" type="button">Save Custom Theme</button>
|
||||
<button id="resetTheme" class="secondary" type="button">Reset to Default</button>
|
||||
</div>
|
||||
<p id="themeStatus" class="action-status"></p>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function initSidebarCategories() {
|
||||
const storageKey = 'minepanel.sidebar.categories';
|
||||
let savedState = {};
|
||||
try {
|
||||
savedState = JSON.parse(sessionStorage.getItem(storageKey) || '{}') || {};
|
||||
} catch (ignored) {
|
||||
savedState = {};
|
||||
}
|
||||
|
||||
function persistState() {
|
||||
try {
|
||||
sessionStorage.setItem(storageKey, JSON.stringify(savedState));
|
||||
} catch (ignored) {
|
||||
// Ignore unavailable sessionStorage.
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll('.side-category-toggle').forEach(toggle => {
|
||||
const category = toggle.dataset.category;
|
||||
const items = document.querySelector(`.side-category-items[data-category-items="${category}"]`);
|
||||
if (!items || !category) return;
|
||||
|
||||
const expanded = savedState[category] === true;
|
||||
items.classList.toggle('expanded', expanded);
|
||||
toggle.classList.toggle('expanded', expanded);
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
const expanded = !items.classList.contains('expanded');
|
||||
items.classList.toggle('expanded', expanded);
|
||||
toggle.classList.toggle('expanded', expanded);
|
||||
savedState[category] = expanded;
|
||||
persistState();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const STORAGE_KEY = window.MinePanelTheme ? window.MinePanelTheme.storageKey : 'minepanel.customTheme';
|
||||
const THEME_KEYS = [
|
||||
'--bg',
|
||||
'--surface',
|
||||
'--surface-2',
|
||||
'--border',
|
||||
'--text',
|
||||
'--text-muted',
|
||||
'--accent',
|
||||
'--accent-strong',
|
||||
'--danger-bg',
|
||||
'--danger-text',
|
||||
'--log-bg',
|
||||
'--sidebar-bg',
|
||||
'--sidebar-border',
|
||||
'--sidebar-meta-bg',
|
||||
'--sidebar-meta-border',
|
||||
'--sidebar-toggle-text',
|
||||
'--sidebar-toggle-icon',
|
||||
'--sidebar-link-text',
|
||||
'--sidebar-link-bg',
|
||||
'--sidebar-link-border',
|
||||
'--sidebar-link-hover-bg',
|
||||
'--button-bg',
|
||||
'--button-border',
|
||||
'--button-hover-bg',
|
||||
'--button-secondary-bg',
|
||||
'--button-secondary-border',
|
||||
'--input-bg',
|
||||
'--focus-ring',
|
||||
'--table-bg',
|
||||
'--table-head-bg',
|
||||
'--table-row-border',
|
||||
'--table-row-hover-bg',
|
||||
'--table-head-text',
|
||||
'--table-cell-text',
|
||||
'--log-border',
|
||||
'--log-text',
|
||||
'--chart-bg',
|
||||
'--chart-border',
|
||||
'--chart-grid',
|
||||
'--chart-label',
|
||||
'--player-box-bg',
|
||||
'--player-box-border',
|
||||
'--player-box-hover-bg',
|
||||
'--player-box-active-bg',
|
||||
'--player-box-active-border',
|
||||
'--player-muted-bg',
|
||||
'--player-muted-border'
|
||||
];
|
||||
|
||||
const THEME_GROUPS = [
|
||||
{
|
||||
title: 'Core',
|
||||
keys: [
|
||||
'--bg',
|
||||
'--surface',
|
||||
'--surface-2',
|
||||
'--border',
|
||||
'--text',
|
||||
'--text-muted',
|
||||
'--accent',
|
||||
'--accent-strong',
|
||||
'--danger-bg',
|
||||
'--danger-text'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Sidebar',
|
||||
keys: [
|
||||
'--sidebar-bg',
|
||||
'--sidebar-border',
|
||||
'--sidebar-meta-bg',
|
||||
'--sidebar-meta-border',
|
||||
'--sidebar-toggle-text',
|
||||
'--sidebar-toggle-icon',
|
||||
'--sidebar-link-text',
|
||||
'--sidebar-link-bg',
|
||||
'--sidebar-link-border',
|
||||
'--sidebar-link-hover-bg'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Buttons and Inputs',
|
||||
keys: [
|
||||
'--button-bg',
|
||||
'--button-border',
|
||||
'--button-hover-bg',
|
||||
'--button-secondary-bg',
|
||||
'--button-secondary-border',
|
||||
'--input-bg',
|
||||
'--focus-ring'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Logs and Tables',
|
||||
keys: [
|
||||
'--log-bg',
|
||||
'--log-border',
|
||||
'--log-text',
|
||||
'--table-bg',
|
||||
'--table-head-bg',
|
||||
'--table-row-border',
|
||||
'--table-row-hover-bg',
|
||||
'--table-head-text',
|
||||
'--table-cell-text'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Charts',
|
||||
keys: [
|
||||
'--chart-bg',
|
||||
'--chart-border',
|
||||
'--chart-grid',
|
||||
'--chart-label'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Players',
|
||||
keys: [
|
||||
'--player-box-bg',
|
||||
'--player-box-border',
|
||||
'--player-box-hover-bg',
|
||||
'--player-box-active-bg',
|
||||
'--player-box-active-border',
|
||||
'--player-muted-bg',
|
||||
'--player-muted-border'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const PRESETS = {
|
||||
default: {
|
||||
'--bg': '#0b1220',
|
||||
'--surface': '#111a2e',
|
||||
'--surface-2': '#16233d',
|
||||
'--border': '#24324f',
|
||||
'--text': '#e5ebf8',
|
||||
'--text-muted': '#99a8c6',
|
||||
'--accent': '#4f8cff',
|
||||
'--accent-strong': '#3a75e8',
|
||||
'--danger-bg': '#3d1d29',
|
||||
'--danger-text': '#ffb7c7',
|
||||
'--log-bg': '#0a101d',
|
||||
'--sidebar-bg': '#060b16',
|
||||
'--sidebar-border': '#1b2942',
|
||||
'--sidebar-meta-bg': '#0d1528',
|
||||
'--sidebar-meta-border': '#1f3050',
|
||||
'--sidebar-toggle-text': '#7f93b8',
|
||||
'--sidebar-toggle-icon': '#8fa6d0',
|
||||
'--sidebar-link-text': '#c7d7f7',
|
||||
'--sidebar-link-bg': '#0f1930',
|
||||
'--sidebar-link-border': '#263a5d',
|
||||
'--sidebar-link-hover-bg': '#162642',
|
||||
'--button-bg': '#4f8cff',
|
||||
'--button-border': '#3a75e8',
|
||||
'--button-hover-bg': '#5a95ff',
|
||||
'--button-secondary-bg': '#21314d',
|
||||
'--button-secondary-border': '#344769',
|
||||
'--input-bg': '#0f1930',
|
||||
'--focus-ring': '#4f8cff',
|
||||
'--table-bg': '#0e172b',
|
||||
'--table-head-bg': '#13203b',
|
||||
'--table-row-border': '#1f2d49',
|
||||
'--table-row-hover-bg': '#111d35',
|
||||
'--table-head-text': '#d7e2f8',
|
||||
'--table-cell-text': '#c7d4ee',
|
||||
'--log-border': '#213457',
|
||||
'--log-text': '#d6e0f8',
|
||||
'--chart-bg': '#0b1427',
|
||||
'--chart-border': '#213457',
|
||||
'--chart-grid': '#7896d2',
|
||||
'--chart-label': '#8ea6d2',
|
||||
'--player-box-bg': '#0f1930',
|
||||
'--player-box-border': '#23365a',
|
||||
'--player-box-hover-bg': '#162642',
|
||||
'--player-box-active-bg': '#1b2c4a',
|
||||
'--player-box-active-border': '#3f73cf',
|
||||
'--player-muted-bg': '#101a2f',
|
||||
'--player-muted-border': '#2a3f63'
|
||||
},
|
||||
midnight: {
|
||||
'--bg': '#070b17',
|
||||
'--surface': '#0f1a30',
|
||||
'--surface-2': '#132445',
|
||||
'--border': '#263f67',
|
||||
'--text': '#e6f0ff',
|
||||
'--text-muted': '#97abd2',
|
||||
'--accent': '#4f8cff',
|
||||
'--accent-strong': '#2c6fe2',
|
||||
'--danger-bg': '#3b1a2f',
|
||||
'--danger-text': '#ffb5c6',
|
||||
'--log-bg': '#090f1d',
|
||||
'--sidebar-bg': '#050915',
|
||||
'--sidebar-border': '#1b3158',
|
||||
'--sidebar-meta-bg': '#0c1730',
|
||||
'--sidebar-meta-border': '#1f3a67',
|
||||
'--sidebar-toggle-text': '#8aa1ca',
|
||||
'--sidebar-toggle-icon': '#9cb4e2',
|
||||
'--sidebar-link-text': '#d2e2ff',
|
||||
'--sidebar-link-bg': '#11203b',
|
||||
'--sidebar-link-border': '#2b4773',
|
||||
'--sidebar-link-hover-bg': '#183055',
|
||||
'--button-bg': '#4f8cff',
|
||||
'--button-border': '#2c6fe2',
|
||||
'--button-hover-bg': '#5f98ff',
|
||||
'--button-secondary-bg': '#23385e',
|
||||
'--button-secondary-border': '#35507e',
|
||||
'--input-bg': '#11203b',
|
||||
'--focus-ring': '#4f8cff',
|
||||
'--table-bg': '#0f1a31',
|
||||
'--table-head-bg': '#13274a',
|
||||
'--table-row-border': '#2a4773',
|
||||
'--table-row-hover-bg': '#183157',
|
||||
'--table-head-text': '#d7e6ff',
|
||||
'--table-cell-text': '#cddcff',
|
||||
'--log-border': '#2b4a78',
|
||||
'--log-text': '#d4e3ff',
|
||||
'--chart-bg': '#0f1d38',
|
||||
'--chart-border': '#2b4a78',
|
||||
'--chart-grid': '#87a7df',
|
||||
'--chart-label': '#a3b8df',
|
||||
'--player-box-bg': '#11203b',
|
||||
'--player-box-border': '#2b4773',
|
||||
'--player-box-hover-bg': '#183055',
|
||||
'--player-box-active-bg': '#223c69',
|
||||
'--player-box-active-border': '#4b82de',
|
||||
'--player-muted-bg': '#0f1b34',
|
||||
'--player-muted-border': '#2a4268'
|
||||
},
|
||||
forest: {
|
||||
'--bg': '#0b1612',
|
||||
'--surface': '#11231b',
|
||||
'--surface-2': '#173026',
|
||||
'--border': '#29503f',
|
||||
'--text': '#e7fff5',
|
||||
'--text-muted': '#97c7b0',
|
||||
'--accent': '#3fbf7f',
|
||||
'--accent-strong': '#2ca866',
|
||||
'--danger-bg': '#3d1d29',
|
||||
'--danger-text': '#ffb7c7',
|
||||
'--log-bg': '#0b1511',
|
||||
'--sidebar-bg': '#08130f',
|
||||
'--sidebar-border': '#1f3f33',
|
||||
'--sidebar-meta-bg': '#10231c',
|
||||
'--sidebar-meta-border': '#2a4f40',
|
||||
'--sidebar-toggle-text': '#8db8a3',
|
||||
'--sidebar-toggle-icon': '#9fceb8',
|
||||
'--sidebar-link-text': '#d6f5e7',
|
||||
'--sidebar-link-bg': '#133026',
|
||||
'--sidebar-link-border': '#2f5f4b',
|
||||
'--sidebar-link-hover-bg': '#1a3d31',
|
||||
'--button-bg': '#3fbf7f',
|
||||
'--button-border': '#2ca866',
|
||||
'--button-hover-bg': '#4dce8d',
|
||||
'--button-secondary-bg': '#24493b',
|
||||
'--button-secondary-border': '#356652',
|
||||
'--input-bg': '#133026',
|
||||
'--focus-ring': '#3fbf7f',
|
||||
'--table-bg': '#11271f',
|
||||
'--table-head-bg': '#173629',
|
||||
'--table-row-border': '#2e5b48',
|
||||
'--table-row-hover-bg': '#1a3f31',
|
||||
'--table-head-text': '#daf5e8',
|
||||
'--table-cell-text': '#c8ebda',
|
||||
'--log-border': '#2e5a49',
|
||||
'--log-text': '#d9f4e8',
|
||||
'--chart-bg': '#10261e',
|
||||
'--chart-border': '#2e5a49',
|
||||
'--chart-grid': '#7fb6a1',
|
||||
'--chart-label': '#94c7b3',
|
||||
'--player-box-bg': '#133026',
|
||||
'--player-box-border': '#2f5f4b',
|
||||
'--player-box-hover-bg': '#1a3d31',
|
||||
'--player-box-active-bg': '#23503f',
|
||||
'--player-box-active-border': '#43a379',
|
||||
'--player-muted-bg': '#11271f',
|
||||
'--player-muted-border': '#2c5342'
|
||||
},
|
||||
ember: {
|
||||
'--bg': '#18100b',
|
||||
'--surface': '#2a1a11',
|
||||
'--surface-2': '#3a2416',
|
||||
'--border': '#5a3a23',
|
||||
'--text': '#fff0e8',
|
||||
'--text-muted': '#d6b8a4',
|
||||
'--accent': '#ff8f4f',
|
||||
'--accent-strong': '#e67839',
|
||||
'--danger-bg': '#4a1f22',
|
||||
'--danger-text': '#ffb7b7',
|
||||
'--log-bg': '#1a120d',
|
||||
'--sidebar-bg': '#140d08',
|
||||
'--sidebar-border': '#4c311f',
|
||||
'--sidebar-meta-bg': '#23160e',
|
||||
'--sidebar-meta-border': '#5e3b26',
|
||||
'--sidebar-toggle-text': '#d1a78f',
|
||||
'--sidebar-toggle-icon': '#e0b69e',
|
||||
'--sidebar-link-text': '#ffe4d2',
|
||||
'--sidebar-link-bg': '#382116',
|
||||
'--sidebar-link-border': '#6f4228',
|
||||
'--sidebar-link-hover-bg': '#4a2c1d',
|
||||
'--button-bg': '#ff8f4f',
|
||||
'--button-border': '#e67839',
|
||||
'--button-hover-bg': '#ffa366',
|
||||
'--button-secondary-bg': '#5a3622',
|
||||
'--button-secondary-border': '#7a4a30',
|
||||
'--input-bg': '#382116',
|
||||
'--focus-ring': '#ff8f4f',
|
||||
'--table-bg': '#2f1d13',
|
||||
'--table-head-bg': '#432719',
|
||||
'--table-row-border': '#6b4027',
|
||||
'--table-row-hover-bg': '#4b2e1f',
|
||||
'--table-head-text': '#ffe7da',
|
||||
'--table-cell-text': '#f2d2bf',
|
||||
'--log-border': '#70442a',
|
||||
'--log-text': '#ffe4d5',
|
||||
'--chart-bg': '#341f14',
|
||||
'--chart-border': '#70442a',
|
||||
'--chart-grid': '#c18e71',
|
||||
'--chart-label': '#d2a98f',
|
||||
'--player-box-bg': '#382116',
|
||||
'--player-box-border': '#6f4228',
|
||||
'--player-box-hover-bg': '#4a2c1d',
|
||||
'--player-box-active-bg': '#603924',
|
||||
'--player-box-active-border': '#ff9a63',
|
||||
'--player-muted-bg': '#311d12',
|
||||
'--player-muted-border': '#5b3521'
|
||||
}
|
||||
};
|
||||
|
||||
function setStatus(message, isError) {
|
||||
const status = document.getElementById('themeStatus');
|
||||
status.textContent = message || '';
|
||||
status.className = isError ? 'action-status error-text' : 'action-status success-text';
|
||||
}
|
||||
|
||||
function currentValueForKey(key) {
|
||||
const value = getComputedStyle(document.documentElement).getPropertyValue(key).trim();
|
||||
return value || PRESETS.default[key] || '#000000';
|
||||
}
|
||||
|
||||
function normalizeHex(value) {
|
||||
if (!value) return '#000000';
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed.startsWith('#')) return '#000000';
|
||||
if (trimmed.length === 7) return trimmed;
|
||||
if (trimmed.length === 4) {
|
||||
return `#${trimmed[1]}${trimmed[1]}${trimmed[2]}${trimmed[2]}${trimmed[3]}${trimmed[3]}`;
|
||||
}
|
||||
return '#000000';
|
||||
}
|
||||
|
||||
function buildThemeField(key) {
|
||||
const row = document.createElement('label');
|
||||
row.className = 'theme-field';
|
||||
row.setAttribute('for', `theme-${key.replace(/[^a-z0-9]/gi, '')}`);
|
||||
|
||||
const name = document.createElement('span');
|
||||
name.textContent = key;
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'color';
|
||||
input.id = `theme-${key.replace(/[^a-z0-9]/gi, '')}`;
|
||||
input.dataset.themeKey = key;
|
||||
input.value = normalizeHex(currentValueForKey(key));
|
||||
input.addEventListener('input', () => {
|
||||
document.documentElement.style.setProperty(key, input.value);
|
||||
});
|
||||
|
||||
row.appendChild(name);
|
||||
row.appendChild(input);
|
||||
return row;
|
||||
}
|
||||
|
||||
function buildThemeGroup(title, keys) {
|
||||
const section = document.createElement('section');
|
||||
section.className = 'theme-group';
|
||||
|
||||
const heading = document.createElement('h3');
|
||||
heading.className = 'theme-group-title';
|
||||
heading.textContent = title;
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.className = 'theme-group-grid';
|
||||
|
||||
keys.forEach(key => {
|
||||
content.appendChild(buildThemeField(key));
|
||||
});
|
||||
|
||||
section.appendChild(heading);
|
||||
section.appendChild(content);
|
||||
return section;
|
||||
}
|
||||
|
||||
function buildThemeGrid() {
|
||||
const grid = document.getElementById('themeGrid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
const usedKeys = new Set();
|
||||
THEME_GROUPS.forEach(group => {
|
||||
const validKeys = group.keys.filter(key => THEME_KEYS.includes(key));
|
||||
if (validKeys.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
validKeys.forEach(key => usedKeys.add(key));
|
||||
grid.appendChild(buildThemeGroup(group.title, validKeys));
|
||||
});
|
||||
|
||||
const ungrouped = THEME_KEYS.filter(key => !usedKeys.has(key));
|
||||
if (ungrouped.length > 0) {
|
||||
grid.appendChild(buildThemeGroup('Other', ungrouped));
|
||||
}
|
||||
}
|
||||
|
||||
function collectThemeFromInputs() {
|
||||
const theme = {};
|
||||
document.querySelectorAll('input[data-theme-key]').forEach(input => {
|
||||
theme[input.dataset.themeKey] = input.value;
|
||||
});
|
||||
return theme;
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
if (window.MinePanelTheme) {
|
||||
window.MinePanelTheme.applyTheme(theme);
|
||||
} else {
|
||||
Object.entries(theme).forEach(([key, value]) => {
|
||||
document.documentElement.style.setProperty(key, value);
|
||||
});
|
||||
}
|
||||
buildThemeGrid();
|
||||
}
|
||||
|
||||
async function api(url, options) {
|
||||
const res = await fetch(url, options);
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || 'Request failed');
|
||||
return data;
|
||||
}
|
||||
|
||||
async function loadMe() {
|
||||
const data = await api('/api/me');
|
||||
document.getElementById('me').textContent = `Logged in as ${data.user.username} (${data.user.role})`;
|
||||
}
|
||||
|
||||
document.getElementById('applyPreset').addEventListener('click', () => {
|
||||
const preset = document.getElementById('themePreset').value;
|
||||
const theme = PRESETS[preset] || PRESETS.default;
|
||||
applyTheme(theme);
|
||||
setStatus(`Applied ${preset} preset. Save to keep it.`, false);
|
||||
});
|
||||
|
||||
document.getElementById('saveTheme').addEventListener('click', () => {
|
||||
const theme = collectThemeFromInputs();
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(theme));
|
||||
setStatus('Custom theme saved.', false);
|
||||
});
|
||||
|
||||
document.getElementById('resetTheme').addEventListener('click', () => {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
applyTheme(PRESETS.default);
|
||||
setStatus('Theme reset to default.', false);
|
||||
});
|
||||
|
||||
document.getElementById('logout').addEventListener('click', async () => {
|
||||
await api('/api/logout', { method: 'POST' });
|
||||
window.location.href = '/';
|
||||
});
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
initSidebarCategories();
|
||||
await loadMe();
|
||||
buildThemeGrid();
|
||||
} catch {
|
||||
window.location.href = '/';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user