1 <?php 2 require_once __DIR__ . '/../includes/bootstrap.php'; 3 must_admin_only(); 4 $PAGE_TITLE = 'Themes & Templates'; 5 $ADMIN_PAGE = 'themes'; 6 $flash = null; 7 8 if ($_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 44 include __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="/* Write your custom CSS here */ 129 /* Override any CSS variables or rules */ 130 131 /* Example: Change primary color */ 132 :root { 133 --blue: #e74c3c; 134 --blue-d: #c0392b; 135 --blue-l: #fdf2f2; 136 } 137 138 /* Example: Custom background */ 139 body { 140 background: #1a1a2e; 141 color: #e0e0e0; 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 #f1f5f9;transition:background .18s} 193 .theme-row:hover{background:#f8fafc} 194 .theme-row:last-child{border-bottom:none} 195 .theme-active{background:#eff6ff} 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:#1e293b;border-radius:8px;overflow:hidden} 199 .css-presets{display:flex;align-items:center;gap:4px;padding:8px 12px;background:#0f172a} 200 .css-ta{background:#1e293b!important;color:#e2e8f0!important;font-family:var(--mono)!important;font-size:13px!important;border-radius:0!important;border:none!important;padding:14px 16px!important;min-height:300px;line-height:1.6} 201 .css-ta:focus{box-shadow:none!important} 202 </style> 203 204 <script> 205 var presets = { 206 dark: `:root { 207 --blue: #6366f1; 208 --blue-d: #4f46e5; 209 --blue-l: #1e1b4b; 210 --bg: #0f172a; 211 --surface: #1e293b; 212 --border: #334155; 213 --border-l: #1e293b; 214 --text: #f1f5f9; 215 --muted: #94a3b8; 216 --faint: #64748b; 217 } 218 .site-header { background: #1e293b; border-color: #334155; } 219 .sidebar { background: #1e293b; border-color: #334155; } 220 .site-footer { background: #1e293b; border-color: #334155; } 221 .cat-card, .topic-row, .post { background: #1e293b; border-color: #334155; } 222 .reply-box { background: #1e293b; border-color: #334155; } 223 .tl-hdr { background: #0f172a; } 224 .topic-list { background: #1e293b; border-color: #334155; }`, 225 226 warm: `:root { 227 --blue: #ea580c; 228 --blue-d: #c2410c; 229 --blue-l: #fff7ed; 230 --bg: #fdf4ec; 231 --surface: #fffaf5; 232 --border: #fed7aa; 233 --text: #431407; 234 --muted: #9a3412; 235 }`, 236 237 green: `:root { 238 --blue: #16a34a; 239 --blue-d: #15803d; 240 --blue-l: #f0fdf4; 241 --bg: #f0fdf4; 242 --surface: #ffffff; 243 --border: #bbf7d0; 244 --text: #14532d; 245 --muted: #166534; 246 }`, 247 248 purple: `:root { 249 --blue: #9333ea; 250 --blue-d: #7e22ce; 251 --blue-l: #faf5ff; 252 --bg: #faf5ff; 253 --surface: #ffffff; 254 --border: #e9d5ff; 255 --text: #3b0764; 256 --muted: #6b21a8; 257 }` 258 }; 259 260 function applyPreset(name) { 261 document.getElementById('cssEditor').value = presets[name] || ''; 262 } 263 264 function 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'; ?>