@a83/orbiter-admin 0.3.44 → 0.3.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a83/orbiter-admin",
3
- "version": "0.3.44",
3
+ "version": "0.3.46",
4
4
  "description": "Standalone admin server for Orbiter CMS",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
package/public/style.css CHANGED
@@ -1158,8 +1158,8 @@ html[data-style="xfce"] .editor-shell {
1158
1158
  flex-direction: column;
1159
1159
  align-items: center;
1160
1160
  justify-content: flex-end;
1161
- width: var(--dock-item-base, 50px);
1162
- padding: 8px 7px 7px;
1161
+ min-width: var(--dock-item-base, 50px);
1162
+ padding: 10px 12px 9px;
1163
1163
  background: transparent;
1164
1164
  border: none;
1165
1165
  border-radius: 12px;
@@ -1196,21 +1196,25 @@ html[data-style="xfce"] .editor-shell {
1196
1196
  }
1197
1197
  .xfce-dock-item.active {
1198
1198
  color: var(--accent);
1199
- background: color-mix(in srgb, var(--accent) 9%, transparent);
1199
+ background: color-mix(in srgb, var(--accent) 14%, transparent);
1200
+ box-shadow:
1201
+ 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent),
1202
+ 0 0 14px color-mix(in srgb, var(--accent) 22%, transparent),
1203
+ inset 0 1px 0 color-mix(in srgb, white 18%, transparent);
1200
1204
  }
1201
- .xfce-dock-item.active::before { opacity: .5; }
1202
- /* active indicator: glowing scan-line instead of dot */
1205
+ .xfce-dock-item.active::before { opacity: .65; }
1206
+ /* active indicator: glowing dot at bottom */
1203
1207
  .xfce-dock-item.active::after {
1204
1208
  content: '';
1205
1209
  position: absolute;
1206
- bottom: 0;
1210
+ bottom: 4px;
1207
1211
  left: 50%;
1208
1212
  transform: translateX(-50%);
1209
- width: 60%;
1210
- height: 2px;
1211
- border-radius: 1px;
1212
- background: linear-gradient(90deg, transparent, var(--accent) 30%, var(--accent) 70%, transparent);
1213
- box-shadow: 0 0 8px 1px color-mix(in srgb, var(--accent) 70%, transparent);
1213
+ width: 4px;
1214
+ height: 4px;
1215
+ border-radius: 50%;
1216
+ background: var(--accent);
1217
+ box-shadow: 0 0 6px 2px color-mix(in srgb, var(--accent) 80%, transparent);
1214
1218
  }
1215
1219
 
