chore: cursor-blink on empty-state placeholders
Adds the cursor-blink class to "no X" / "select a Y" placeholders across core (calendar popup, board preview, search preview, detail split, notifs) and plugins (chat, email, gitea, slideshow). Visual consistency with the rest of the pixel-font UI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
2aec2d8f44
commit
a29708df85
11 files changed
+16
-16
No files matched your search
+2
-2
@@ -1229,7 +1229,7 @@ if (str_starts_with($route, '/api/')) {
|
|||||||
</div>
|
</div>
|
||||||
<div id="board-preview-empty" class="flex-1 flex flex-col items-center justify-center text-t-dim">
|
<div id="board-preview-empty" class="flex-1 flex flex-col items-center justify-center text-t-dim">
|
||||||
<i class="ph ph-article text-3xl mb-2"></i>
|
<i class="ph ph-article text-3xl mb-2"></i>
|
||||||
<p class="font-pixel text-[10px]">click a card to preview</p>
|
<p class="font-pixel text-[10px] cursor-blink">click a card to preview</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="board-preview-content" class="hidden flex-1 flex flex-col min-h-0">
|
<div id="board-preview-content" class="hidden flex-1 flex flex-col min-h-0">
|
||||||
<div id="board-preview-body" class="flex-1 overflow-y-auto p-5 prose text-sm text-t-text"></div>
|
<div id="board-preview-body" class="flex-1 overflow-y-auto p-5 prose text-sm text-t-text"></div>
|
||||||
@@ -1826,7 +1826,7 @@ if (str_starts_with($route, '/api/')) {
|
|||||||
<div class="flex flex-1 min-h-0">
|
<div class="flex flex-1 min-h-0">
|
||||||
<div id="search-results" class="overflow-y-auto w-2/5 min-w-[220px] border-r border-t-border"></div>
|
<div id="search-results" class="overflow-y-auto w-2/5 min-w-[220px] border-r border-t-border"></div>
|
||||||
<div class="flex-1 overflow-y-auto p-5">
|
<div class="flex-1 overflow-y-auto p-5">
|
||||||
<div id="search-preview-empty" class="flex flex-col items-center justify-center h-full text-t-dim"><i class="ph ph-article text-3xl mb-2"></i><p class="font-pixel text-[10px]">select a card</p></div>
|
<div id="search-preview-empty" class="flex flex-col items-center justify-center h-full text-t-dim"><i class="ph ph-article text-3xl mb-2"></i><p class="font-pixel text-[10px] cursor-blink">select a card</p></div>
|
||||||
<div id="search-preview-content" class="hidden">
|
<div id="search-preview-content" class="hidden">
|
||||||
<div class="card-bar w-16 mb-4 rounded-sm" id="search-preview-color"></div>
|
<div class="card-bar w-16 mb-4 rounded-sm" id="search-preview-color"></div>
|
||||||
<h2 id="search-preview-title" class="font-pixel text-t-bright text-sm mb-4 leading-relaxed"></h2>
|
<h2 id="search-preview-title" class="font-pixel text-t-bright text-sm mb-4 leading-relaxed"></h2>
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ function openDayPopup(ds, cellEl) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
} else {
|
} else {
|
||||||
list.innerHTML = '<div style="padding:16px 12px;text-align:center;font-family:var(--t-font-pixel);font-size:9px;color:var(--t-dim);">no cards</div>';
|
list.innerHTML = '<div class="cursor-blink" style="padding:16px 12px;text-align:center;font-family:var(--t-font-pixel);font-size:9px;color:var(--t-dim);">no cards</div>';
|
||||||
}
|
}
|
||||||
dayPopup.appendChild(list);
|
dayPopup.appendChild(list);
|
||||||
|
|
||||||
|
|||||||
@@ -807,7 +807,7 @@ function renderSplitGroup(cards, label) {
|
|||||||
|
|
||||||
function renderSplitPicks(cards, label) {
|
function renderSplitPicks(cards, label) {
|
||||||
if (!cards.length) {
|
if (!cards.length) {
|
||||||
splitPicks.innerHTML = `<div class="px-3 py-4 text-center text-t-dim font-pixel text-[9px]">no matches</div>`;
|
splitPicks.innerHTML = `<div class="px-3 py-4 text-center text-t-dim font-pixel text-[9px] cursor-blink">no matches</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
splitPicks.innerHTML = renderSplitGroup(cards, label);
|
splitPicks.innerHTML = renderSplitGroup(cards, label);
|
||||||
@@ -819,7 +819,7 @@ function renderSplitPicks(cards, label) {
|
|||||||
function renderSplitGroups(groups) {
|
function renderSplitGroups(groups) {
|
||||||
const html = groups.filter(g => g.cards.length).map(g => renderSplitGroup(g.cards, g.label)).join('');
|
const html = groups.filter(g => g.cards.length).map(g => renderSplitGroup(g.cards, g.label)).join('');
|
||||||
if (!html) {
|
if (!html) {
|
||||||
splitPicks.innerHTML = `<div class="px-3 py-4 text-center text-t-dim font-pixel text-[9px]">no matches</div>`;
|
splitPicks.innerHTML = `<div class="px-3 py-4 text-center text-t-dim font-pixel text-[9px] cursor-blink">no matches</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
splitPicks.innerHTML = html;
|
splitPicks.innerHTML = html;
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ function renderStream() {
|
|||||||
const all = getAllItems();
|
const all = getAllItems();
|
||||||
|
|
||||||
if (!all.length) {
|
if (!all.length) {
|
||||||
stream.innerHTML = '<div class="text-xs text-t-dim font-pixel text-center py-4">no notifications</div>';
|
stream.innerHTML = '<div class="text-xs text-t-dim font-pixel text-center py-4 cursor-blink">no notifications</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ function updatePreview() {
|
|||||||
|
|
||||||
function renderHome() {
|
function renderHome() {
|
||||||
const recent = [...getPool()].sort((a, b) => (b.updated_at || '').localeCompare(a.updated_at || '')).slice(0, 6);
|
const recent = [...getPool()].sort((a, b) => (b.updated_at || '').localeCompare(a.updated_at || '')).slice(0, 6);
|
||||||
if (!recent.length) { results.innerHTML = `<div class="px-4 py-8 text-center text-t-dim text-xs font-pixel">no cards yet</div>`; return; }
|
if (!recent.length) { results.innerHTML = `<div class="px-4 py-8 text-center text-t-dim text-xs font-pixel cursor-blink">no cards yet</div>`; return; }
|
||||||
results.innerHTML = `<div class="px-4 pt-3 pb-1"><span class="font-pixel text-[9px] text-t-dim uppercase tracking-wider">recent</span></div>` +
|
results.innerHTML = `<div class="px-4 pt-3 pb-1"><span class="font-pixel text-[9px] text-t-dim uppercase tracking-wider">recent</span></div>` +
|
||||||
recent.map((c, i) => hitHtml(c, i, null)).join('');
|
recent.map((c, i) => hitHtml(c, i, null)).join('');
|
||||||
hits = recent; active = recent.length ? 0 : -1;
|
hits = recent; active = recent.length ? 0 : -1;
|
||||||
@@ -110,7 +110,7 @@ function search(query) {
|
|||||||
// Tag-only search (no words)
|
// Tag-only search (no words)
|
||||||
if (!words.length) {
|
if (!words.length) {
|
||||||
hits = pool; active = pool.length ? 0 : -1;
|
hits = pool; active = pool.length ? 0 : -1;
|
||||||
if (!pool.length) { results.innerHTML = `<div class="px-4 py-8 text-center"><i class="ph ph-hash text-2xl text-t-dim mb-2 block"></i><p class="text-xs text-t-dim font-pixel">no cards with ${tagFilters.map(t => '#' + t).join(' + ')}</p></div>`; footer.classList.add('hidden'); clearPreview(); return; }
|
if (!pool.length) { results.innerHTML = `<div class="px-4 py-8 text-center"><i class="ph ph-hash text-2xl text-t-dim mb-2 block"></i><p class="text-xs text-t-dim font-pixel cursor-blink">no cards with ${tagFilters.map(t => '#' + t).join(' + ')}</p></div>`; footer.classList.add('hidden'); clearPreview(); return; }
|
||||||
results.innerHTML = `<div class="px-4 pt-3 pb-1"><span class="font-pixel text-[9px] text-t-dim uppercase tracking-wider">${pool.length} tagged</span></div>` +
|
results.innerHTML = `<div class="px-4 pt-3 pb-1"><span class="font-pixel text-[9px] text-t-dim uppercase tracking-wider">${pool.length} tagged</span></div>` +
|
||||||
pool.map((c, i) => hitHtml(c, i, null)).join('');
|
pool.map((c, i) => hitHtml(c, i, null)).join('');
|
||||||
footer.classList.remove('hidden'); bindHits(); updateActive(); updatePreview(); return;
|
footer.classList.remove('hidden'); bindHits(); updateActive(); updatePreview(); return;
|
||||||
@@ -141,7 +141,7 @@ function search(query) {
|
|||||||
}).filter(Boolean).sort((a, b) => b.score - a.score);
|
}).filter(Boolean).sort((a, b) => b.score - a.score);
|
||||||
|
|
||||||
hits = scored.map(s => s.card); active = scored.length ? 0 : -1;
|
hits = scored.map(s => s.card); active = scored.length ? 0 : -1;
|
||||||
if (!scored.length) { results.innerHTML = `<div class="px-4 py-8 text-center"><i class="ph ph-magnifying-glass text-2xl text-t-dim mb-2 block"></i><p class="text-xs text-t-dim font-pixel">no results for "${esc(query)}"</p></div>`; footer.classList.add('hidden'); clearPreview(); return; }
|
if (!scored.length) { results.innerHTML = `<div class="px-4 py-8 text-center"><i class="ph ph-magnifying-glass text-2xl text-t-dim mb-2 block"></i><p class="text-xs text-t-dim font-pixel cursor-blink">no results for "${esc(query)}"</p></div>`; footer.classList.add('hidden'); clearPreview(); return; }
|
||||||
results.innerHTML = `<div class="px-4 pt-3 pb-1"><span class="font-pixel text-[9px] text-t-dim uppercase tracking-wider">${scored.length} result${scored.length > 1 ? 's' : ''}</span></div>` +
|
results.innerHTML = `<div class="px-4 pt-3 pb-1"><span class="font-pixel text-[9px] text-t-dim uppercase tracking-wider">${scored.length} result${scored.length > 1 ? 's' : ''}</span></div>` +
|
||||||
scored.map((s, i) => hitHtml(s.card, i, words)).join('');
|
scored.map((s, i) => hitHtml(s.card, i, words)).join('');
|
||||||
footer.classList.remove('hidden'); bindHits(); updateActive(); updatePreview();
|
footer.classList.remove('hidden'); bindHits(); updateActive(); updatePreview();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<div id="chat-list" class="chat-list"></div>
|
<div id="chat-list" class="chat-list"></div>
|
||||||
<div id="chat-empty" class="hidden chat-empty">
|
<div id="chat-empty" class="hidden chat-empty">
|
||||||
<i class="ph ph-chats text-2xl text-t-dim mb-2"></i>
|
<i class="ph ph-chats text-2xl text-t-dim mb-2"></i>
|
||||||
<p class="font-pixel text-[10px] text-t-dim">no conversations yet</p>
|
<p class="font-pixel text-[10px] text-t-dim cursor-blink">no conversations yet</p>
|
||||||
<button id="chat-empty-start" class="mt-3 px-2 py-1 text-[10px] font-pixel border border-t-accent text-t-accent hover:bg-t-accent/10 transition-colors">Start chat</button>
|
<button id="chat-empty-start" class="mt-3 px-2 py-1 text-[10px] font-pixel border border-t-accent text-t-accent hover:bg-t-accent/10 transition-colors">Start chat</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ async function openPickView() {
|
|||||||
const self = recipientCache.self;
|
const self = recipientCache.self;
|
||||||
const others = tenants.filter(t => t.username !== self);
|
const others = tenants.filter(t => t.username !== self);
|
||||||
if (!others.length) {
|
if (!others.length) {
|
||||||
list.innerHTML = '<div class="px-3 py-3 text-[10px] text-t-dim font-pixel">no other users on the relay</div>';
|
list.innerHTML = '<div class="px-3 py-3 text-[10px] text-t-dim font-pixel cursor-blink">no other users on the relay</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
list.innerHTML = others.map(t => `
|
list.innerHTML = others.map(t => `
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
<div id="email-body-empty" class="flex items-center justify-center flex-1 text-t-dim text-xs font-pixel">
|
<div id="email-body-empty" class="flex items-center justify-center flex-1 text-t-dim text-xs font-pixel">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<i class="ph ph-envelope-open text-3xl mb-2 block"></i>
|
<i class="ph ph-envelope-open text-3xl mb-2 block"></i>
|
||||||
select a message
|
<span class="cursor-blink">select a message</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="email-body" class="hidden flex flex-col flex-1 min-h-0">
|
<div id="email-body" class="hidden flex flex-col flex-1 min-h-0">
|
||||||
|
|||||||
@@ -441,7 +441,7 @@ function gotoNextPage() {
|
|||||||
function renderMessageList() {
|
function renderMessageList() {
|
||||||
const list = $('email-list');
|
const list = $('email-list');
|
||||||
if (!currentMessages.length) {
|
if (!currentMessages.length) {
|
||||||
list.innerHTML = '<div class="p-4 text-xs text-t-dim italic">no messages</div>';
|
list.innerHTML = '<div class="p-4 text-xs text-t-dim italic cursor-blink">no messages</div>';
|
||||||
renderSelectionBar();
|
renderSelectionBar();
|
||||||
renderSpamToolbar();
|
renderSpamToolbar();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
<div id="gitea-detail-empty" class="flex items-center justify-center flex-1 text-t-dim text-xs font-pixel">
|
<div id="gitea-detail-empty" class="flex items-center justify-center flex-1 text-t-dim text-xs font-pixel">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<i class="ph ph-list-bullets text-3xl mb-2 block"></i>
|
<i class="ph ph-list-bullets text-3xl mb-2 block"></i>
|
||||||
select an issue
|
<span class="cursor-blink">select an issue</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- ── Create issue form ── -->
|
<!-- ── Create issue form ── -->
|
||||||
|
|||||||
@@ -640,7 +640,7 @@ async function refreshGalleryPicker() {
|
|||||||
if (!images.length) {
|
if (!images.length) {
|
||||||
gp.grid.innerHTML = `<div class="col-span-full text-center py-16" style="grid-column:1/-1;">
|
gp.grid.innerHTML = `<div class="col-span-full text-center py-16" style="grid-column:1/-1;">
|
||||||
<i class="ph ph-image text-4xl text-t-dim mb-2 block"></i>
|
<i class="ph ph-image text-4xl text-t-dim mb-2 block"></i>
|
||||||
<p class="font-pixel text-[10px] text-t-dim">no images yet — upload above</p>
|
<p class="font-pixel text-[10px] text-t-dim cursor-blink">no images yet — upload above</p>
|
||||||
</div>`;
|
</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -705,7 +705,7 @@ function renderPickerList(query) {
|
|||||||
picker.list.innerHTML = filtered.map(c => {
|
picker.list.innerHTML = filtered.map(c => {
|
||||||
const col = c.color ? `background:${c.color};` : '';
|
const col = c.color ? `background:${c.color};` : '';
|
||||||
return `<button class="slideshow-pick-row" data-pick-target="card:${escHtml(c.id)}"><span style="display:inline-block;width:8px;height:8px;${col}"></span><span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escHtml(c.title || '(untitled)')}</span></button>`;
|
return `<button class="slideshow-pick-row" data-pick-target="card:${escHtml(c.id)}"><span style="display:inline-block;width:8px;height:8px;${col}"></span><span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escHtml(c.title || '(untitled)')}</span></button>`;
|
||||||
}).join('') || `<div class="font-pixel text-[10px] text-t-dim px-2 py-2">no matches</div>`;
|
}).join('') || `<div class="font-pixel text-[10px] text-t-dim px-2 py-2 cursor-blink">no matches</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
picker.search?.addEventListener('input', () => renderPickerList(picker.search.value));
|
picker.search?.addEventListener('input', () => renderPickerList(picker.search.value));
|
||||||
|
|||||||
Reference in new issue
Block a user