feat(ui): phase 1 — layout, tokens, header, rail redesign
- Switch mono font from Monaspace Neon to JetBrains Mono - Grid layout 192px | 1fr | 400px (was 180/320) - Move capture bar from header to bottom of center panel - Add search input to header center - Redesign tag rail: grid items with arrow/dot/name/count - Add intent section (grab/read/fill) in cards view rail - Add --a-str token, toast component - Logo 16px 700 weight
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Redesign — Design Handoff Implementation
|
||||||
|
|
||||||
|
## Phase 1: Layout + Tokens + Header + Rail
|
||||||
|
- [ ] Update CSS tokens (add --a-str, switch mono font to JetBrains Mono)
|
||||||
|
- [ ] Fix grid dimensions (192px rail, 400px peek)
|
||||||
|
- [ ] Move capture bar from header to bottom of center panel
|
||||||
|
- [ ] Add search bar to header (centered, max-width 400px)
|
||||||
|
- [ ] Redesign tag rail: grid layout (arrow ▸ + dot + name + count)
|
||||||
|
- [ ] Add intent section (grab/read/fill) for cards view in rail
|
||||||
|
|
||||||
|
## Phase 2: Stream + Cards Views
|
||||||
|
- [ ] Stream rows: promoted entries get card-style border/radius + card-type badge
|
||||||
|
- [ ] Card rows: rich single-line with title — preview — affordance badges — tag pills — pin — use count
|
||||||
|
- [ ] Affordance detection client-side (fill, steps, decide, link, code)
|
||||||
|
- [ ] Affordance badge components
|
||||||
|
- [ ] Cards sub-header (scope label + card count + sort dropdown)
|
||||||
|
- [ ] Section labels (★ pinned, recent)
|
||||||
|
- [ ] Flash animation on copy
|
||||||
|
- [ ] Bottom capture bar styling per view (different placeholders)
|
||||||
|
|
||||||
|
## Phase 3: Peek Pane + Modes
|
||||||
|
- [ ] Idle state with keyboard shortcuts display
|
||||||
|
- [ ] Stream entry peek: eyebrow, body, tags, context, actions
|
||||||
|
- [ ] Card peek: card container with eyebrow, title, desc, meta, content sections
|
||||||
|
- [ ] Code block with syntax highlighting
|
||||||
|
- [ ] Decision section display
|
||||||
|
- [ ] Steps section display
|
||||||
|
- [ ] Link section display
|
||||||
|
- [ ] Run mode (interactive checklist with progress bar)
|
||||||
|
- [ ] Fill mode (inline slot editor with tab navigation)
|
||||||
|
- [ ] Edit mode (form fields)
|
||||||
|
- [ ] Toast notifications
|
||||||
|
|
||||||
|
## Phase 4: Polish
|
||||||
|
- [ ] Promote modal enhancement (add hint text per type)
|
||||||
|
- [ ] Remaining keyboard shortcuts (r=run, f=fill)
|
||||||
|
- [ ] Scroll behavior and edge cases
|
||||||
+149
-52
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
const PAGE_SIZE = 50;
|
const PAGE_SIZE = 50;
|
||||||
|
|
||||||
|
const INTENT_HINTS = { grab: 'scan + copy', read: 'expand + study', fill: 'templates only' };
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
view: 'stream',
|
view: 'stream',
|
||||||
entities: [],
|
entities: [],
|
||||||
@@ -24,6 +26,7 @@
|
|||||||
activeTag: null,
|
activeTag: null,
|
||||||
hasMore: false,
|
hasMore: false,
|
||||||
activeMonth: null,
|
activeMonth: null,
|
||||||
|
intent: 'grab',
|
||||||
};
|
};
|
||||||
|
|
||||||
const $ = (sel) => document.querySelector(sel);
|
const $ = (sel) => document.querySelector(sel);
|
||||||
@@ -223,32 +226,128 @@
|
|||||||
|
|
||||||
function formatDate(dateStr) {
|
function formatDate(dateStr) {
|
||||||
const d = new Date(dateStr);
|
const d = new Date(dateStr);
|
||||||
const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
const months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
|
||||||
return months[d.getMonth()] + ' ' + d.getDate();
|
return months[d.getMonth()] + ' ' + d.getDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Tag Rail ──
|
||||||
|
|
||||||
function renderTagRail() {
|
function renderTagRail() {
|
||||||
const rail = $('#tag-rail');
|
const rail = $('#tag-rail');
|
||||||
const allItem = `<div class="tag-item ${!state.activeTag ? 'active' : ''}" data-tag="">
|
const total = state.tags.reduce((s, t) => s + t.count, 0);
|
||||||
<span class="tag-name" style="font-style: italic">all</span>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
rail.innerHTML = allItem + state.tags.map(t =>
|
let html = `<div class="rail-head"><span class="rail-brand">nib</span></div>`;
|
||||||
`<div class="tag-item ${state.activeTag === t.tag ? 'active' : ''}" data-tag="${t.tag}">
|
html += '<div class="rail-scroll">';
|
||||||
<span class="tag-name">${t.tag}</span>
|
|
||||||
<span class="tag-count">${t.count}</span>
|
|
||||||
</div>`
|
|
||||||
).join('');
|
|
||||||
|
|
||||||
rail.querySelectorAll('.tag-item').forEach(el => {
|
if (state.view === 'cards') {
|
||||||
|
html += '<div class="rail-sec">';
|
||||||
|
html += '<div class="rail-lbl">intent</div>';
|
||||||
|
for (const k of ['grab', 'read', 'fill']) {
|
||||||
|
const on = state.intent === k ? ' on' : '';
|
||||||
|
const count = k === 'grab' ? state.entities.length : k === 'read' ? state.entities.filter(e => e.card_data).length : state.entities.filter(e => e.body && /\$\{.+\}/.test(e.body)).length;
|
||||||
|
html += `<button class="rail-item${on}" data-intent="${k}">`;
|
||||||
|
html += `<span class="rail-arrow">${state.intent === k ? '▸' : ''}</span>`;
|
||||||
|
html += '<span class="rail-dot"></span>';
|
||||||
|
html += `<span class="rail-name">${k}</span>`;
|
||||||
|
html += `<span class="rail-count">${count}</span>`;
|
||||||
|
if (state.intent === k) html += `<span class="rail-hint">${INTENT_HINTS[k]}</span>`;
|
||||||
|
html += '</button>';
|
||||||
|
}
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '<div class="rail-sec">';
|
||||||
|
html += '<div class="rail-lbl">tags</div>';
|
||||||
|
|
||||||
|
const allOn = !state.activeTag ? ' on' : '';
|
||||||
|
html += `<button class="rail-item${allOn}" data-tag="">`;
|
||||||
|
html += `<span class="rail-arrow">${!state.activeTag ? '▸' : ''}</span>`;
|
||||||
|
html += '<span class="rail-dot"></span>';
|
||||||
|
html += `<span class="rail-name">#all</span>`;
|
||||||
|
html += `<span class="rail-count">${total}</span>`;
|
||||||
|
html += '</button>';
|
||||||
|
|
||||||
|
for (const t of state.tags) {
|
||||||
|
const on = state.activeTag === t.tag ? ' on' : '';
|
||||||
|
html += `<button class="rail-item${on}" data-tag="${t.tag}">`;
|
||||||
|
html += `<span class="rail-arrow">${state.activeTag === t.tag ? '▸' : ''}</span>`;
|
||||||
|
html += '<span class="rail-dot"></span>';
|
||||||
|
html += `<span class="rail-name">#${t.tag}</span>`;
|
||||||
|
html += `<span class="rail-count">${t.count}</span>`;
|
||||||
|
html += '</button>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</div></div>';
|
||||||
|
rail.innerHTML = html;
|
||||||
|
|
||||||
|
rail.querySelectorAll('.rail-item[data-tag]').forEach(el => {
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
state.activeTag = el.dataset.tag || null;
|
state.activeTag = el.dataset.tag || null;
|
||||||
loadEntities();
|
loadEntities();
|
||||||
renderTagRail();
|
renderTagRail();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
rail.querySelectorAll('.rail-item[data-intent]').forEach(el => {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
state.intent = el.dataset.intent;
|
||||||
|
renderTagRail();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Capture Bar ──
|
||||||
|
|
||||||
|
function renderCaptureBar() {
|
||||||
|
const bar = $('#capture-bar');
|
||||||
|
const placeholder = state.view === 'stream'
|
||||||
|
? 'capture · - todo @time event !time reminder #tag |title'
|
||||||
|
: '|title // desc #tag ${slot} 1. step';
|
||||||
|
|
||||||
|
bar.innerHTML = `
|
||||||
|
<div class="cap-row">
|
||||||
|
<span class="cap-prompt">›</span>
|
||||||
|
<textarea id="capture-input" rows="1" placeholder="${placeholder}" spellcheck="false"></textarea>
|
||||||
|
<span class="cap-hint">⏎ save</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const input = $('#capture-input');
|
||||||
|
input.addEventListener('keydown', (ev) => {
|
||||||
|
if (ev.key === 'Enter' && !ev.shiftKey) {
|
||||||
|
ev.preventDefault();
|
||||||
|
handleCapture();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCapture() {
|
||||||
|
const input = $('#capture-input');
|
||||||
|
const val = input.value.trim();
|
||||||
|
if (!val) return;
|
||||||
|
|
||||||
|
const parsed = parseInput(val);
|
||||||
|
if (!parsed) return;
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
body: parsed.body,
|
||||||
|
glyph: parsed.glyph,
|
||||||
|
tags: parsed.tags,
|
||||||
|
};
|
||||||
|
if (parsed.title) data.title = parsed.title;
|
||||||
|
if (parsed.description) data.description = parsed.description;
|
||||||
|
if (parsed.timeAnchor) data.time_anchor = parsed.timeAnchor;
|
||||||
|
if (parsed.cardSuffix) data.card_type = parsed.cardSuffix;
|
||||||
|
|
||||||
|
await api.createEntity(data);
|
||||||
|
input.value = '';
|
||||||
|
await loadEntities();
|
||||||
|
await loadTags();
|
||||||
|
showToast('captured');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Entity List ──
|
||||||
|
|
||||||
function groupByDate(entities) {
|
function groupByDate(entities) {
|
||||||
const groups = [];
|
const groups = [];
|
||||||
let current = null;
|
let current = null;
|
||||||
@@ -308,10 +407,12 @@
|
|||||||
function renderEntityItem(e, idx) {
|
function renderEntityItem(e, idx) {
|
||||||
const glyph = displayGlyph(e);
|
const glyph = displayGlyph(e);
|
||||||
const gc = glyphClass(e);
|
const gc = glyphClass(e);
|
||||||
const selected = idx === state.selectedIndex ? 'selected' : '';
|
const selected = idx === state.selectedIndex ? ' selected' : '';
|
||||||
const tags = (e.tags || []).map(t => `<span class="entity-tag">${t}</span>`).join('');
|
const isCard = e.card_type ? ' is-card' : '';
|
||||||
|
const tags = (e.tags || []).slice(0, 2).map(t => `<span class="entity-tag">${t}</span>`).join('');
|
||||||
const time = e.time_anchor ? `<span class="entity-time">@${e.time_anchor}</span>` : '';
|
const time = e.time_anchor ? `<span class="entity-time">@${e.time_anchor}</span>` : '';
|
||||||
const useBadge = e.use_count > 0 ? `<span class="use-badge">${e.use_count}×</span>` : '';
|
const useBadge = e.use_count > 0 ? `<span class="use-badge">${e.use_count}×</span>` : '';
|
||||||
|
const cardBadge = e.card_type ? `<span class="card-badge">${e.card_type}</span>` : '';
|
||||||
|
|
||||||
let label;
|
let label;
|
||||||
if (e.title) {
|
if (e.title) {
|
||||||
@@ -321,11 +422,11 @@
|
|||||||
label = `<span class="entity-body">${escHtml(e.body)}</span>`;
|
label = `<span class="entity-body">${escHtml(e.body)}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `<div class="entity-item ${selected}" data-index="${idx}" data-id="${e.id}">
|
return `<div class="entity-item${selected}${isCard}" data-index="${idx}" data-id="${e.id}">
|
||||||
<span class="entity-glyph ${gc}">${glyph}</span>
|
<span class="entity-glyph ${gc}">${glyph}</span>
|
||||||
${label}
|
${label}
|
||||||
${time}
|
${time}
|
||||||
<span class="entity-tags">${tags}</span>
|
<span class="entity-tags">${tags}${cardBadge}</span>
|
||||||
<span class="entity-meta">${useBadge}</span>
|
<span class="entity-meta">${useBadge}</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -351,10 +452,10 @@
|
|||||||
|
|
||||||
if (e.card_type) {
|
if (e.card_type) {
|
||||||
cardContent = renderCardContent(e);
|
cardContent = renderCardContent(e);
|
||||||
actions += `<button class="action-btn" onclick="nibApp.copyEntity('${e.id}')">copy</button>`;
|
actions += `<button class="action-btn primary" onclick="nibApp.copyEntity('${e.id}')">copy</button>`;
|
||||||
actions += `<button class="action-btn" onclick="nibApp.demoteEntity('${e.id}')">demote</button>`;
|
actions += `<button class="action-btn" onclick="nibApp.demoteEntity('${e.id}')">demote</button>`;
|
||||||
} else {
|
} else {
|
||||||
actions += `<button class="action-btn primary" onclick="nibApp.showPromote('${e.id}')">promote</button>`;
|
actions += `<button class="action-btn primary" onclick="nibApp.showPromote('${e.id}')">promote →</button>`;
|
||||||
actions += `<button class="action-btn" onclick="nibApp.showAbsorb('${e.id}')">absorb</button>`;
|
actions += `<button class="action-btn" onclick="nibApp.showAbsorb('${e.id}')">absorb</button>`;
|
||||||
}
|
}
|
||||||
actions += `<button class="action-btn danger" onclick="nibApp.deleteEntity('${e.id}')">delete</button>`;
|
actions += `<button class="action-btn danger" onclick="nibApp.deleteEntity('${e.id}')">delete</button>`;
|
||||||
@@ -363,6 +464,7 @@
|
|||||||
const titleHtml = e.title ? `<h2 class="detail-title" data-id="${e.id}">${escHtml(e.title)}</h2>` : '';
|
const titleHtml = e.title ? `<h2 class="detail-title" data-id="${e.id}">${escHtml(e.title)}</h2>` : '';
|
||||||
|
|
||||||
pane.innerHTML = `
|
pane.innerHTML = `
|
||||||
|
<div class="detail-scroll">
|
||||||
<div class="detail-header">
|
<div class="detail-header">
|
||||||
<span class="detail-glyph ${gc}">${glyph}</span>
|
<span class="detail-glyph ${gc}">${glyph}</span>
|
||||||
<span class="detail-id">${shortId}</span>
|
<span class="detail-id">${shortId}</span>
|
||||||
@@ -374,6 +476,7 @@
|
|||||||
${tags ? `<div class="detail-tags">${tags}</div>` : ''}
|
${tags ? `<div class="detail-tags">${tags}</div>` : ''}
|
||||||
${cardContent}
|
${cardContent}
|
||||||
<div class="detail-actions">${actions}</div>
|
<div class="detail-actions">${actions}</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const titleEl = pane.querySelector('.detail-title');
|
const titleEl = pane.querySelector('.detail-title');
|
||||||
@@ -560,13 +663,10 @@
|
|||||||
<button class="month-nav-btn" id="month-prev">◂</button>
|
<button class="month-nav-btn" id="month-prev">◂</button>
|
||||||
<span class="month-nav-label">${label}</span>
|
<span class="month-nav-label">${label}</span>
|
||||||
<button class="month-nav-btn" id="month-next">▸</button>
|
<button class="month-nav-btn" id="month-next">▸</button>
|
||||||
${state.activeMonth ? '<button class="month-nav-clear">clear</button>' : ''}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
$('#month-prev').addEventListener('click', () => shiftMonth(-1));
|
$('#month-prev').addEventListener('click', () => shiftMonth(-1));
|
||||||
$('#month-next').addEventListener('click', () => shiftMonth(1));
|
$('#month-next').addEventListener('click', () => shiftMonth(1));
|
||||||
const clearBtn = nav.querySelector('.month-nav-clear');
|
|
||||||
if (clearBtn) clearBtn.addEventListener('click', () => { state.activeMonth = null; loadEntities(); renderMonthNav(); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shiftMonth(dir) {
|
function shiftMonth(dir) {
|
||||||
@@ -590,10 +690,25 @@
|
|||||||
function switchView(view) {
|
function switchView(view) {
|
||||||
state.view = view;
|
state.view = view;
|
||||||
state.activeMonth = null;
|
state.activeMonth = null;
|
||||||
|
state.selectedIndex = -1;
|
||||||
$$('.nav-btn').forEach(b => b.classList.toggle('active', b.dataset.view === view));
|
$$('.nav-btn').forEach(b => b.classList.toggle('active', b.dataset.view === view));
|
||||||
window.location.hash = view === 'cards' ? '/cards' : '/';
|
window.location.hash = view === 'cards' ? '/cards' : '/';
|
||||||
loadEntities();
|
loadEntities();
|
||||||
renderMonthNav();
|
renderMonthNav();
|
||||||
|
renderTagRail();
|
||||||
|
renderCaptureBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Toast ==========
|
||||||
|
|
||||||
|
function showToast(msg) {
|
||||||
|
let el = $('.toast');
|
||||||
|
if (el) el.remove();
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.className = 'toast';
|
||||||
|
el.textContent = msg;
|
||||||
|
document.body.appendChild(el);
|
||||||
|
setTimeout(() => el.remove(), 1600);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Public API (for inline handlers) ==========
|
// ========== Public API (for inline handlers) ==========
|
||||||
@@ -606,6 +721,7 @@
|
|||||||
await navigator.clipboard.writeText(e.body);
|
await navigator.clipboard.writeText(e.body);
|
||||||
await api.useEntity(id);
|
await api.useEntity(id);
|
||||||
await loadEntities();
|
await loadEntities();
|
||||||
|
showToast('copied');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('clipboard:', err);
|
console.error('clipboard:', err);
|
||||||
}
|
}
|
||||||
@@ -630,12 +746,14 @@
|
|||||||
await api.demoteEntity(id);
|
await api.demoteEntity(id);
|
||||||
await loadEntities();
|
await loadEntities();
|
||||||
await loadTags();
|
await loadTags();
|
||||||
|
showToast('demoted');
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteEntity(id) {
|
async deleteEntity(id) {
|
||||||
await api.deleteEntity(id);
|
await api.deleteEntity(id);
|
||||||
await loadEntities();
|
await loadEntities();
|
||||||
await loadTags();
|
await loadTags();
|
||||||
|
showToast('deleted');
|
||||||
},
|
},
|
||||||
|
|
||||||
async resolveTemplate(id) {
|
async resolveTemplate(id) {
|
||||||
@@ -651,6 +769,7 @@
|
|||||||
await navigator.clipboard.writeText(resolved);
|
await navigator.clipboard.writeText(resolved);
|
||||||
await api.useEntity(id);
|
await api.useEntity(id);
|
||||||
await loadEntities();
|
await loadEntities();
|
||||||
|
showToast('copied');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('clipboard:', err);
|
console.error('clipboard:', err);
|
||||||
}
|
}
|
||||||
@@ -688,6 +807,7 @@
|
|||||||
await loadTags();
|
await loadTags();
|
||||||
const idx = state.entities.findIndex(x => x.id === targetId);
|
const idx = state.entities.findIndex(x => x.id === targetId);
|
||||||
if (idx >= 0) selectEntity(idx);
|
if (idx >= 0) selectEntity(idx);
|
||||||
|
showToast('absorbed');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -706,33 +826,6 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== Capture bar ==========
|
|
||||||
|
|
||||||
$('#capture-bar').addEventListener('submit', async (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
const input = $('#capture-input');
|
|
||||||
const val = input.value.trim();
|
|
||||||
if (!val) return;
|
|
||||||
|
|
||||||
const parsed = parseInput(val);
|
|
||||||
if (!parsed) return;
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
body: parsed.body,
|
|
||||||
glyph: parsed.glyph,
|
|
||||||
tags: parsed.tags,
|
|
||||||
};
|
|
||||||
if (parsed.title) data.title = parsed.title;
|
|
||||||
if (parsed.description) data.description = parsed.description;
|
|
||||||
if (parsed.timeAnchor) data.time_anchor = parsed.timeAnchor;
|
|
||||||
if (parsed.cardSuffix) data.card_type = parsed.cardSuffix;
|
|
||||||
|
|
||||||
await api.createEntity(data);
|
|
||||||
input.value = '';
|
|
||||||
await loadEntities();
|
|
||||||
await loadTags();
|
|
||||||
});
|
|
||||||
|
|
||||||
// ========== Promote modal ==========
|
// ========== Promote modal ==========
|
||||||
|
|
||||||
$$('.type-btn').forEach(btn => {
|
$$('.type-btn').forEach(btn => {
|
||||||
@@ -745,6 +838,7 @@
|
|||||||
await api.promoteEntity(id, btn.dataset.type);
|
await api.promoteEntity(id, btn.dataset.type);
|
||||||
await loadEntities();
|
await loadEntities();
|
||||||
await loadTags();
|
await loadTags();
|
||||||
|
showToast('promoted → ' + btn.dataset.type);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -761,12 +855,11 @@
|
|||||||
// ========== Keyboard shortcuts ==========
|
// ========== Keyboard shortcuts ==========
|
||||||
|
|
||||||
let lastDTime = 0;
|
let lastDTime = 0;
|
||||||
const captureInput = $('#capture-input');
|
|
||||||
|
|
||||||
document.addEventListener('keydown', (ev) => {
|
document.addEventListener('keydown', (ev) => {
|
||||||
if (document.activeElement === captureInput ||
|
const tag = (ev.target.tagName || '').toLowerCase();
|
||||||
document.activeElement.classList.contains('detail-body-edit')) {
|
if (tag === 'input' || tag === 'textarea') {
|
||||||
if (ev.key === 'Escape') document.activeElement.blur();
|
if (ev.key === 'Escape') ev.target.blur();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -789,7 +882,7 @@
|
|||||||
break;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
captureInput.focus();
|
$('#capture-input').focus();
|
||||||
break;
|
break;
|
||||||
case 'p': {
|
case 'p': {
|
||||||
const e = state.entities[state.selectedIndex];
|
const e = state.entities[state.selectedIndex];
|
||||||
@@ -848,6 +941,9 @@
|
|||||||
}
|
}
|
||||||
$$('.nav-btn').forEach(b => b.classList.toggle('active', b.dataset.view === state.view));
|
$$('.nav-btn').forEach(b => b.classList.toggle('active', b.dataset.view === state.view));
|
||||||
loadEntities();
|
loadEntities();
|
||||||
|
renderMonthNav();
|
||||||
|
renderTagRail();
|
||||||
|
renderCaptureBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('hashchange', handleHash);
|
window.addEventListener('hashchange', handleHash);
|
||||||
@@ -884,6 +980,7 @@
|
|||||||
// ========== Init ==========
|
// ========== Init ==========
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
|
renderCaptureBar();
|
||||||
await Promise.all([loadEntities(), loadTags()]);
|
await Promise.all([loadEntities(), loadTags()]);
|
||||||
handleHash();
|
handleHash();
|
||||||
renderMonthNav();
|
renderMonthNav();
|
||||||
|
|||||||
+6
-11
@@ -6,28 +6,22 @@
|
|||||||
<title>nib</title>
|
<title>nib</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="/style.css">
|
<link rel="stylesheet" href="/style.css">
|
||||||
<style>
|
|
||||||
@font-face { font-family: 'Monaspace Neon'; font-weight: 300; src: url('https://cdn.jsdelivr.net/gh/githubnext/monaspace@v1.000/fonts/webfonts/MonaspaceNeon-Light.woff2') format('woff2'); }
|
|
||||||
@font-face { font-family: 'Monaspace Neon'; font-weight: 400; src: url('https://cdn.jsdelivr.net/gh/githubnext/monaspace@v1.000/fonts/webfonts/MonaspaceNeon-Regular.woff2') format('woff2'); }
|
|
||||||
@font-face { font-family: 'Monaspace Neon'; font-weight: 500; src: url('https://cdn.jsdelivr.net/gh/githubnext/monaspace@v1.000/fonts/webfonts/MonaspaceNeon-Medium.woff2') format('woff2'); }
|
|
||||||
@font-face { font-family: 'Monaspace Neon'; font-weight: 700; src: url('https://cdn.jsdelivr.net/gh/githubnext/monaspace@v1.000/fonts/webfonts/MonaspaceNeon-Bold.woff2') format('woff2'); }
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<header>
|
<header>
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<h1 class="logo">nib</h1>
|
<span class="logo">nib</span>
|
||||||
<nav>
|
<nav>
|
||||||
<button data-view="stream" class="nav-btn active">stream</button>
|
<button data-view="stream" class="nav-btn active">stream</button>
|
||||||
<button data-view="cards" class="nav-btn">cards</button>
|
<button data-view="cards" class="nav-btn">cards</button>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<form id="capture-bar" autocomplete="off">
|
<div class="header-search">
|
||||||
<input type="text" id="capture-input" placeholder="capture — - todo # note * event" spellcheck="false">
|
<input type="text" id="search-input" placeholder="? search #tag" spellcheck="false">
|
||||||
</form>
|
</div>
|
||||||
<button class="theme-toggle" id="theme-toggle" title="toggle theme">◑</button>
|
<button class="theme-toggle" id="theme-toggle" title="toggle theme">◑</button>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
@@ -35,6 +29,7 @@
|
|||||||
<section id="entity-panel">
|
<section id="entity-panel">
|
||||||
<div id="month-nav"></div>
|
<div id="month-nav"></div>
|
||||||
<div id="entity-list"></div>
|
<div id="entity-list"></div>
|
||||||
|
<div id="capture-bar"></div>
|
||||||
</section>
|
</section>
|
||||||
<aside id="detail-pane">
|
<aside id="detail-pane">
|
||||||
<div class="detail-empty">select an entity</div>
|
<div class="detail-empty">select an entity</div>
|
||||||
|
|||||||
+293
-135
@@ -1,4 +1,4 @@
|
|||||||
/* ── TOKENS ─────────────────────────────────────────── */
|
/* ── TOKENS (nib DS v2) ─────────────────────────────── */
|
||||||
:root {
|
:root {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
--bg: #0c0b09;
|
--bg: #0c0b09;
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
--dim: #504840;
|
--dim: #504840;
|
||||||
--accent: #c8942a;
|
--accent: #c8942a;
|
||||||
--a-bg: rgba(200,148,42,.09);
|
--a-bg: rgba(200,148,42,.09);
|
||||||
|
--a-str: rgba(200,148,42,.22);
|
||||||
--todo: #d4a84b;
|
--todo: #d4a84b;
|
||||||
--note: #6ab8b0;
|
--note: #6ab8b0;
|
||||||
--event: #6898c8;
|
--event: #6898c8;
|
||||||
@@ -18,9 +19,8 @@
|
|||||||
--ok: #7aab72;
|
--ok: #7aab72;
|
||||||
--danger: #b85858;
|
--danger: #b85858;
|
||||||
--lineage: #9878bc;
|
--lineage: #9878bc;
|
||||||
--pin: #c8942a;
|
|
||||||
--sans: 'Space Grotesk', system-ui, sans-serif;
|
--sans: 'Space Grotesk', system-ui, sans-serif;
|
||||||
--mono: 'Monaspace Neon', ui-monospace, monospace;
|
--mono: 'JetBrains Mono', ui-monospace, monospace;
|
||||||
--r1: 2px;
|
--r1: 2px;
|
||||||
--r2: 4px;
|
--r2: 4px;
|
||||||
--r3: 8px;
|
--r3: 8px;
|
||||||
@@ -40,6 +40,7 @@
|
|||||||
--dim: #a09080;
|
--dim: #a09080;
|
||||||
--accent: #8a6018;
|
--accent: #8a6018;
|
||||||
--a-bg: rgba(138,96,24,.08);
|
--a-bg: rgba(138,96,24,.08);
|
||||||
|
--a-str: rgba(138,96,24,.18);
|
||||||
--todo: #7a5c00;
|
--todo: #7a5c00;
|
||||||
--note: #1a7070;
|
--note: #1a7070;
|
||||||
--event: #245890;
|
--event: #245890;
|
||||||
@@ -47,14 +48,16 @@
|
|||||||
--ok: #2a6828;
|
--ok: #2a6828;
|
||||||
--danger: #882030;
|
--danger: #882030;
|
||||||
--lineage: #5830a0;
|
--lineage: #5830a0;
|
||||||
--pin: #8a6018;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── RESET ──────────────────────────────────────────── */
|
/* ── RESET ──────────────────────────────────────────── */
|
||||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
html, body { height: 100%; overflow: hidden; }
|
||||||
::-webkit-scrollbar { width: 3px; }
|
::-webkit-scrollbar { width: 3px; }
|
||||||
::-webkit-scrollbar-track { background: transparent; }
|
::-webkit-scrollbar-track { background: transparent; }
|
||||||
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
||||||
|
button { background: none; border: none; cursor: pointer; font: inherit; color: inherit; }
|
||||||
|
input, textarea, select { font: inherit; color: inherit; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: var(--sans);
|
font-family: var(--sans);
|
||||||
@@ -62,96 +65,85 @@ body {
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-rows: 36px 1fr;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
background: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── HEADER ─────────────────────────────────────────── */
|
/* ── HEADER ─────────────────────────────────────────── */
|
||||||
header {
|
header {
|
||||||
|
background: var(--surf);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 16px;
|
gap: 14px;
|
||||||
padding: 0 20px;
|
padding: 0 18px;
|
||||||
height: 36px;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
background: var(--surf);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-left {
|
.header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 16px;
|
gap: 12px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 15px;
|
font-size: 16px;
|
||||||
font-weight: 300;
|
font-weight: 700;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
letter-spacing: .3em;
|
letter-spacing: -.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav { display: flex; gap: 2px; }
|
||||||
display: flex;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn {
|
.nav-btn {
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--dim);
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: var(--r1);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 11px;
|
|
||||||
font-family: var(--sans);
|
font-family: var(--sans);
|
||||||
|
font-size: 11px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
color: var(--dim);
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: var(--r1);
|
||||||
transition: color var(--t-fast), background var(--t-fast);
|
transition: color var(--t-fast), background var(--t-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-btn:hover { color: var(--muted); }
|
.nav-btn:hover { color: var(--muted); }
|
||||||
.nav-btn.active { color: var(--accent); background: var(--a-bg); }
|
.nav-btn.active { color: var(--accent); background: var(--a-bg); }
|
||||||
|
|
||||||
#capture-bar {
|
.header-search {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-width: 600px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#capture-input {
|
#search-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
color: var(--text);
|
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: var(--r2);
|
border-radius: var(--r2);
|
||||||
|
padding: 4px 10px;
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
color: var(--text);
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color var(--t-fast);
|
transition: border-color var(--t-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
#capture-input:hover { border-color: var(--muted); }
|
#search-input:hover { border-color: var(--muted); }
|
||||||
#capture-input:focus { border-color: var(--accent); }
|
#search-input:focus { border-color: var(--accent); }
|
||||||
#capture-input::placeholder { color: var(--dim); }
|
#search-input::placeholder { color: var(--dim); }
|
||||||
|
|
||||||
.theme-toggle {
|
.theme-toggle {
|
||||||
background: none;
|
margin-left: auto;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--r1);
|
border-radius: var(--r1);
|
||||||
color: var(--dim);
|
color: var(--dim);
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
cursor: pointer;
|
|
||||||
flex-shrink: 0;
|
|
||||||
transition: color var(--t-fast), border-color var(--t-fast);
|
transition: color var(--t-fast), border-color var(--t-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,67 +152,132 @@ nav {
|
|||||||
/* ── MAIN LAYOUT ────────────────────────────────────── */
|
/* ── MAIN LAYOUT ────────────────────────────────────── */
|
||||||
main {
|
main {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 180px 1fr 320px;
|
grid-template-columns: 192px 1fr 400px;
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── TAG RAIL ───────────────────────────────────────── */
|
/* ── TAG RAIL ───────────────────────────────────────── */
|
||||||
#tag-rail {
|
#tag-rail {
|
||||||
border-right: 1px solid var(--border);
|
|
||||||
padding: 12px 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
background: var(--surf);
|
background: var(--surf);
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-item {
|
.rail-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
align-items: baseline;
|
||||||
|
gap: 5px;
|
||||||
|
padding: .85rem 1rem .75rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rail-brand {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rail-scroll {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rail-sec { padding: 4px 0 10px; }
|
||||||
|
|
||||||
|
.rail-lbl {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 9px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .16em;
|
||||||
|
color: var(--dim);
|
||||||
|
padding: 4px 16px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rail-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 8px 8px 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
width: 100%;
|
||||||
padding: 4px 16px;
|
padding: 4px 16px;
|
||||||
cursor: pointer;
|
text-align: left;
|
||||||
|
font-family: var(--mono);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
transition: color var(--t-fast), background var(--t-fast);
|
transition: color var(--t-fast), background var(--t-fast);
|
||||||
|
border-left: 2px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-item:hover { background: var(--raised); color: var(--text); }
|
.rail-item:hover { color: var(--text); background: var(--raised); }
|
||||||
.tag-item.active { color: var(--accent); background: var(--a-bg); }
|
.rail-item.on { color: var(--accent); background: var(--a-bg); border-left-color: var(--accent); }
|
||||||
|
|
||||||
.tag-name { font-family: var(--mono); font-size: 11px; }
|
.rail-arrow {
|
||||||
.tag-name::before { content: '#'; color: var(--dim); }
|
font-size: 9px;
|
||||||
.tag-count {
|
color: var(--accent);
|
||||||
font-family: var(--mono);
|
width: 8px;
|
||||||
font-size: 10px;
|
}
|
||||||
|
|
||||||
|
.rail-item:not(.on) .rail-arrow { color: transparent; }
|
||||||
|
|
||||||
|
.rail-dot {
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--muted);
|
||||||
|
opacity: .4;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rail-item.on .rail-dot { background: var(--accent); opacity: 1; }
|
||||||
|
|
||||||
|
.rail-name { font-weight: 500; }
|
||||||
|
|
||||||
|
.rail-count {
|
||||||
|
font-size: 9px;
|
||||||
color: var(--dim);
|
color: var(--dim);
|
||||||
min-width: 20px;
|
font-variant-numeric: tabular-nums;
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── ENTITY PANEL ───────────────────────────────────── */
|
.rail-item.on .rail-count { color: var(--accent); opacity: .7; }
|
||||||
|
|
||||||
|
.rail-hint {
|
||||||
|
grid-column: 2 / 5;
|
||||||
|
font-size: 9px;
|
||||||
|
color: var(--dim);
|
||||||
|
font-family: var(--mono);
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rail-item:not(.on) .rail-hint { display: none; }
|
||||||
|
|
||||||
|
/* ── CENTER PANEL ───────────────────────────────────── */
|
||||||
#entity-panel {
|
#entity-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
#month-nav {
|
#month-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 6px 20px;
|
padding: 5px 18px;
|
||||||
border-bottom: 1px solid var(--soft);
|
border-bottom: 1px solid var(--soft);
|
||||||
|
background: var(--surf);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#month-nav:empty { display: none; }
|
#month-nav:empty { display: none; }
|
||||||
|
|
||||||
.month-nav-btn {
|
.month-nav-btn {
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--dim);
|
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 11px;
|
font-size: 10px;
|
||||||
cursor: pointer;
|
color: var(--dim);
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: var(--r1);
|
border-radius: var(--r1);
|
||||||
transition: color var(--t-fast), background var(--t-fast);
|
transition: color var(--t-fast), background var(--t-fast);
|
||||||
@@ -231,38 +288,25 @@ main {
|
|||||||
.month-nav-label {
|
.month-nav-label {
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--text);
|
color: var(--muted);
|
||||||
min-width: 80px;
|
flex: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.month-nav-clear {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--dim);
|
|
||||||
font-family: var(--mono);
|
|
||||||
font-size: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-left: auto;
|
|
||||||
transition: color var(--t-fast);
|
|
||||||
}
|
|
||||||
|
|
||||||
.month-nav-clear:hover { color: var(--text); }
|
|
||||||
|
|
||||||
/* ── ENTITY LIST ────────────────────────────────────── */
|
/* ── ENTITY LIST ────────────────────────────────────── */
|
||||||
#entity-list {
|
#entity-list {
|
||||||
overflow-y: auto;
|
|
||||||
padding: 4px 0;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 4px 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-header {
|
.date-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: .6rem;
|
gap: 8px;
|
||||||
padding: 8px 20px 4px;
|
padding: 10px 20px 5px;
|
||||||
font-size: 10px;
|
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
|
font-size: 9px;
|
||||||
color: var(--dim);
|
color: var(--dim);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: .2em;
|
letter-spacing: .2em;
|
||||||
@@ -279,35 +323,44 @@ main {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 6px 20px;
|
padding: 5px 16px 5px 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background var(--t-fast);
|
|
||||||
border-left: 2px solid transparent;
|
border-left: 2px solid transparent;
|
||||||
|
min-height: 32px;
|
||||||
|
transition: background var(--t-fast), border-left-color var(--t-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-item:hover { background: var(--raised); }
|
.entity-item:hover { background: var(--surf); }
|
||||||
.entity-item.selected {
|
.entity-item.selected { background: var(--surf); border-left-color: var(--accent); }
|
||||||
|
|
||||||
|
.entity-item.is-card {
|
||||||
background: var(--surf);
|
background: var(--surf);
|
||||||
border-left-color: var(--accent);
|
margin: 2px 10px;
|
||||||
|
border-radius: var(--r2);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-left-width: 1px;
|
||||||
|
padding: 7px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entity-item.is-card:hover { border-color: var(--muted); }
|
||||||
|
.entity-item.is-card.selected { border-color: var(--accent); background: var(--a-bg); }
|
||||||
|
|
||||||
.entity-glyph {
|
.entity-glyph {
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
width: 14px;
|
width: 14px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.glyph-note { color: var(--dim); }
|
.glyph-note { color: var(--muted); }
|
||||||
.glyph-todo { color: var(--todo); }
|
.glyph-todo { color: var(--todo); }
|
||||||
.glyph-event { color: var(--event); }
|
.glyph-event { color: var(--event); }
|
||||||
.glyph-snippet { color: var(--accent); }
|
.glyph-snippet { color: var(--accent); }
|
||||||
.glyph-template { color: var(--lineage); }
|
.glyph-template { color: var(--lineage); }
|
||||||
.glyph-checklist { color: var(--remind); }
|
.glyph-checklist { color: var(--remind); }
|
||||||
.glyph-decision { color: var(--note); }
|
.glyph-decision { color: var(--note); }
|
||||||
.glyph-link { color: var(--danger); }
|
.glyph-link { color: var(--event); }
|
||||||
|
|
||||||
.entity-title {
|
.entity-title {
|
||||||
font-family: var(--sans);
|
font-family: var(--sans);
|
||||||
@@ -355,7 +408,7 @@ main {
|
|||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
padding: 1px 6px;
|
padding: 1px 5px;
|
||||||
border-radius: var(--r1);
|
border-radius: var(--r1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,17 +421,86 @@ main {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-badge {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 9px;
|
||||||
|
color: var(--accent);
|
||||||
|
border: 1px solid rgba(200,148,42,.4);
|
||||||
|
background: var(--a-bg);
|
||||||
|
padding: 1px 5px;
|
||||||
|
border-radius: var(--r1);
|
||||||
|
}
|
||||||
|
|
||||||
.use-badge {
|
.use-badge {
|
||||||
color: var(--todo);
|
color: var(--todo);
|
||||||
font-size: 10px;
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── CAPTURE BAR ────────────────────────────────────── */
|
||||||
|
#capture-bar {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
background: var(--surf);
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: border-top-color var(--t-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
#capture-bar:focus-within { border-top-color: rgba(200,148,42,.35); }
|
||||||
|
|
||||||
|
.cap-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 0 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cap-prompt {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--accent);
|
||||||
|
opacity: .4;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: opacity var(--t-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
#capture-bar:focus-within .cap-prompt { opacity: 1; }
|
||||||
|
|
||||||
|
#capture-input {
|
||||||
|
flex: 1;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
padding: 8px 10px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1.5;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#capture-input::placeholder { color: var(--dim); }
|
||||||
|
|
||||||
|
.cap-hint {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 9px;
|
||||||
|
color: var(--dim);
|
||||||
|
padding-bottom: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
letter-spacing: .04em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── DETAIL PANE ────────────────────────────────────── */
|
/* ── DETAIL PANE ────────────────────────────────────── */
|
||||||
#detail-pane {
|
#detail-pane {
|
||||||
border-left: 1px solid var(--border);
|
|
||||||
padding: 20px;
|
|
||||||
overflow-y: auto;
|
|
||||||
background: var(--surf);
|
background: var(--surf);
|
||||||
|
border-left: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-scroll {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-empty {
|
.detail-empty {
|
||||||
@@ -420,8 +542,8 @@ main {
|
|||||||
|
|
||||||
.detail-title {
|
.detail-title {
|
||||||
font-family: var(--sans);
|
font-family: var(--sans);
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
@@ -449,7 +571,7 @@ main {
|
|||||||
.detail-body {
|
.detail-body {
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.7;
|
line-height: 1.72;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
@@ -468,7 +590,7 @@ main {
|
|||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.7;
|
line-height: 1.72;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
@@ -490,40 +612,40 @@ main {
|
|||||||
|
|
||||||
.detail-tag {
|
.detail-tag {
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 11px;
|
font-size: 10px;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
border: 1px solid currentColor;
|
border: 1px solid rgba(200,148,42,.35);
|
||||||
border-color: color-mix(in srgb, var(--accent) 38%, transparent);
|
|
||||||
background: var(--a-bg);
|
background: var(--a-bg);
|
||||||
padding: 2px 8px;
|
padding: 2px 7px;
|
||||||
border-radius: var(--r1);
|
border-radius: var(--r1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-actions {
|
.detail-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 5px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
border-top: 1px solid var(--soft);
|
||||||
|
padding-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
background: none;
|
font-family: var(--sans);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
padding: 4px 12px;
|
padding: 4px 11px;
|
||||||
border-radius: var(--r1);
|
border-radius: var(--r1);
|
||||||
cursor: pointer;
|
|
||||||
font-size: 11px;
|
|
||||||
font-family: var(--mono);
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 5px;
|
||||||
transition: color var(--t-fast), border-color var(--t-fast);
|
transition: color var(--t-fast), border-color var(--t-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn:hover { border-color: var(--accent); color: var(--accent); }
|
.action-btn:hover { color: var(--accent); border-color: var(--accent); }
|
||||||
.action-btn.primary { border-color: var(--accent); color: var(--accent); background: var(--a-bg); }
|
.action-btn.primary { color: var(--accent); border-color: var(--accent); background: var(--a-bg); }
|
||||||
.action-btn.danger { color: var(--danger); border-color: var(--danger); }
|
.action-btn.danger { color: var(--danger); border-color: rgba(184,88,88,.4); }
|
||||||
.action-btn.danger:hover { background: color-mix(in srgb, var(--danger) 8%, transparent); }
|
.action-btn.danger:hover { border-color: var(--danger); }
|
||||||
|
|
||||||
/* ── TEMPLATE SLOTS ─────────────────────────────────── */
|
/* ── TEMPLATE SLOTS ─────────────────────────────────── */
|
||||||
.slot-form { margin: 16px 0; }
|
.slot-form { margin: 16px 0; }
|
||||||
@@ -591,7 +713,7 @@ main {
|
|||||||
.modal-backdrop {
|
.modal-backdrop {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: rgba(0,0,0,.65);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,17 +725,28 @@ main {
|
|||||||
background: var(--surf);
|
background: var(--surf);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--r3);
|
border-radius: var(--r3);
|
||||||
padding: 24px;
|
padding: 22px;
|
||||||
z-index: 101;
|
z-index: 101;
|
||||||
min-width: 320px;
|
min-width: 300px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content h3 {
|
.modal-content h3 {
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
color: var(--text);
|
|
||||||
margin-bottom: 16px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
color: var(--text);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-sub {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-bottom: 14px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-picker {
|
.type-picker {
|
||||||
@@ -625,34 +758,38 @@ main {
|
|||||||
.type-btn {
|
.type-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
padding: 8px 14px;
|
padding: 8px 12px;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
color: var(--text);
|
|
||||||
border-radius: var(--r2);
|
border-radius: var(--r2);
|
||||||
cursor: pointer;
|
text-align: left;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
|
color: var(--text);
|
||||||
transition: border-color var(--t-fast), background var(--t-fast);
|
transition: border-color var(--t-fast), background var(--t-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-btn:hover { border-color: var(--accent); background: var(--raised); }
|
.type-btn:hover { border-color: var(--accent); background: var(--raised); }
|
||||||
.type-btn.suggested { border-color: var(--accent); background: var(--a-bg); }
|
.type-btn.suggested { border-color: var(--accent); background: var(--a-bg); }
|
||||||
|
|
||||||
.type-glyph { font-size: 14px; width: 20px; text-align: center; }
|
.type-glyph { font-size: 13px; width: 16px; flex-shrink: 0; }
|
||||||
|
|
||||||
|
.type-hint {
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
.modal-close {
|
.modal-close {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
padding: 6px;
|
padding: 5px;
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--dim);
|
color: var(--dim);
|
||||||
font-size: 10px;
|
font-size: 9px;
|
||||||
cursor: pointer;
|
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
|
text-align: center;
|
||||||
transition: color var(--t-fast);
|
transition: color var(--t-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,7 +807,6 @@ main {
|
|||||||
color: var(--dim);
|
color: var(--dim);
|
||||||
padding: 4px 20px;
|
padding: 4px 20px;
|
||||||
border-radius: var(--r1);
|
border-radius: var(--r1);
|
||||||
cursor: pointer;
|
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
transition: color var(--t-fast), border-color var(--t-fast);
|
transition: color var(--t-fast), border-color var(--t-fast);
|
||||||
@@ -689,7 +825,6 @@ main {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
cursor: pointer;
|
|
||||||
border-radius: var(--r2);
|
border-radius: var(--r2);
|
||||||
transition: background var(--t-fast);
|
transition: background var(--t-fast);
|
||||||
}
|
}
|
||||||
@@ -703,6 +838,29 @@ main {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── TOAST ──────────────────────────────────────────── */
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: var(--raised);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--r2);
|
||||||
|
padding: 6px 16px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text);
|
||||||
|
z-index: 300;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,.4);
|
||||||
|
animation: toast-in .16s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes toast-in {
|
||||||
|
from { opacity: 0; transform: translate(-50%, 6px); }
|
||||||
|
to { opacity: 1; transform: translate(-50%, 0); }
|
||||||
|
}
|
||||||
|
|
||||||
/* ── RESPONSIVE ─────────────────────────────────────── */
|
/* ── RESPONSIVE ─────────────────────────────────────── */
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
main { grid-template-columns: 1fr; }
|
main { grid-template-columns: 1fr; }
|
||||||
|
|||||||
Reference in New Issue
Block a user