1216
1220
  .xfce-dock-icon {
@@ -1227,9 +1231,10 @@ html[data-style="xfce"] .editor-shell {
1227
1231
  transform: scale(1.14);
1228
1232
  }
1229
1233
  .xfce-dock-item.active .xfce-dock-icon {
1230
- opacity: .95;
1234
+ opacity: 1;
1231
1235
  color: var(--accent);
1232
- filter: drop-shadow(0 0 5px color-mix(in srgb, var(--accent) 65%, transparent));
1236
+ filter: drop-shadow(0 0 8px color-mix(in srgb, var(--accent) 80%, transparent));
1237
+ transform: scale(1.08);
1233
1238
  }
1234
1239
  .xfce-dock-lbl {
1235
1240
  font-family: var(--mono);
@@ -1246,8 +1251,10 @@ html[data-style="xfce"] .editor-shell {
1246
1251
  color: color-mix(in srgb, var(--accent) 80%, var(--heading));
1247
1252
  }
1248
1253
  .xfce-dock-item.active .xfce-dock-lbl {
1249
- opacity: .85;
1254
+ opacity: 1;
1250
1255
  color: var(--accent);
1256
+ font-weight: 700;
1257
+ letter-spacing: .08em;
1251
1258
  }
1252
1259
 
1253
1260
  /* ── HUD Panel ────────────────────────────────────────────── */
@@ -2016,6 +2023,12 @@ a.xfce-sb-logo:hover { opacity: .8; }
2016
2023
  font-family: var(--mono); transition: background .15s;
2017
2024
  }
2018
2025
  .xfce-sb-palette-btn:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
2026
+ .xfce-sb-cheat {
2027
+ background: none; border: none; cursor: pointer; padding: 0 3px;
2028
+ font-size: 13px; color: var(--muted); font-family: var(--mono); line-height: 1;
2029
+ transition: color .15s;
2030
+ }
2031
+ .xfce-sb-cheat:hover { color: var(--accent); }
2019
2032
 
2020
2033
  /* Draft badge on dock items */
2021
2034
  .xfce-dock-badge {
@@ -2150,6 +2163,20 @@ html[data-dock-pos="left"] .xfce-col-preview.visible {
2150
2163
  .xfce-pal-item-body { flex: 1; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
2151
2164
  .xfce-pal-snippet { font-size: 10px; color: var(--muted); font-family: var(--mono); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2152
2165
 
2166
+ /* palette — recent entries */
2167
+ .xfce-pal-icon-sm { width: 20px; text-align: center; font-size: 10px; color: var(--muted); flex-shrink: 0; }
2168
+ .xfce-pal-group-sub {
2169
+ font-size: 8px; font-family: var(--mono); color: var(--line);
2170
+ letter-spacing: .08em; text-transform: uppercase;
2171
+ padding: 8px 14px 4px; border-top: 1px solid var(--line);
2172
+ }
2173
+ .xfce-pal-status {
2174
+ font-size: 9px; font-family: var(--mono); border-radius: 4px;
2175
+ padding: 1px 6px; border: 1px solid currentColor; white-space: nowrap;
2176
+ }
2177
+ .xfce-pal-status-published { color: var(--jade, #5dc97e); }
2178
+ .xfce-pal-status-draft { color: var(--muted); }
2179
+
2153
2180
  /* ── Status bar build indicator ──────────────────────────── */
2154
2181
  .xfce-sb-build { font-size: 9px; font-family: var(--mono); color: var(--muted); white-space: nowrap; }
2155
2182
  .xfce-sb-g-ind {
@@ -2196,6 +2223,71 @@ html[data-style="xfce"][data-dock-pos="left"] .xfce-ws-overlay {
2196
2223
  bottom: auto; left: 110px; top: 50%; transform: translateY(-50%);
2197
2224
  }
2198
2225
 
2226
+ /* ── Zen / Focus mode ────────────────────────────────────── */
2227
+ html[data-zen="1"] .xfce-sb {
2228
+ opacity: 0; pointer-events: none;
2229
+ transform: translateY(-100%);
2230
+ transition: opacity .3s, transform .3s;
2231
+ }
2232
+ html[data-zen="1"] .xfce-dock {
2233
+ opacity: 0; pointer-events: none;
2234
+ transform: translateY(30px);
2235
+ transition: opacity .3s, transform .3s;
2236
+ }
2237
+ html[data-zen="1"][data-dock-pos="left"] .xfce-dock {
2238
+ transform: translateX(-30px);
2239
+ }
2240
+ html[data-zen="1"] .main {
2241
+ padding-top: 8px !important;
2242
+ padding-bottom: 20px !important;
2243
+ }
2244
+
2245
+ /* ── Shortcut Cheatsheet ─────────────────────────────────── */
2246
+ .xfce-cheat-overlay {
2247
+ display: none; position: fixed; inset: 0;
2248
+ background: rgba(0,0,0,.55); backdrop-filter: blur(4px);
2249
+ z-index: 100001; align-items: center; justify-content: center;
2250
+ }
2251
+ .xfce-cheat-overlay.open { display: flex; }
2252
+ .xfce-cheat-panel {
2253
+ background: var(--bg1); border: 1px solid var(--line);
2254
+ border-radius: 14px; box-shadow: 0 16px 48px rgba(0,0,0,.55);
2255
+ width: 560px; max-width: calc(100vw - 40px); font-family: var(--mono);
2256
+ overflow: hidden;
2257
+ }
2258
+ .xfce-cheat-bar {
2259
+ display: flex; align-items: center; justify-content: space-between;
2260
+ padding: 12px 16px; border-bottom: 1px solid var(--line);
2261
+ background: var(--bg2);
2262
+ }
2263
+ .xfce-cheat-title { color: var(--accent); font-size: 11px; font-weight: 700; letter-spacing: .07em; }
2264
+ .xfce-cheat-close {
2265
+ background: none; border: none; color: var(--muted); cursor: pointer;
2266
+ font-size: 13px; line-height: 1; padding: 2px 4px;
2267
+ }
2268
+ .xfce-cheat-close:hover { color: var(--text); }
2269
+ .xfce-cheat-body {
2270
+ display: grid; grid-template-columns: 1fr 1fr; gap: 0;
2271
+ padding: 16px; gap: 0 24px; max-height: 70vh; overflow-y: auto;
2272
+ }
2273
+ .xfce-cheat-section {
2274
+ font-size: 9px; letter-spacing: .1em; color: var(--accent);
2275
+ text-transform: uppercase; font-weight: 700;
2276
+ margin-bottom: 8px; margin-top: 4px;
2277
+ }
2278
+ .xfce-cheat-row {
2279
+ display: flex; align-items: baseline; gap: 10px;
2280
+ padding: 3px 0; border-bottom: 1px solid rgba(255,255,255,.04);
2281
+ }
2282
+ .xfce-cheat-row:last-child { border-bottom: none; }
2283
+ .xfce-cheat-key {
2284
+ background: var(--bg2); border: 1px solid var(--line);
2285
+ border-radius: 4px; padding: 1px 6px; font-size: 10px;
2286
+ color: var(--accent); font-family: var(--mono); white-space: nowrap;
2287
+ flex-shrink: 0; min-width: 80px; text-align: center;
2288
+ }
2289
+ .xfce-cheat-desc { font-size: 10px; color: var(--text); }
2290
+
2199
2291
  /* ── Bell / Notification Center ─────────────────────────── */
2200
2292
  .xfce-sb-bell {
2201
2293
  background: none; border: none; cursor: pointer; padding: 0 2px;
@@ -2249,6 +2341,32 @@ html[data-style="xfce"][data-dock-pos="left"] .xfce-ws-overlay {
2249
2341
  .xfce-notif-err .xfce-notif-msg { color: var(--red); }
2250
2342
  .xfce-notif-empty { padding: 20px 12px; color: var(--muted); font-size: 10px; text-align: center; }
2251
2343
 
2344
+ /* ── Build indicator — running state ────────────────────── */
2345
+ .xfce-sb-build--running {
2346
+ color: var(--accent) !important;
2347
+ animation: xfce-blink .9s step-end infinite;
2348
+ }
2349
+
2350
+ /* ── Status bar breadcrumb ───────────────────────────────── */
2351
+ .xfce-sb-crumb-link {
2352
+ color: var(--accent); text-decoration: none; font-family: var(--mono); font-size: 11px;
2353
+ }
2354
+ .xfce-sb-crumb-link:hover { text-decoration: underline; }
2355
+ .xfce-sb-crumb-sep { color: var(--muted); font-size: 10px; margin: 0 2px; }
2356
+ .xfce-sb-crumb-page { color: var(--text); font-family: var(--mono); font-size: 11px; }
2357
+
2358
+ /* ── HUD Activity feed ───────────────────────────────────── */
2359
+ .xfce-hud-act-row {
2360
+ display: flex; align-items: baseline; justify-content: space-between;
2361
+ gap: 8px; padding: 3px 0; border-bottom: 1px solid rgba(255,255,255,.04);
2362
+ font-size: 10px; font-family: var(--mono);
2363
+ }
2364
+ .xfce-hud-act-row:last-child { border-bottom: none; }
2365
+ .xfce-hud-act-msg { flex: 1; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2366
+ .xfce-hud-act-time { color: var(--line); font-size: 9px; flex-shrink: 0; }
2367
+ .xfce-hud-act-ok .xfce-hud-act-msg { color: var(--jade, #5dc97e); }
2368
+ .xfce-hud-act-err .xfce-hud-act-msg { color: var(--red, #e06c6c); }
2369
+
2252
2370
  /* ── HUD Drafts section ──────────────────────────────────── */
2253
2371
  .xfce-hud-draft-row {
2254
2372
  display: flex; align-items: baseline; justify-content: space-between;
package/public/xfce.js CHANGED
@@ -13,14 +13,14 @@
13
13
  var NAV = [
14
14
  { icon: '⬡', label: 'Dashboard', href: '/dashboard.html', key: 'dashboard' },
15
15
  { icon: '◫', label: 'Media', href: '/media.html', key: 'media' },
16
- { icon: '⚙', label: 'Settings', href: '/settings.html', key: 'settings' },
17
16
  { icon: '⊛', label: 'Users', href: '/users.html', key: 'users' },
18
17
  ];
19
18
 
20
19
  var TOOLS = [
21
- { icon: '▦', label: 'Schema', href: '/schema.html', key: 'schema' },
22
- { icon: '◉', label: 'Build', href: '/build.html', key: 'build' },
23
- { icon: '↓', label: 'Import', href: '/import.html', key: 'import' },
20
+ { icon: '▦', label: 'Schema', href: '/schema.html', key: 'schema' },
21
+ { icon: '◉', label: 'Build', href: '/build.html', key: 'build' },
22
+ { icon: '↓', label: 'Import', href: '/import.html', key: 'import' },
23
+ { icon: '⚙', label: 'Settings', href: '/settings.html', key: 'settings' },
24
24
  ];
25
25
 
26
26
  var WORKSPACE = [
@@ -30,9 +30,10 @@
30
30
 
31
31
  // palette items — nav + tools pre-seeded; collections appended after /api/info
32
32
  var _palItems = NAV.concat(TOOLS).map(function (n) {
33
- return { icon: n.icon, label: n.label, href: n.href, group: n.key in { schema:1, build:1, import:1 } ? 'Tools' : 'Nav' };
33
+ return { icon: n.icon, label: n.label, href: n.href, group: n.key in { schema:1, build:1, import:1, settings:1 } ? 'Tools' : 'Nav' };
34
34
  });
35
- var _termCols = []; // collection metadata for palette commands
35
+ var _termCols = []; // collection metadata for palette commands
36
+ var _palRecents = []; // prefetched recent entries for empty palette
36
37
 
37
38
  // ── Helpers ───────────────────────────────────────────────────────────
38
39
  function el(tag, cls, html) {
@@ -66,6 +67,8 @@
66
67
  '<span id="xfce-sb-g-ind" class="xfce-sb-g-ind" style="display:none" title="g mode: d=dashboard m=media s=settings u=users b=build i=import h=schema a=account">g ›</span>',
67
68
  '<button id="xfce-sb-bell" class="xfce-sb-bell" title="Notifications"><span id="xfce-sb-bell-icon">○</span><span id="xfce-sb-bell-badge" class="xfce-sb-bell-badge" style="display:none"></span></button>',
68
69
  '<span class="xfce-sb-div">·</span>',
70
+ '<button id="xfce-sb-cheat" class="xfce-sb-cheat" title="Shortcuts (?)">?</button>',
71
+ '<span class="xfce-sb-div">·</span>',
69
72
  '<button id="xfce-sb-palette-btn" class="xfce-sb-palette-btn" title="Command palette (⌘K)">⌘</button>',
70
73
  '<span class="xfce-sb-div">·</span>',
71
74
  '<span id="xfce-sb-build" class="xfce-sb-build" title="Last build"></span>',
@@ -79,10 +82,18 @@
79
82
  ].join('');
80
83
  document.body.insertBefore(sb, document.body.firstChild);
81
84
 
82
- // Page title from document.title
83
- var title = document.title.replace(/\s*—\s*Orbiter.*$/, '').trim();
85
+ // Breadcrumb / page title
84
86
  var titleEl = document.getElementById('xfce-sb-title');
85
- if (titleEl && title) titleEl.textContent = title;
87
+ if (titleEl) {
88
+ if (activeCol && (page === 'entries' || page === 'editor')) {
89
+ // will be filled in by loadInfo once we know the collection label
90
+ titleEl.dataset.col = activeCol;
91
+ titleEl.dataset.page = page;
92
+ } else {
93
+ var title = document.title.replace(/\s*—\s*Orbiter.*$/, '').trim();
94
+ if (title) titleEl.textContent = title;
95
+ }
96
+ }
86
97
 
87
98
  // Clock
88
99
  function tick() {
@@ -110,6 +121,12 @@
110
121
  e.stopPropagation();
111
122
  toggleNotifPanel();
112
123
  });
124
+
125
+ // Cheatsheet
126
+ document.getElementById('xfce-sb-cheat').addEventListener('click', function (e) {
127
+ e.stopPropagation();
128
+ toggleCheatsheet();
129
+ });
113
130
  }
114
131
 
115
132
  // ── HUD Meta Panel ────────────────────────────────────────────────────
@@ -130,6 +147,8 @@
130
147
  '<div id="xfce-hud-cols" class="xfce-hud-rows"></div>',
131
148
  '<div class="xfce-hud-section-label" style="margin-top:16px">Drafts</div>',
132
149
  '<div id="xfce-hud-drafts" class="xfce-hud-rows xfce-hud-drafts"></div>',
150
+ '<div class="xfce-hud-section-label" style="margin-top:16px">Activity</div>',
151
+ '<div id="xfce-hud-activity" class="xfce-hud-rows xfce-hud-activity"></div>',
133
152
  '<div class="xfce-hud-section-label" style="margin-top:16px">Navigation</div>',
134
153
  '<div class="xfce-hud-nav-links" id="xfce-hud-nav"></div>',
135
154
  '</div>',
@@ -154,6 +173,7 @@
154
173
  function toggleHUD() {
155
174
  if (!metaPanel) return;
156
175
  metaPanel.classList.toggle('open');
176
+ if (metaPanel.classList.contains('open')) refreshHUDActivity();
157
177
  }
158
178
 
159
179
  // ── Tools popup ───────────────────────────────────────────────────────
@@ -162,19 +182,12 @@
162
182
  function buildToolsPopup() {
163
183
  toolsPopup = el('div', 'xfce-tools-popup');
164
184
  toolsPopup.id = 'xfce-tools-popup';
165
- TOOLS.forEach(function (t) {
185
+ TOOLS.filter(function (t) { return t.key !== 'settings'; }).forEach(function (t) {
166
186
  var a = el('a', 'xfce-tools-item' + (page === t.key ? ' active' : ''));
167
187
  a.href = t.href;
168
188
  a.innerHTML = '<span class="xfce-tools-icon">' + t.icon + '</span><span>' + t.label + '</span>';
169
189
  toolsPopup.appendChild(a);
170
190
  });
171
- var settingsSep = document.createElement('div');
172
- settingsSep.className = 'xfce-tools-sep';
173
- toolsPopup.appendChild(settingsSep);
174
- var settingsLink = el('a', 'xfce-tools-item' + (page === 'settings' ? ' active' : ''));
175
- settingsLink.href = '/settings.html';
176
- settingsLink.innerHTML = '<span class="xfce-tools-icon">⚙</span><span>Settings</span>';
177
- toolsPopup.appendChild(settingsLink);
178
191
  var sep = document.createElement('div');
179
192
  sep.className = 'xfce-tools-sep';
180
193
  toolsPopup.appendChild(sep);
@@ -446,9 +459,55 @@
446
459
  }
447
460
 
448
461
  q = q.toLowerCase();
449
- var filtered = q
450
- ? _palItems.filter(function (it) { return it.label.toLowerCase().includes(q); })
451
- : _palItems;
462
+
463
+ // Empty input show recents + nav
464
+ if (!q) {
465
+ var html = '';
466
+ if (_palRecents.length) {
467
+ html += '<div class="xfce-pal-group">Recent</div>';
468
+ _palRecents.forEach(function (r) {
469
+ var href = '/collections/' + encodeURIComponent(r.collection) + '/entries/' + encodeURIComponent(r.slug);
470
+ html += '<div class="xfce-pal-item" data-href="' + href + '">'
471
+ + '<span class="xfce-pal-icon xfce-pal-icon-sm">◈</span>'
472
+ + '<div class="xfce-pal-item-body">'
473
+ + '<span class="xfce-pal-label">' + escHtml(r.title || r.slug) + '</span>'
474
+ + '<span class="xfce-pal-snippet">' + escHtml(r.label) + '</span>'
475
+ + '</div>'
476
+ + '<span class="xfce-pal-hint-r xfce-pal-status xfce-pal-status-' + r.status + '">' + r.status + '</span>'
477
+ + '</div>';
478
+ });
479
+ html += '<div class="xfce-pal-group">Navigation</div>';
480
+ }
481
+ var navGroups = {};
482
+ _palItems.forEach(function (it) {
483
+ var g = it.group || 'Nav';
484
+ if (!navGroups[g]) navGroups[g] = [];
485
+ navGroups[g].push(it);
486
+ });
487
+ Object.keys(navGroups).forEach(function (g) {
488
+ if (_palRecents.length) html += '<div class="xfce-pal-group-sub">' + g + '</div>';
489
+ else html += '<div class="xfce-pal-group">' + g + '</div>';
490
+ navGroups[g].forEach(function (it) {
491
+ html += '<div class="xfce-pal-item" data-href="' + it.href + '">'
492
+ + '<span class="xfce-pal-icon">' + it.icon + '</span>'
493
+ + '<span class="xfce-pal-label">' + escHtml(it.label) + '</span>'
494
+ + (it.meta ? '<span class="xfce-pal-hint-r">' + escHtml(it.meta) + '</span>' : '')
495
+ + '</div>';
496
+ });
497
+ });
498
+ paletteResults.innerHTML = html;
499
+ paletteResults.querySelectorAll('.xfce-pal-item[data-href]').forEach(function (item) {
500
+ item.addEventListener('click', function () { location.href = item.dataset.href; closePalette(); });
501
+ item.addEventListener('mouseenter', function () {
502
+ var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
503
+ palActive = Array.from(items).indexOf(item);
504
+ updatePalActive(items);
505
+ });
506
+ });
507
+ return;
508
+ }
509
+
510
+ var filtered = _palItems.filter(function (it) { return it.label.toLowerCase().includes(q); });
452
511
 
453
512
  if (!filtered.length) {
454
513
  paletteResults.innerHTML = '<div class="xfce-pal-empty">No results</div>';
@@ -521,6 +580,8 @@
521
580
  case 'search': palSearch(args.join(' ')); break;
522
581
  case 'build': palBuild(); break;
523
582
  case 'export': palExport(args); break;
583
+ case 'random': palRandom(); break;
584
+ case '=': palMath(args.join(' ')); break;
524
585
  default: palPrint('unknown: <b>' + escHtml(cmd) + '</b> &mdash; try <b>&gt; help</b>', 'err');
525
586
  }
526
587
  }
@@ -534,6 +595,8 @@
534
595
  '<code>info</code> &mdash; pod &amp; version info',
535
596
  '<code>build</code> &mdash; trigger deploy',
536
597
  '<code>export &lt;col&gt; [--md] [--drafts]</code> &mdash; download',
598
+ '<code>random</code> &mdash; jump to a random entry',
599
+ '<code>= &lt;expr&gt;</code> &mdash; evaluate math expression',
537
600
  ].join('<br>'), 'dim');
538
601
  }
539
602
 
@@ -650,12 +713,50 @@
650
713
  .then(function (r) { return r.json(); })
651
714
  .then(function (d) {
652
715
  paletteResults.innerHTML = '';
653
- if (d.ok || d.message) { palPrint('✓ ' + (d.message || 'build triggered'), 'ok'); setTimeout(closePalette, 900); }
654
- else palPrint('build error: ' + escHtml(d.error || JSON.stringify(d)), 'err');
716
+ if (d.ok || d.message) {
717
+ palPrint(' ' + (d.message || 'build triggered'), 'ok');
718
+ startBuildPoll();
719
+ setTimeout(closePalette, 900);
720
+ } else palPrint('build error: ' + escHtml(d.error || JSON.stringify(d)), 'err');
655
721
  })
656
722
  .catch(function () { paletteResults.innerHTML = ''; palPrint('build request failed', 'err'); });
657
723
  }
658
724
 
725
+ function palMath(expr) {
726
+ if (!expr) { palPrint('usage: <code>= 2 * 450 + 12</code>', 'dim'); return; }
727
+ try {
728
+ // only allow safe math chars
729
+ if (!/^[\d\s\+\-\*\/\.\(\)%,^]+$/.test(expr)) throw new Error('unsafe');
730
+ // replace ^ with ** for exponentiation
731
+ var safe = expr.replace(/\^/g, '**');
732
+ // eslint-disable-next-line no-new-func
733
+ var result = Function('"use strict"; return (' + safe + ')')();
734
+ if (typeof result !== 'number' || !isFinite(result)) throw new Error('not a number');
735
+ palPrint('<span style="font-size:15px;color:var(--accent)">' + result + '</span>'
736
+ + ' <span style="color:var(--muted);font-size:10px">= ' + escHtml(expr) + '</span>', '');
737
+ } catch (e) {
738
+ palPrint('invalid expression', 'err');
739
+ }
740
+ }
741
+
742
+ function palRandom() {
743
+ palPrint('picking a random entry…', 'muted');
744
+ fetch('/api/search/recent?limit=50', { credentials: 'include' })
745
+ .then(function (r) { return r.ok ? r.json() : []; })
746
+ .then(function (rows) {
747
+ var published = rows.filter(function (r) { return r.status === 'published'; });
748
+ if (!published.length) { paletteResults.innerHTML = ''; palPrint('no published entries found', 'muted'); return; }
749
+ var pick = published[Math.floor(Math.random() * published.length)];
750
+ var href = '/collections/' + encodeURIComponent(pick.collection) + '/entries/' + encodeURIComponent(pick.slug);
751
+ paletteResults.innerHTML = '';
752
+ palPrint('✦ <a href="' + href + '" style="color:var(--accent)">' + escHtml(pick.title || pick.slug) + '</a>'
753
+ + ' <span style="color:var(--muted)">in ' + escHtml(pick.label) + '</span>', '');
754
+ setTimeout(function () { location.href = href; closePalette(); }, 1200);
755
+ })
756
+ .catch(function () { paletteResults.innerHTML = ''; palPrint('error fetching entries', 'err'); });
757
+ }
758
+
759
+
659
760
  function palExport(args) {
660
761
  if (!args.length) { palPrint('usage: export &lt;collection&gt; [--md] [--drafts]', 'err'); return; }
661
762
  var colId = args[0];
@@ -749,6 +850,7 @@
749
850
  if (badge) { badge.textContent = _notifUnread > 9 ? '9+' : _notifUnread; badge.style.display = ''; }
750
851
  if (icon) { icon.textContent = '●'; }
751
852
  if (_notifPanel && _notifPanel.classList.contains('open')) renderNotifList();
853
+ refreshHUDActivity();
752
854
  }
753
855
 
754
856
  function buildNotifPanel() {
@@ -793,6 +895,24 @@
793
895
  }).join('');
794
896
  }
795
897
 
898
+ function refreshHUDActivity() {
899
+ var el2 = document.getElementById('xfce-hud-activity');
900
+ if (!el2) return;
901
+ if (!_notifications.length) {
902
+ el2.innerHTML = '<div class="xfce-hud-empty">No activity yet</div>';
903
+ return;
904
+ }
905
+ el2.innerHTML = _notifications.slice(0, 8).map(function (n) {
906
+ var ago = Math.floor((Date.now() - n.time) / 1000);
907
+ var t = ago < 60 ? ago + 's' : ago < 3600 ? Math.floor(ago / 60) + 'm' : Math.floor(ago / 3600) + 'h';
908
+ var cls = n.type === 'success' ? 'xfce-hud-act-ok' : n.type === 'error' ? 'xfce-hud-act-err' : '';
909
+ return '<div class="xfce-hud-act-row' + (cls ? ' ' + cls : '') + '">'
910
+ + '<span class="xfce-hud-act-msg">' + escHtml(n.msg) + '</span>'
911
+ + '<span class="xfce-hud-act-time">' + t + '</span>'
912
+ + '</div>';
913
+ }).join('');
914
+ }
915
+
796
916
  function toggleNotifPanel() {
797
917
  if (!_notifPanel) buildNotifPanel();
798
918
  var isOpen = _notifPanel.classList.toggle('open');
@@ -1173,6 +1293,9 @@
1173
1293
  });
1174
1294
  dockInner.appendChild(toolsBtn);
1175
1295
 
1296
+ var settingsDockBtn = makeDockItem('⚙', 'Settings', '/settings.html', page === 'settings', false);
1297
+ dockInner.appendChild(settingsDockBtn);
1298
+
1176
1299
  dockInner.appendChild(el('div', 'xfce-dock-sep'));
1177
1300
 
1178
1301
  // HUD toggle
@@ -1292,6 +1415,17 @@
1292
1415
  return hudRow(col.label, col.total + ' published' + draftTxt);
1293
1416
  }).join('');
1294
1417
  }
1418
+
1419
+ // Breadcrumb in status bar center
1420
+ var titleEl = document.getElementById('xfce-sb-title');
1421
+ if (titleEl && titleEl.dataset.col) {
1422
+ var colMeta = (info.collections || []).find(function (c) { return c.id === titleEl.dataset.col; });
1423
+ var colLabel = colMeta ? colMeta.label : titleEl.dataset.col;
1424
+ var crumbPage = titleEl.dataset.page === 'editor' ? 'Editor' : 'Entries';
1425
+ titleEl.innerHTML = '<a href="/entries.html?col=' + encodeURIComponent(titleEl.dataset.col) + '" class="xfce-sb-crumb-link">' + escHtml(colLabel) + '</a>'
1426
+ + '<span class="xfce-sb-crumb-sep"> › </span>'
1427
+ + '<span class="xfce-sb-crumb-page">' + crumbPage + '</span>';
1428
+ }
1295
1429
  })
1296
1430
  .catch(function () {});
1297
1431
 
@@ -1336,12 +1470,100 @@
1336
1470
  if (sbUser) sbUser.textContent = d.user.username;
1337
1471
  })
1338
1472
  .catch(function () {});
1473
+
1474
+ // Prefetch recent entries for palette empty state
1475
+ fetch('/api/search/recent?limit=7', { credentials: 'include' })
1476
+ .then(function (r) { return r.ok ? r.json() : []; })
1477
+ .then(function (rows) { _palRecents = rows; })
1478
+ .catch(function () {});
1339
1479
  }
1340
1480
 
1341
1481
  function hudRow(label, value) {
1342
1482
  return '<div class="xfce-hud-row"><span>' + label + '</span><span>' + value + '</span></div>';
1343
1483
  }
1344
1484
 
1485
+ // ── Zen / Focus mode ─────────────────────────────────────────────────
1486
+ function toggleZen() {
1487
+ var html = document.documentElement;
1488
+ var on = html.dataset.zen !== '1';
1489
+ html.dataset.zen = on ? '1' : '';
1490
+ localStorage.setItem('orb_zen', on ? '1' : '');
1491
+ if (on) window.xfceToast('Focus mode on — ⌘⇧F to exit', 'info');
1492
+ }
1493
+
1494
+ // ── Shortcut cheatsheet ───────────────────────────────────────────────
1495
+ var _cheatEl = null;
1496
+
1497
+ function buildCheatsheet() {
1498
+ _cheatEl = el('div', 'xfce-cheat-overlay');
1499
+ _cheatEl.id = 'xfce-cheat';
1500
+ _cheatEl.innerHTML = [
1501
+ '<div class="xfce-cheat-panel">',
1502
+ '<div class="xfce-cheat-bar">',
1503
+ '<span class="xfce-cheat-title">⌨ Shortcuts</span>',
1504
+ '<button class="xfce-cheat-close" id="xfce-cheat-close">✕</button>',
1505
+ '</div>',
1506
+ '<div class="xfce-cheat-body">',
1507
+ '<div class="xfce-cheat-col">',
1508
+ '<div class="xfce-cheat-section">Navigation</div>',
1509
+ cheatRow('⌘K &nbsp;/&nbsp; /', 'Open command palette'),
1510
+ cheatRow('↑ ↓', 'Move selection in palette'),
1511
+ cheatRow('↵', 'Go to selected item'),
1512
+ cheatRow('Esc', 'Close overlay / panel'),
1513
+ cheatRow('g &nbsp;+&nbsp; d', 'Dashboard'),
1514
+ cheatRow('g &nbsp;+&nbsp; m', 'Media'),
1515
+ cheatRow('g &nbsp;+&nbsp; u', 'Users'),
1516
+ cheatRow('g &nbsp;+&nbsp; s', 'Settings'),
1517
+ cheatRow('g &nbsp;+&nbsp; b', 'Build'),
1518
+ cheatRow('g &nbsp;+&nbsp; i', 'Import'),
1519
+ cheatRow('g &nbsp;+&nbsp; h', 'Schema'),
1520
+ cheatRow('g &nbsp;+&nbsp; a', 'Account'),
1521
+ cheatRow('1 – 9', 'Jump to nth dock item'),
1522
+ '</div>',
1523
+ '<div class="xfce-cheat-col">',
1524
+ '<div class="xfce-cheat-section">Panels</div>',
1525
+ cheatRow('⌘⇧D', 'Toggle HUD'),
1526
+ cheatRow('⌘⇧F', 'Focus / Zen mode'),
1527
+ cheatRow('⌘⇧L', 'Switch to Glass mode'),
1528
+ cheatRow('?', 'This cheatsheet'),
1529
+ '<div class="xfce-cheat-section" style="margin-top:14px">Palette commands</div>',
1530
+ cheatRow('&gt; ls', 'List collections'),
1531
+ cheatRow('&gt; go &lt;page&gt;', 'Navigate to page'),
1532
+ cheatRow('&gt; new &lt;col&gt;', 'New entry in collection'),
1533
+ cheatRow('&gt; search &lt;q&gt;', 'Search entries'),
1534
+ cheatRow('&gt; build', 'Trigger site build'),
1535
+ cheatRow('&gt; export &lt;col&gt;', 'Export collection'),
1536
+ cheatRow('&gt; random', 'Jump to random entry'),
1537
+ cheatRow('&gt; = &lt;expr&gt;', 'Evaluate math'),
1538
+ cheatRow('&gt; info', 'Show pod info'),
1539
+ cheatRow('&gt; help', 'Show command help'),
1540
+ '</div>',
1541
+ '</div>',
1542
+ '</div>',
1543
+ ].join('');
1544
+ document.body.appendChild(_cheatEl);
1545
+ document.getElementById('xfce-cheat-close').addEventListener('click', closeCheatsheet);
1546
+ _cheatEl.addEventListener('click', function (e) {
1547
+ if (e.target === _cheatEl) closeCheatsheet();
1548
+ });
1549
+ document.addEventListener('keydown', function (e) {
1550
+ if (e.key === 'Escape' && _cheatEl && _cheatEl.classList.contains('open')) closeCheatsheet();
1551
+ });
1552
+ }
1553
+
1554
+ function cheatRow(key, desc) {
1555
+ return '<div class="xfce-cheat-row"><kbd class="xfce-cheat-key">' + key + '</kbd><span class="xfce-cheat-desc">' + desc + '</span></div>';
1556
+ }
1557
+
1558
+ function toggleCheatsheet() {
1559
+ if (!_cheatEl) buildCheatsheet();
1560
+ _cheatEl.classList.toggle('open');
1561
+ }
1562
+
1563
+ function closeCheatsheet() {
1564
+ if (_cheatEl) _cheatEl.classList.remove('open');
1565
+ }
1566
+
1345
1567
  // ── Keyboard shortcuts ────────────────────────────────────────────────
1346
1568
  var _gPending = false, _gTimer = null;
1347
1569
  var G_MAP = { d: '/dashboard.html', m: '/media.html', s: '/settings.html',
@@ -1403,6 +1625,13 @@
1403
1625
  return;
1404
1626
  }
1405
1627
 
1628
+ // ⌘⇧F — zen / focus mode
1629
+ if (mod && e.shiftKey && (e.key === 'f' || e.key === 'F')) {
1630
+ e.preventDefault();
1631
+ toggleZen();
1632
+ return;
1633
+ }
1634
+
1406
1635
  // ⌘⇧L — switch back to glass mode
1407
1636
  if (mod && e.shiftKey && (e.key === 'l' || e.key === 'L')) {
1408
1637
  e.preventDefault();
@@ -1411,6 +1640,13 @@
1411
1640
  return;
1412
1641
  }
1413
1642
 
1643
+ // ? — shortcut cheatsheet
1644
+ if (!mod && !e.altKey && e.key === '?' && !isEditing(e.target)) {
1645
+ e.preventDefault();
1646
+ toggleCheatsheet();
1647
+ return;
1648
+ }
1649
+
1414
1650
  // 1–9 — jump to nth dock link (no modifier, not in input)
1415
1651
  if (!mod && !e.shiftKey && !e.altKey && !isEditing(e.target)) {
1416
1652
  var n = parseInt(e.key);
@@ -1430,19 +1666,40 @@
1430
1666
  }
1431
1667
 
1432
1668
  // ── Build status in status bar ────────────────────────────────────────
1669
+ var _buildPolling = false;
1670
+
1433
1671
  function loadBuildStatus() {
1434
1672
  fetch('/api/build/status', { credentials: 'include' })
1435
1673
  .then(function (r) { return r.ok ? r.json() : null; })
1436
1674
  .then(function (d) {
1437
- if (!d || !d.lastTriggered) return;
1675
+ if (!d) return;
1438
1676
  var el2 = document.getElementById('xfce-sb-build');
1439
1677
  var sep = document.getElementById('xfce-sb-build-sep');
1440
1678
  if (!el2) return;
1441
- var dt = new Date(d.lastTriggered.replace(' ', 'T'));
1442
- var now = new Date();
1443
- var diffM = Math.floor((now - dt) / 60000);
1679
+
1680
+ var running = d.running === true;
1681
+ el2.classList.toggle('xfce-sb-build--running', running);
1682
+
1683
+ if (running) {
1684
+ el2.textContent = '◉ building…';
1685
+ el2.title = 'Build in progress';
1686
+ if (sep) sep.style.display = '';
1687
+ if (!_buildPolling) {
1688
+ _buildPolling = true;
1689
+ setTimeout(function poll() {
1690
+ loadBuildStatus();
1691
+ if (_buildPolling) setTimeout(poll, 4000);
1692
+ }, 4000);
1693
+ }
1694
+ return;
1695
+ }
1696
+
1697
+ _buildPolling = false;
1698
+ if (!d.lastTriggered) return;
1699
+ var dt = new Date(d.lastTriggered.replace(' ', 'T'));
1700
+ var diffM = Math.floor((Date.now() - dt) / 60000);
1444
1701
  var label = diffM < 1 ? 'built now'
1445
- : diffM < 60 ? 'built ' + diffM + 'm ago'
1702
+ : diffM < 60 ? 'built ' + diffM + 'm ago'
1446
1703
  : diffM < 1440 ? 'built ' + Math.floor(diffM / 60) + 'h ago'
1447
1704
  : 'built ' + dt.toLocaleDateString([], { month: 'short', day: 'numeric' });
1448
1705
  el2.textContent = '◉ ' + label;
@@ -1451,6 +1708,18 @@
1451
1708
  });
1452
1709
  }
1453
1710
 
1711
+ function startBuildPoll() {
1712
+ _buildPolling = true;
1713
+ var el2 = document.getElementById('xfce-sb-build');
1714
+ var sep = document.getElementById('xfce-sb-build-sep');
1715
+ if (el2) { el2.textContent = '◉ building…'; el2.classList.add('xfce-sb-build--running'); }
1716
+ if (sep) sep.style.display = '';
1717
+ setTimeout(function poll() {
1718
+ loadBuildStatus();
1719
+ if (_buildPolling) setTimeout(poll, 4000);
1720
+ }, 3000);
1721
+ }
1722
+
1454
1723
 
1455
1724
  // ── Init ──────────────────────────────────────────────────────────────
1456
1725
  function init() {
@@ -1463,6 +1732,9 @@
1463
1732
  bindKeys();
1464
1733
  initFocusMode();
1465
1734
  observeSavedFlash();
1735
+ if (localStorage.getItem('orb_zen') === '1') {
1736
+ document.documentElement.dataset.zen = '1';
1737
+ }
1466
1738
  }
1467
1739
 
1468
1740
  if (document.readyState === 'loading') {