feat: UI redesign, capture grammar, demo command #14
@@ -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
|
||||
+159
-62
@@ -16,6 +16,8 @@
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
|
||||
const INTENT_HINTS = { grab: 'scan + copy', read: 'expand + study', fill: 'templates only' };
|
||||
|
||||
const state = {
|
||||
view: 'stream',
|
||||
entities: [],
|
||||
@@ -24,6 +26,7 @@
|
||||
activeTag: null,
|
||||
hasMore: false,
|
||||
activeMonth: null,
|
||||
intent: 'grab',
|
||||
};
|
||||
|
||||
const $ = (sel) => document.querySelector(sel);
|
||||
@@ -223,32 +226,128 @@
|
||||
|
||||
function formatDate(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();
|
||||
}
|
||||
|
||||
// ── Tag Rail ──
|
||||
|
||||
function renderTagRail() {
|
||||
const rail = $('#tag-rail');
|
||||
const allItem = `<div class="tag-item ${!state.activeTag ? 'active' : ''}" data-tag="">
|
||||
<span class="tag-name" style="font-style: italic">all</span>
|
||||
</div>`;
|
||||
const total = state.tags.reduce((s, t) => s + t.count, 0);
|
||||
|
||||
rail.innerHTML = allItem + state.tags.map(t =>
|
||||
`<div class="tag-item ${state.activeTag === t.tag ? 'active' : ''}" data-tag="${t.tag}">
|
||||
<span class="tag-name">${t.tag}</span>
|
||||
<span class="tag-count">${t.count}</span>
|
||||
</div>`
|
||||
).join('');
|
||||
let html = `<div class="rail-head"><span class="rail-brand">nib</span></div>`;
|
||||
html += '<div class="rail-scroll">';
|
||||
|
||||
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', () => {
|
||||
state.activeTag = el.dataset.tag || null;
|
||||
loadEntities();
|
||||
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) {
|
||||
const groups = [];
|
||||
let current = null;
|
||||
@@ -308,10 +407,12 @@
|
||||
function renderEntityItem(e, idx) {
|
||||
const glyph = displayGlyph(e);
|
||||
const gc = glyphClass(e);
|
||||
const selected = idx === state.selectedIndex ? 'selected' : '';
|
||||
const tags = (e.tags || []).map(t => `<span class="entity-tag">${t}</span>`).join('');
|
||||
const selected = idx === state.selectedIndex ? ' selected' : '';
|
||||
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 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;
|
||||
if (e.title) {
|
||||
@@ -321,11 +422,11 @@
|
||||
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>
|
||||
${label}
|
||||
${time}
|
||||
<span class="entity-tags">${tags}</span>
|
||||
<span class="entity-tags">${tags}${cardBadge}</span>
|
||||
<span class="entity-meta">${useBadge}</span>
|
||||
</div>`;
|
||||
}
|
||||
@@ -351,10 +452,10 @@
|
||||
|
||||
if (e.card_type) {
|
||||
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>`;
|
||||
} 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 danger" onclick="nibApp.deleteEntity('${e.id}')">delete</button>`;
|
||||
@@ -363,17 +464,19 @@
|
||||
const titleHtml = e.title ? `<h2 class="detail-title" data-id="${e.id}">${escHtml(e.title)}</h2>` : '';
|
||||
|
||||
pane.innerHTML = `
|
||||
<div class="detail-header">
|
||||
<span class="detail-glyph ${gc}">${glyph}</span>
|
||||
<span class="detail-id">${shortId}</span>
|
||||
${e.time_anchor ? `<span class="entity-time">@${e.time_anchor}</span>` : ''}
|
||||
<div class="detail-scroll">
|
||||
<div class="detail-header">
|
||||
<span class="detail-glyph ${gc}">${glyph}</span>
|
||||
<span class="detail-id">${shortId}</span>
|
||||
${e.time_anchor ? `<span class="entity-time">@${e.time_anchor}</span>` : ''}
|
||||
</div>
|
||||
${descHtml}
|
||||
${titleHtml}
|
||||
<div class="detail-body" data-id="${e.id}">${escHtml(e.body)}</div>
|
||||
${tags ? `<div class="detail-tags">${tags}</div>` : ''}
|
||||
${cardContent}
|
||||
<div class="detail-actions">${actions}</div>
|
||||
</div>
|
||||
${descHtml}
|
||||
${titleHtml}
|
||||
<div class="detail-body" data-id="${e.id}">${escHtml(e.body)}</div>
|
||||
${tags ? `<div class="detail-tags">${tags}</div>` : ''}
|
||||
${cardContent}
|
||||
<div class="detail-actions">${actions}</div>
|
||||
`;
|
||||
|
||||
const titleEl = pane.querySelector('.detail-title');
|
||||
@@ -560,13 +663,10 @@
|
||||
<button class="month-nav-btn" id="month-prev">◂</button>
|
||||
<span class="month-nav-label">${label}</span>
|
||||
<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-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) {
|
||||
@@ -590,10 +690,25 @@
|
||||
function switchView(view) {
|
||||
state.view = view;
|
||||
state.activeMonth = null;
|
||||
state.selectedIndex = -1;
|
||||
$$('.nav-btn').forEach(b => b.classList.toggle('active', b.dataset.view === view));
|
||||
window.location.hash = view === 'cards' ? '/cards' : '/';
|
||||
loadEntities();
|
||||
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) ==========
|
||||
@@ -606,6 +721,7 @@
|
||||
await navigator.clipboard.writeText(e.body);
|
||||
await api.useEntity(id);
|
||||
await loadEntities();
|
||||
showToast('copied');
|
||||
} catch (err) {
|
||||
console.error('clipboard:', err);
|
||||
}
|
||||
@@ -630,12 +746,14 @@
|
||||
await api.demoteEntity(id);
|
||||
await loadEntities();
|
||||
await loadTags();
|
||||
showToast('demoted');
|
||||
},
|
||||
|
||||
async deleteEntity(id) {
|
||||
await api.deleteEntity(id);
|
||||
await loadEntities();
|
||||
await loadTags();
|
||||
showToast('deleted');
|
||||
},
|
||||
|
||||
async resolveTemplate(id) {
|
||||
@@ -651,6 +769,7 @@
|
||||
await navigator.clipboard.writeText(resolved);
|
||||
await api.useEntity(id);
|
||||
await loadEntities();
|
||||
showToast('copied');
|
||||
} catch (err) {
|
||||
console.error('clipboard:', err);
|
||||
}
|
||||
@@ -688,6 +807,7 @@
|
||||
await loadTags();
|
||||
const idx = state.entities.findIndex(x => x.id === targetId);
|
||||
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 ==========
|
||||
|
||||
$$('.type-btn').forEach(btn => {
|
||||
@@ -745,6 +838,7 @@
|
||||
await api.promoteEntity(id, btn.dataset.type);
|
||||
await loadEntities();
|
||||
await loadTags();
|
||||
showToast('promoted → ' + btn.dataset.type);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -761,12 +855,11 @@
|
||||
// ========== Keyboard shortcuts ==========
|
||||
|
||||
let lastDTime = 0;
|
||||
const captureInput = $('#capture-input');
|
||||
|
||||
document.addEventListener('keydown', (ev) => {
|
||||
if (document.activeElement === captureInput ||
|
||||
document.activeElement.classList.contains('detail-body-edit')) {
|
||||
if (ev.key === 'Escape') document.activeElement.blur();
|
||||
const tag = (ev.target.tagName || '').toLowerCase();
|
||||
if (tag === 'input' || tag === 'textarea') {
|
||||
if (ev.key === 'Escape') ev.target.blur();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -789,7 +882,7 @@
|
||||
break;
|
||||
case 'n':
|
||||
ev.preventDefault();
|
||||
captureInput.focus();
|
||||
$('#capture-input').focus();
|
||||
break;
|
||||
case 'p': {
|
||||
const e = state.entities[state.selectedIndex];
|
||||
@@ -848,6 +941,9 @@
|
||||
}
|
||||
$$('.nav-btn').forEach(b => b.classList.toggle('active', b.dataset.view === state.view));
|
||||
loadEntities();
|
||||
renderMonthNav();
|
||||
renderTagRail();
|
||||
renderCaptureBar();
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', handleHash);
|
||||
@@ -884,6 +980,7 @@
|
||||
// ========== Init ==========
|
||||
|
||||
async function init() {
|
||||
renderCaptureBar();
|
||||
await Promise.all([loadEntities(), loadTags()]);
|
||||
handleHash();
|
||||
renderMonthNav();
|
||||
|
||||
+6
-11
@@ -6,28 +6,22 @@
|
||||
<title>nib</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<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">
|
||||
<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>
|
||||
<body>
|
||||
<div id="app">
|
||||
<header>
|
||||
<div class="header-left">
|
||||
<h1 class="logo">nib</h1>
|
||||
<span class="logo">nib</span>
|
||||
<nav>
|
||||
<button data-view="stream" class="nav-btn active">stream</button>
|
||||
<button data-view="cards" class="nav-btn">cards</button>
|
||||
</nav>
|
||||
</div>
|
||||
<form id="capture-bar" autocomplete="off">
|
||||
<input type="text" id="capture-input" placeholder="capture — - todo # note * event" spellcheck="false">
|
||||
</form>
|
||||
<div class="header-search">
|
||||
<input type="text" id="search-input" placeholder="? search #tag" spellcheck="false">
|
||||
</div>
|
||||
<button class="theme-toggle" id="theme-toggle" title="toggle theme">◑</button>
|
||||
</header>
|
||||
<main>
|
||||
@@ -35,6 +29,7 @@
|
||||
<section id="entity-panel">
|
||||
<div id="month-nav"></div>
|
||||
<div id="entity-list"></div>
|
||||
<div id="capture-bar"></div>
|
||||
</section>
|
||||
<aside id="detail-pane">
|
||||
<div class="detail-empty">select an entity</div>
|
||||
|
||||
+293
-135
@@ -1,4 +1,4 @@
|
||||
/* ── TOKENS ─────────────────────────────────────────── */
|
||||
/* ── TOKENS (nib DS v2) ─────────────────────────────── */
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--bg: #0c0b09;
|
||||
@@ -11,6 +11,7 @@
|
||||
--dim: #504840;
|
||||
--accent: #c8942a;
|
||||
--a-bg: rgba(200,148,42,.09);
|
||||
--a-str: rgba(200,148,42,.22);
|
||||
--todo: #d4a84b;
|
||||
--note: #6ab8b0;
|
||||
--event: #6898c8;
|
||||
@@ -18,9 +19,8 @@
|
||||
--ok: #7aab72;
|
||||
--danger: #b85858;
|
||||
--lineage: #9878bc;
|
||||
--pin: #c8942a;
|
||||
--sans: 'Space Grotesk', system-ui, sans-serif;
|
||||
--mono: 'Monaspace Neon', ui-monospace, monospace;
|
||||
--mono: 'JetBrains Mono', ui-monospace, monospace;
|
||||
--r1: 2px;
|
||||
--r2: 4px;
|
||||
--r3: 8px;
|
||||
@@ -40,6 +40,7 @@
|
||||
--dim: #a09080;
|
||||
--accent: #8a6018;
|
||||
--a-bg: rgba(138,96,24,.08);
|
||||
--a-str: rgba(138,96,24,.18);
|
||||
--todo: #7a5c00;
|
||||
--note: #1a7070;
|
||||
--event: #245890;
|
||||
@@ -47,14 +48,16 @@
|
||||
--ok: #2a6828;
|
||||
--danger: #882030;
|
||||
--lineage: #5830a0;
|
||||
--pin: #8a6018;
|
||||
}
|
||||
|
||||
/* ── RESET ──────────────────────────────────────────── */
|
||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body { height: 100%; overflow: hidden; }
|
||||
::-webkit-scrollbar { width: 3px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-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 {
|
||||
font-family: var(--sans);
|
||||
@@ -62,96 +65,85 @@ body {
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-template-rows: 36px 1fr;
|
||||
height: 100vh;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
/* ── HEADER ─────────────────────────────────────────── */
|
||||
header {
|
||||
background: var(--surf);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 0 20px;
|
||||
height: 36px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--surf);
|
||||
flex-shrink: 0;
|
||||
gap: 14px;
|
||||
padding: 0 18px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: var(--mono);
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
letter-spacing: .3em;
|
||||
letter-spacing: -.02em;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
nav { display: flex; gap: 2px; }
|
||||
|
||||
.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-size: 11px;
|
||||
font-weight: 500;
|
||||
color: var(--dim);
|
||||
padding: 3px 8px;
|
||||
border-radius: var(--r1);
|
||||
transition: color var(--t-fast), background var(--t-fast);
|
||||
}
|
||||
|
||||
.nav-btn:hover { color: var(--muted); }
|
||||
.nav-btn.active { color: var(--accent); background: var(--a-bg); }
|
||||
|
||||
#capture-bar {
|
||||
.header-search {
|
||||
flex: 1;
|
||||
max-width: 600px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
#capture-input {
|
||||
#search-input {
|
||||
width: 100%;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--r2);
|
||||
padding: 4px 10px;
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
transition: border-color var(--t-fast);
|
||||
}
|
||||
|
||||
#capture-input:hover { border-color: var(--muted); }
|
||||
#capture-input:focus { border-color: var(--accent); }
|
||||
#capture-input::placeholder { color: var(--dim); }
|
||||
#search-input:hover { border-color: var(--muted); }
|
||||
#search-input:focus { border-color: var(--accent); }
|
||||
#search-input::placeholder { color: var(--dim); }
|
||||
|
||||
.theme-toggle {
|
||||
background: none;
|
||||
margin-left: auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r1);
|
||||
color: var(--dim);
|
||||
font-family: var(--mono);
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: color var(--t-fast), border-color var(--t-fast);
|
||||
}
|
||||
|
||||
@@ -160,67 +152,132 @@ nav {
|
||||
/* ── MAIN LAYOUT ────────────────────────────────────── */
|
||||
main {
|
||||
display: grid;
|
||||
grid-template-columns: 180px 1fr 320px;
|
||||
flex: 1;
|
||||
grid-template-columns: 192px 1fr 400px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── TAG RAIL ───────────────────────────────────────── */
|
||||
#tag-rail {
|
||||
border-right: 1px solid var(--border);
|
||||
padding: 12px 0;
|
||||
overflow-y: auto;
|
||||
background: var(--surf);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
.rail-head {
|
||||
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;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--muted);
|
||||
transition: color var(--t-fast), background var(--t-fast);
|
||||
border-left: 2px solid transparent;
|
||||
}
|
||||
|
||||
.tag-item:hover { background: var(--raised); color: var(--text); }
|
||||
.tag-item.active { color: var(--accent); background: var(--a-bg); }
|
||||
.rail-item:hover { color: var(--text); background: var(--raised); }
|
||||
.rail-item.on { color: var(--accent); background: var(--a-bg); border-left-color: var(--accent); }
|
||||
|
||||
.tag-name { font-family: var(--mono); font-size: 11px; }
|
||||
.tag-name::before { content: '#'; color: var(--dim); }
|
||||
.tag-count {
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
.rail-arrow {
|
||||
font-size: 9px;
|
||||
color: var(--accent);
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.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);
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* ── 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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
#month-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 20px;
|
||||
padding: 5px 18px;
|
||||
border-bottom: 1px solid var(--soft);
|
||||
background: var(--surf);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#month-nav:empty { display: none; }
|
||||
|
||||
.month-nav-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--dim);
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
font-size: 10px;
|
||||
color: var(--dim);
|
||||
padding: 2px 6px;
|
||||
border-radius: var(--r1);
|
||||
transition: color var(--t-fast), background var(--t-fast);
|
||||
@@ -231,38 +288,25 @@ main {
|
||||
.month-nav-label {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--text);
|
||||
min-width: 80px;
|
||||
color: var(--muted);
|
||||
flex: 1;
|
||||
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 {
|
||||
overflow-y: auto;
|
||||
padding: 4px 0;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 4px 0 8px;
|
||||
}
|
||||
|
||||
.date-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .6rem;
|
||||
padding: 8px 20px 4px;
|
||||
font-size: 10px;
|
||||
gap: 8px;
|
||||
padding: 10px 20px 5px;
|
||||
font-family: var(--mono);
|
||||
font-size: 9px;
|
||||
color: var(--dim);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .2em;
|
||||
@@ -279,35 +323,44 @@ main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 20px;
|
||||
padding: 5px 16px 5px 20px;
|
||||
cursor: pointer;
|
||||
transition: background var(--t-fast);
|
||||
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.selected {
|
||||
.entity-item:hover { background: var(--surf); }
|
||||
.entity-item.selected { background: var(--surf); border-left-color: var(--accent); }
|
||||
|
||||
.entity-item.is-card {
|
||||
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 {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.glyph-note { color: var(--dim); }
|
||||
.glyph-note { color: var(--muted); }
|
||||
.glyph-todo { color: var(--todo); }
|
||||
.glyph-event { color: var(--event); }
|
||||
.glyph-snippet { color: var(--accent); }
|
||||
.glyph-template { color: var(--lineage); }
|
||||
.glyph-checklist { color: var(--remind); }
|
||||
.glyph-decision { color: var(--note); }
|
||||
.glyph-link { color: var(--danger); }
|
||||
.glyph-link { color: var(--event); }
|
||||
|
||||
.entity-title {
|
||||
font-family: var(--sans);
|
||||
@@ -355,7 +408,7 @@ main {
|
||||
font-size: 9px;
|
||||
color: var(--muted);
|
||||
border: 1px solid var(--border);
|
||||
padding: 1px 6px;
|
||||
padding: 1px 5px;
|
||||
border-radius: var(--r1);
|
||||
}
|
||||
|
||||
@@ -368,17 +421,86 @@ main {
|
||||
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 {
|
||||
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 {
|
||||
border-left: 1px solid var(--border);
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
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 {
|
||||
@@ -420,8 +542,8 @@ main {
|
||||
|
||||
.detail-title {
|
||||
font-family: var(--sans);
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
cursor: text;
|
||||
padding: 2px 6px;
|
||||
@@ -449,7 +571,7 @@ main {
|
||||
.detail-body {
|
||||
font-family: var(--mono);
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
line-height: 1.72;
|
||||
margin-bottom: 16px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
@@ -468,7 +590,7 @@ main {
|
||||
min-height: 80px;
|
||||
font-family: var(--mono);
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
line-height: 1.72;
|
||||
margin-bottom: 16px;
|
||||
padding: 6px 8px;
|
||||
background: var(--bg);
|
||||
@@ -490,40 +612,40 @@ main {
|
||||
|
||||
.detail-tag {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
color: var(--accent);
|
||||
border: 1px solid currentColor;
|
||||
border-color: color-mix(in srgb, var(--accent) 38%, transparent);
|
||||
border: 1px solid rgba(200,148,42,.35);
|
||||
background: var(--a-bg);
|
||||
padding: 2px 8px;
|
||||
padding: 2px 7px;
|
||||
border-radius: var(--r1);
|
||||
}
|
||||
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
border-top: 1px solid var(--soft);
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: none;
|
||||
font-family: var(--sans);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
padding: 4px 12px;
|
||||
padding: 4px 11px;
|
||||
border-radius: var(--r1);
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
font-family: var(--mono);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
gap: 5px;
|
||||
transition: color var(--t-fast), border-color var(--t-fast);
|
||||
}
|
||||
|
||||
.action-btn:hover { border-color: var(--accent); color: var(--accent); }
|
||||
.action-btn.primary { border-color: var(--accent); color: var(--accent); background: var(--a-bg); }
|
||||
.action-btn.danger { color: var(--danger); border-color: var(--danger); }
|
||||
.action-btn.danger:hover { background: color-mix(in srgb, var(--danger) 8%, transparent); }
|
||||
.action-btn:hover { color: var(--accent); border-color: var(--accent); }
|
||||
.action-btn.primary { color: var(--accent); border-color: var(--accent); background: var(--a-bg); }
|
||||
.action-btn.danger { color: var(--danger); border-color: rgba(184,88,88,.4); }
|
||||
.action-btn.danger:hover { border-color: var(--danger); }
|
||||
|
||||
/* ── TEMPLATE SLOTS ─────────────────────────────────── */
|
||||
.slot-form { margin: 16px 0; }
|
||||
@@ -591,7 +713,7 @@ main {
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
background: rgba(0,0,0,.65);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
@@ -603,17 +725,28 @@ main {
|
||||
background: var(--surf);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r3);
|
||||
padding: 24px;
|
||||
padding: 22px;
|
||||
z-index: 101;
|
||||
min-width: 320px;
|
||||
min-width: 300px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,.5);
|
||||
}
|
||||
|
||||
.modal-content h3 {
|
||||
font-family: var(--mono);
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
margin-bottom: 16px;
|
||||
font-size: 12px;
|
||||
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 {
|
||||
@@ -625,34 +758,38 @@ main {
|
||||
.type-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 14px;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
border-radius: var(--r2);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
font-family: var(--mono);
|
||||
color: var(--text);
|
||||
transition: border-color var(--t-fast), background var(--t-fast);
|
||||
}
|
||||
|
||||
.type-btn:hover { border-color: var(--accent); background: var(--raised); }
|
||||
.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 {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
padding: 6px;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
color: var(--dim);
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
font-size: 9px;
|
||||
font-family: var(--mono);
|
||||
text-align: center;
|
||||
transition: color var(--t-fast);
|
||||
}
|
||||
|
||||
@@ -670,7 +807,6 @@ main {
|
||||
color: var(--dim);
|
||||
padding: 4px 20px;
|
||||
border-radius: var(--r1);
|
||||
cursor: pointer;
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
transition: color var(--t-fast), border-color var(--t-fast);
|
||||
@@ -689,7 +825,6 @@ main {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
border-radius: var(--r2);
|
||||
transition: background var(--t-fast);
|
||||
}
|
||||
@@ -703,6 +838,29 @@ main {
|
||||
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 ─────────────────────────────────────── */
|
||||
@media (max-width: 900px) {
|
||||
main { grid-template-columns: 1fr; }
|
||||
|
||||
Reference in New Issue
Block a user