1<?php
2require_once __DIR__ . '/../includes/bootstrap.php';
3must_admin_only();
4$PAGE_TITLE = 'Themes & Templates';
5$ADMIN_PAGE = 'themes';
6$flash = null;
7
8if ($_SERVER['REQUEST_METHOD']==='POST' && csrf_ok()) {
9 $action = post('action');
10
11 if ($action === 'create' || $action === 'update') {
12 $name = post('name');
13 $css = post('css');
14 if ($name) {
15 if ($action === 'create') {
16 $slug = unique_slug($name, 'themes');
17 DB::insert('INSERT INTO themes (name,slug,css) VALUES (?,?,?)', [$name,$slug,$css]);
18 $flash = ['ok','Theme created!'];
19 } else {
20 $tid = (int)post('id');
21 DB::run('UPDATE themes SET name=?,css=? WHERE id=?', [$name,$css,$tid]);
22 $flash = ['ok','Theme updated!'];
23 }
24 }
25 } elseif ($action === 'activate') {
26 $tid = (int)post('id');
27 DB::run('UPDATE themes SET is_active=0');
28 DB::run('UPDATE themes SET is_active=1 WHERE id=?', [$tid]);
29 $flash = ['ok','Theme activated!'];
30 } elseif ($action === 'deactivate') {
31 DB::run('UPDATE themes SET is_active=0');
32 $flash = ['ok','Default theme restored.'];
33 } elseif ($action === 'delete') {
34 $tid = (int)post('id');
35 DB::run('DELETE FROM themes WHERE id=?', [$tid]);
36 $flash = ['ok','Theme deleted.'];
37 }
38}
39
40$themes = DB::rows('SELECT * FROM themes ORDER BY id');
41$editId = (int)get('edit');
42$editTheme = $editId ? DB::row('SELECT * FROM themes WHERE id=?', [$editId]) : null;
43
44include __DIR__ . '/../views/partials/admin_layout.php';
45?>
46
47<?php if ($flash): ?>
48 <div class="alert <?= $flash[0] ?>"><?= e($flash[1]) ?></div>
49<?php endif; ?>
50
51<div class="admin-two-col">
52 <!-- Theme List -->
53 <div class="acard">
54 <div class="acard-head">
55 <h2>๐จ Themes</h2>
56 <span style="font-size:12px;color:#94a3b8">One theme active at a time</span>
57 </div>
58 <div class="acard-body" style="padding:0">
59 <?php if (empty($themes)): ?>
60 <p style="padding:20px;color:#94a3b8;text-align:center">No themes yet. Create one below.</p>
61 <?php else: ?>
62 <?php foreach ($themes as $th): ?>
63 <div class="theme-row <?= $th['is_active'] ? 'theme-active' : '' ?>">
64 <div class="theme-info">
65 <?php if ($th['is_active']): ?>
66 <span class="stag active" style="margin-right:8px">โ Active</span>
67 <?php endif; ?>
68 <strong><?= e($th['name']) ?></strong>
69 <span style="font-size:12px;color:#94a3b8;margin-left:8px"><?= strlen($th['css']) ?> chars CSS</span>
70 </div>
71 <div class="theme-btns">
72 <a href="?edit=<?= $th['id'] ?>" class="btn-ghost btn-sm">Edit</a>
73 <?php if (!$th['is_active']): ?>
74 <form method="POST" style="display:inline"><?= csrf_input() ?>
75 <input type="hidden" name="action" value="activate">
76 <input type="hidden" name="id" value="<?= $th['id'] ?>">
77 <button class="btn-primary btn-sm">Activate</button>
78 </form>
79 <?php else: ?>
80 <form method="POST" style="display:inline"><?= csrf_input() ?>
81 <input type="hidden" name="action" value="deactivate">
82 <button class="btn-ghost btn-sm">Deactivate</button>
83 </form>
84 <?php endif; ?>
85 <form method="POST" style="display:inline"><?= csrf_input() ?>
86 <input type="hidden" name="action" value="delete">
87 <input type="hidden" name="id" value="<?= $th['id'] ?>">
88 <button class="btn-danger btn-sm" onclick="return confirm('Delete theme?')">Delete</button>
89 </form>
90 </div>
91 </div>
92 <?php endforeach; ?>
93 <?php endif; ?>
94 </div>
95 </div>
96
97 <!-- Create / Edit -->
98 <div class="acard">
99 <div class="acard-head">
100 <h2><?= $editTheme ? 'โ๏ธ Edit: '.e($editTheme['name']) : '+ New Theme' ?></h2>
101 <?php if ($editTheme): ?><a href="<?= u('admin/themes.php') ?>" class="btn-ghost btn-sm">Cancel</a><?php endif; ?>
102 </div>
103 <div class="acard-body">
104 <form method="POST">
105 <?= csrf_input() ?>
106 <input type="hidden" name="action" value="<?= $editTheme ? 'update' : 'create' ?>">
107 <?php if ($editTheme): ?>
108 <input type="hidden" name="id" value="<?= $editTheme['id'] ?>">
109 <?php endif; ?>
110
111 <div class="fg">
112 <label>Theme Name</label>
113 <input type="text" name="name" class="fi" required placeholder="My Custom Theme"
114 value="<?= e($editTheme['name'] ?? '') ?>">
115 </div>
116
117 <div class="fg">
118 <label>Custom CSS</label>
119 <div class="css-editor-wrap">
120 <div class="css-presets">
121 <span style="font-size:12px;color:#94a3b8;margin-right:6px">Presets:</span>
122 <button type="button" class="btn-ghost btn-sm" onclick="applyPreset('dark')">๐ Dark</button>
123 <button type="button" class="btn-ghost btn-sm" onclick="applyPreset('warm')">๐ Warm</button>
124 <button type="button" class="btn-ghost btn-sm" onclick="applyPreset('green')">๐ฟ Green</button>
125 <button type="button" class="btn-ghost btn-sm" onclick="applyPreset('purple')">๐ Purple</button>
126 </div>
127 <textarea name="css" id="cssEditor" class="fi css-ta" rows="18" spellcheck="false"
128 placeholder="
129
130
131
132:root {
133 --blue:
134 --blue-d:
135 --blue-l:
136}
137
138
139body {
140 background:
141 color:
142}"><?= e($editTheme['css'] ?? '') ?></textarea>
143 </div>
144 </div>
145
146 <div style="display:flex;gap:10px;align-items:center">
147 <button type="submit" class="btn-primary">๐พ Save Theme</button>
148 <button type="button" class="btn-ghost" onclick="previewTheme()">๐๏ธ Preview</button>
149 <span style="font-size:12px;color:#94a3b8">Preview opens forum in new tab</span>
150 </div>
151 </form>
152 </div>
153 </div>
154</div>
155
156<!-- CSS Variable Reference -->
157<div class="acard">
158 <div class="acard-head"><h2>๐ CSS Variables Reference</h2></div>
159 <div class="acard-body">
160 <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;font-size:13px">
161 <?php
162 $vars = [
163 ['--blue','Primary color (links, buttons)','#3b82f6'],
164 ['--blue-d','Primary hover color','#2563eb'],
165 ['--blue-l','Primary light background','#eff6ff'],
166 ['--bg','Page background','#f1f5f9'],
167 ['--surface','Card/panel background','#ffffff'],
168 ['--border','Border color','#e2e8f0'],
169 ['--text','Main text color','#0f172a'],
170 ['--muted','Secondary text','#64748b'],
171 ['--green','Success/accent color','#22c55e'],
172 ['--red','Danger color','#ef4444'],
173 ['--amber','Warning color','#f59e0b'],
174 ['--header','Header height','56px'],
175 ['--sidebar','Sidebar width','210px'],
176 ['--font','Font family','Inter'],
177 ['--mono','Monospace font','JetBrains Mono'],
178 ['--r','Border radius','8px'],
179 ];
180 foreach ($vars as [$var,$desc,$val]): ?>
181 <div style="background:#f8fafc;padding:8px 10px;border-radius:6px;border:1px solid #e2e8f0">
182 <code style="color:#6366f1;font-size:12px"><?= e($var) ?></code>
183 <div style="font-size:11px;color:#94a3b8;margin-top:2px"><?= e($desc) ?></div>
184 <div style="font-size:11px;color:#64748b">Default: <em><?= e($val) ?></em></div>
185 </div>
186 <?php endforeach; ?>
187 </div>
188 </div>
189</div>
190
191<style>
192.theme-row{display:flex;align-items:center;justify-content:space-between;padding:12px 18px;border-bottom:1px solid
193.theme-row:hover{background:
194.theme-row:last-child{border-bottom:none}
195.theme-active{background:
196.theme-info{display:flex;align-items:center;flex:1}
197.theme-btns{display:flex;gap:6px;align-items:center}
198.css-editor-wrap{background:
199.css-presets{display:flex;align-items:center;gap:4px;padding:8px 12px;background:
200.css-ta{background:
201.css-ta:focus{box-shadow:none!important}
202</style>
203
204<script>
205var presets = {
206 dark: `:root {
207 --blue:
208 --blue-d:
209 --blue-l:
210 --bg:
211 --surface:
212 --border:
213 --border-l:
214 --text:
215 --muted:
216 --faint:
217}
218.site-header { background:
219.sidebar { background:
220.site-footer { background:
221.cat-card, .topic-row, .post { background:
222.reply-box { background:
223.tl-hdr { background:
224.topic-list { background:
225
226 warm: `:root {
227 --blue:
228 --blue-d:
229 --blue-l:
230 --bg:
231 --surface:
232 --border:
233 --text:
234 --muted:
235}`,
236
237 green: `:root {
238 --blue:
239 --blue-d:
240 --blue-l:
241 --bg:
242 --surface:
243 --border:
244 --text:
245 --muted:
246}`,
247
248 purple: `:root {
249 --blue:
250 --blue-d:
251 --blue-l:
252 --bg:
253 --surface:
254 --border:
255 --text:
256 --muted:
257}`
258};
259
260function applyPreset(name) {
261 document.getElementById('cssEditor').value = presets[name] || '';
262}
263
264function previewTheme() {
265 var css = document.getElementById('cssEditor').value;
266 var key = 'nx_preview_css';
267 localStorage.setItem(key, css);
268 var w = window.open('<?= u('/') ?>', '_blank');
269 w.addEventListener('load', function() {
270 w.postMessage({ type: 'nx_preview', css: css }, '*');
271 });
272 alert('Preview tip: the theme preview uses postMessage.\nActivate the theme to see it properly in all browsers.');
273}
274</script>
275
276<?php include __DIR__ . '/../views/partials/admin_layout_end.php'; ?>