diff --git a/web/app.js b/web/app.js index 40ed2b0..79b8032 100644 --- a/web/app.js +++ b/web/app.js @@ -28,6 +28,10 @@ activeMonth: null, intent: 'grab', flashId: null, + peekMode: 'preview', + runChecked: new Set(), + fillValues: {}, + fillActive: 0, }; const $ = (sel) => document.querySelector(sel); @@ -536,109 +540,348 @@ `; } + function fmtDateLong(dateStr) { + const d = new Date(dateStr); + const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']; + return `${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()} · ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; + } + function renderDetailPane() { const pane = $('#detail-pane'); const e = state.entities[state.selectedIndex]; if (!e) { - pane.innerHTML = '
select an entity
'; + pane.innerHTML = renderPeekIdle(); pane.classList.remove('visible'); return; } pane.classList.add('visible'); + + if (state.view === 'stream' || !e.card_type) { + pane.innerHTML = renderStreamPeek(e); + } else if (state.peekMode === 'run') { + pane.innerHTML = renderRunMode(e); + } else if (state.peekMode === 'fill') { + pane.innerHTML = renderFillMode(e); + } else if (state.peekMode === 'edit') { + pane.innerHTML = renderEditMode(e); + } else { + pane.innerHTML = renderCardPeek(e); + } + + bindPeekEvents(e); + } + + function renderPeekIdle() { + const v = state.view; + return `
+
peek
+
Select ${v === 'cards' ? 'a card' : 'an entry'}.
+
${v === 'cards' + ? 'Full detail lives here. Run checklists, fill templates, edit in place.' + : 'Entry detail lives here. Promote any capture to a card when it earns a permanent home.'}
+
+
+
navigate
+
jknext / prev
+
12stream / cards
+
+ ${v === 'stream' ? `
+
stream grammar
+
(bare text) = thought
+
- todo · @time event · !time reminder
+
#tag · |title · // desc · !pin
+
` : `
+
act
+
copy
+
rrun checklist
+
ffill template
+
eedit
+
ppin
+
`} +
+
`; + } + + function renderStreamPeek(e) { + const kind = e.card_type || e.glyph; const glyph = displayGlyph(e); const gc = glyphClass(e); + const kindLbl = { note: 'thought', todo: 'todo', event: 'event', snippet: 'snippet', template: 'template', checklist: 'checklist', decision: 'decision', link: 'link' }[kind] || kind; const tags = (e.tags || []).map(t => `#${t}`).join(''); - const shortId = e.id.slice(0, 12); - let cardContent = ''; let actions = ''; - - if (e.card_type) { - cardContent = renderCardContent(e); - actions += ``; - actions += ``; - } else { + if (!e.card_type) { actions += ``; - actions += ``; } actions += ``; - const descHtml = e.description ? `
${escHtml(e.description)}
` : ''; - const titleHtml = e.title ? `

${escHtml(e.title)}

` : ''; - - pane.innerHTML = ` -
-
- ${glyph} - ${shortId} - ${e.time_anchor ? `@${e.time_anchor}` : ''} -
- ${descHtml} - ${titleHtml} -
${escHtml(e.body)}
- ${tags ? `
${tags}
` : ''} - ${cardContent} -
${actions}
+ return `
+
+ ${glyph} + ${kindLbl} + · + ${e.id.slice(-10)} + ${fmtDateLong(e.created_at)}
- `; - - const titleEl = pane.querySelector('.detail-title'); - if (titleEl) titleEl.addEventListener('dblclick', () => startEditField('title')); - const descEl = pane.querySelector('.detail-desc'); - if (descEl) descEl.addEventListener('dblclick', () => startEditField('description')); - const bodyEl = pane.querySelector('.detail-body'); - if (bodyEl) bodyEl.addEventListener('dblclick', startEditBody); + ${e.title ? `
${escHtml(e.title)}
` : ''} +
${escHtml(e.body)}
+ ${tags ? `
tags
${tags}
` : ''} +
+
context
+
+ created${fmtDateLong(e.created_at)} + ${e.time_anchor ? `time@${e.time_anchor}` : ''} + ${e.card_type ? `statuspromoted → ${e.card_type}` : ''} +
+
+
${actions}
+
`; } - function renderCardContent(e) { - if (!e.card_data) return ''; - let data; - try { data = JSON.parse(e.card_data); } catch { return ''; } + function renderCardPeek(e) { + const glyph = GLYPHS[e.card_type] || '◆'; + const gc = GLYPH_CLASSES[e.card_type] || 'glyph-snippet'; + const affs = detectAffordances(e); + const data = e.card_data ? (() => { try { return JSON.parse(e.card_data); } catch { return {}; } })() : {}; + const tags = (e.tags || []).map(t => `#${t}`).join(''); + const affHtml = affs.map(a => `${AFF_LABELS[a]}`).join(''); + const hasSteps = data.steps && data.steps.length; + const hasDecision = data.chose != null; + const hasFill = /\$\{[^}]+\}/.test(e.body || ''); + const hasLink = !!data.url; - switch (e.card_type) { - case 'template': - if (!data.slots || !data.slots.length) return ''; - return `
- ${data.slots.map(s => ` -
- \${${s.name}} - -
- `).join('')} - -
`; + let sections = ''; - case 'checklist': - if (!data.steps || !data.steps.length) return ''; - return `
- ${data.steps.map((s, i) => ` -
- - ${escHtml(s.text)} -
- `).join('')} -
`; - - case 'decision': - return `
-
chose
${escHtml(data.chose || '—')}
-
why
${escHtml(data.why || '—')}
- ${data.rejected && data.rejected.length ? `
rejected
${data.rejected.map(escHtml).join(', ') || '—'}
` : ''} -
`; - - case 'link': - if (data.url && isSafeUrl(data.url)) { - return `
- -
`; - } - return ''; - - default: - return ''; + if (hasDecision) { + const rejected = (data.rejected || []).map(r => `${escHtml(r)}`).join(''); + sections += `
+
decision${data.status || 'decided'}
+
+
${escHtml(data.chose)}
+
why${escHtml(data.why || '')}
+ ${rejected ? `
considered
${rejected}
` : ''} +
+
`; } + + if (hasLink && !hasDecision) { + sections += `
+
link
+
+
`; + } + + if (hasSteps) { + const steps = data.steps.map((s, i) => `
${escHtml(s.text || s)}
`).join(''); + sections += `
+
steps · ${data.steps.length}
+
${steps}
+
`; + } + + if (!hasDecision && e.body) { + const lang = data.lang || ''; + sections += `
+
content${lang ? `${lang}` : ''}${hasFill ? `` : ''}
+
${escHtml(e.body)}
+
`; + } + + let actions = ``; + if (hasFill) actions += ``; + if (hasSteps) actions += ``; + actions += ``; + actions += ``; + actions += ``; + + return `
+
+
+
+ ${glyph} + ${e.card_type} + · + ${e.id.slice(-10)} + ${e.use_count > 0 ? `${e.use_count}× used` : ''} +
+
${escHtml(e.title || '')}
+ ${e.description ? `
${escHtml(e.description)}
` : ''} +
${affHtml}${tags}${e.pinned ? '' : ''}
+
+ ${sections} +
+
${actions}
+
`; + } + + function renderRunMode(e) { + const data = e.card_data ? (() => { try { return JSON.parse(e.card_data); } catch { return {}; } })() : {}; + if (!data.steps) return renderCardPeek(e); + const total = data.steps.length; + const checked = state.runChecked || new Set(); + const done = checked.size; + const pct = total > 0 ? Math.round(done / total * 100) : 0; + + const steps = data.steps.map((s, i) => { + const isDone = checked.has(i); + const text = s.text || s; + return `
+ ${isDone ? '●' : '○'} + ${escHtml(text)} +
`; + }).join(''); + + return `
+
+ ▶ running + ${done}/${total} done +
+
${escHtml(e.title || '')}
+ ${e.description ? `
${escHtml(e.description)}
` : ''} +
+
+ ${pct}% +
+
${steps}
+
Space toggler resetEsc exit
+
+ + +
+
`; + } + + function renderFillMode(e) { + const slots = []; + const re = /\$\{([^}]+)\}/g; + let m; + const seen = new Set(); + while ((m = re.exec(e.body || '')) !== null) { + const name = m[1].trim(); + if (!seen.has(name)) { seen.add(name); slots.push(name); } + } + if (!slots.length) return renderCardPeek(e); + const fill = state.fillValues || {}; + const active = state.fillActive || 0; + + let content = escHtml(e.body); + for (const name of slots) { + const val = fill[name] || ''; + const idx = slots.indexOf(name); + const cls = idx === active ? 'fill-slot active' : (val ? 'fill-slot filled' : 'fill-slot'); + const width = Math.max(name.length, val.length, 4) * 8 + 16; + content = content.replace(`\${${name}}`, ``); + } + + const allFilled = slots.every(s => fill[s]); + + return `
+
+ ⤓ filling + slot ${active + 1} / ${slots.length} +
+
${escHtml(e.title || '')}
+ ${e.description ? `
${escHtml(e.description)}
` : ''} +
${content}
+
Tab next⇧Tab prev copyEsc cancel
+
+ + +
+
`; + } + + function renderEditMode(e) { + return `
+
✎ editing
+
${escHtml(e.title || 'untitled')}
+
+
+
+
+
+
+
+
+
+
+
⌘⏎ saveEsc cancel
+
+ + +
+
`; + } + + function bindPeekEvents(e) { + const pane = $('#detail-pane'); + if (!e) return; + + if (state.peekMode === 'run') { + pane.querySelectorAll('.peek-run-step').forEach(el => { + el.addEventListener('click', () => { + const idx = parseInt(el.dataset.step); + if (!state.runChecked) state.runChecked = new Set(); + if (state.runChecked.has(idx)) state.runChecked.delete(idx); + else state.runChecked.add(idx); + renderDetailPane(); + }); + }); + } + + if (state.peekMode === 'fill') { + pane.querySelectorAll('.fill-slot input').forEach(input => { + input.addEventListener('input', () => { + if (!state.fillValues) state.fillValues = {}; + state.fillValues[input.dataset.slot] = input.value; + }); + input.addEventListener('focus', () => { + state.fillActive = parseInt(input.dataset.idx); + }); + input.addEventListener('keydown', (ev) => { + if (ev.key === 'Tab') { + ev.preventDefault(); + const slots = pane.querySelectorAll('.fill-slot input'); + const cur = parseInt(input.dataset.idx); + const next = ev.shiftKey ? Math.max(0, cur - 1) : Math.min(slots.length - 1, cur + 1); + state.fillActive = next; + renderDetailPane(); + setTimeout(() => { + const el = pane.querySelector(`.fill-slot input[data-idx="${next}"]`); + if (el) el.focus(); + }, 0); + } else if (ev.key === 'Enter' && !ev.shiftKey) { + ev.preventDefault(); + nibApp.completeFill(); + } else if (ev.key === 'Escape') { + ev.preventDefault(); + nibApp.exitMode(); + } + }); + }); + setTimeout(() => { + const el = pane.querySelector(`.fill-slot input[data-idx="${state.fillActive || 0}"]`); + if (el) el.focus(); + }, 0); + } + + if (state.peekMode === 'edit') { + const bodyTa = pane.querySelector('#edit-body'); + if (bodyTa) { + bodyTa.addEventListener('keydown', (ev) => { + if (ev.key === 'Enter' && (ev.metaKey || ev.ctrlKey)) { ev.preventDefault(); nibApp.saveEdit(e.id); } + if (ev.key === 'Escape') { ev.preventDefault(); nibApp.exitMode(); } + }); + } + } + + // Double-click to edit (stream peek) + const titleEl = pane.querySelector('.peek-title[data-id]'); + if (titleEl) titleEl.addEventListener('dblclick', () => startEditField('title')); + const bodyEl = pane.querySelector('.peek-body[data-id]'); + if (bodyEl) bodyEl.addEventListener('dblclick', startEditBody); } // ========== Inline edit ========== @@ -713,6 +956,10 @@ function selectEntity(idx) { state.selectedIndex = idx; + state.peekMode = 'preview'; + state.runChecked = new Set(); + state.fillValues = {}; + state.fillActive = 0; renderEntityList(); renderDetailPane(); } @@ -932,6 +1179,67 @@ await loadEntities(); selectEntity(state.entities.findIndex(x => x.id === id)); }, + + enterMode(mode) { + state.peekMode = mode; + if (mode === 'run') state.runChecked = new Set(); + if (mode === 'fill') { state.fillValues = {}; state.fillActive = 0; } + renderDetailPane(); + }, + + exitMode() { + state.peekMode = 'preview'; + renderDetailPane(); + }, + + resetRun() { + state.runChecked = new Set(); + renderDetailPane(); + }, + + async completeFill() { + const e = state.entities[state.selectedIndex]; + if (!e) return; + let resolved = e.body || ''; + const fill = state.fillValues || {}; + for (const [name, val] of Object.entries(fill)) { + resolved = resolved.replace(new RegExp('\\$\\{' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\}', 'g'), val); + } + try { + await navigator.clipboard.writeText(resolved); + await api.useEntity(e.id); + state.peekMode = 'preview'; + await loadEntities(); + showToast('copied resolved'); + } catch (err) { + console.error('clipboard:', err); + } + }, + + async saveEdit(id) { + const title = ($('#edit-title') || {}).value || null; + const desc = ($('#edit-desc') || {}).value || null; + const body = ($('#edit-body') || {}).value || ''; + const tagsStr = ($('#edit-tags') || {}).value || ''; + const tags = tagsStr.split(/\s+/).filter(Boolean); + await api.updateEntity(id, { body, title, description: desc, tags }); + state.peekMode = 'preview'; + await loadEntities(); + await loadTags(); + const idx = state.entities.findIndex(x => x.id === id); + if (idx >= 0) selectEntity(idx); + showToast('saved'); + }, + + async togglePin(id) { + const e = state.entities.find(x => x.id === id); + if (!e) return; + await api.updateEntity(id, { pinned: !e.pinned }); + await loadEntities(); + const idx = state.entities.findIndex(x => x.id === id); + if (idx >= 0) { state.selectedIndex = idx; renderEntityList(); renderDetailPane(); } + showToast(e.pinned ? 'unpinned' : 'pinned'); + }, }; // ========== Promote modal ========== @@ -977,6 +1285,13 @@ return; } + if (state.peekMode !== 'preview' && ev.key === 'Escape') { + nibApp.exitMode(); + return; + } + + const sel = state.entities[state.selectedIndex]; + switch (ev.key) { case 'j': ev.preventDefault(); @@ -992,36 +1307,42 @@ ev.preventDefault(); $('#capture-input').focus(); break; - case 'p': { - const e = state.entities[state.selectedIndex]; - if (e && !e.card_type) nibApp.showPromote(e.id); + case 'p': + if (sel && sel.card_type && state.view === 'cards') { + nibApp.togglePin(sel.id); + } else if (sel && !sel.card_type) { + nibApp.showPromote(sel.id); + } break; - } - case 'Enter': { - const e = state.entities[state.selectedIndex]; - if (e) nibApp.copyEntity(e.id); + case 'Enter': + if (sel) nibApp.copyEntity(sel.id); + break; + case 'r': + if (sel && sel.card_type && state.view === 'cards') nibApp.enterMode('run'); + break; + case 'f': + if (sel && sel.card_type && state.view === 'cards') nibApp.enterMode('fill'); + break; + case 'e': + if (sel && sel.card_type && state.view === 'cards') { + nibApp.enterMode('edit'); + } else { + startEditBody(); + } break; - } case 'd': { const now = Date.now(); if (now - lastDTime < 400) { - const e = state.entities[state.selectedIndex]; - if (e) nibApp.deleteEntity(e.id); + if (sel) nibApp.deleteEntity(sel.id); lastDTime = 0; } else { lastDTime = now; } break; } - case 'e': { - startEditBody(); + case 'a': + if (sel && !sel.card_type) nibApp.showAbsorb(sel.id); break; - } - case 'a': { - const e = state.entities[state.selectedIndex]; - if (e && !e.card_type) nibApp.showAbsorb(e.id); - break; - } case '1': switchView('stream'); break; case '2': switchView('cards'); break; } diff --git a/web/style.css b/web/style.css index 9f37905..21a35be 100644 --- a/web/style.css +++ b/web/style.css @@ -660,7 +660,7 @@ main { letter-spacing: .04em; } -/* ── DETAIL PANE ────────────────────────────────────── */ +/* ── PEEK PANE ──────────────────────────────────────── */ #detail-pane { background: var(--surf); border-left: 1px solid var(--border); @@ -669,10 +669,11 @@ main { overflow: hidden; } -.detail-scroll { +.peek-scroll { flex: 1; overflow-y: auto; - padding: 20px; + display: flex; + flex-direction: column; } .detail-empty { @@ -683,49 +684,283 @@ main { font-family: var(--mono); } -.detail-header { +/* peek idle */ +.peek-idle { + padding: 18px; display: flex; - align-items: center; - gap: 8px; - margin-bottom: 16px; + flex-direction: column; + gap: 14px; + flex: 1; + overflow-y: auto; } -.detail-glyph { font-size: 16px; } - -.detail-id { +.peek-idle-eyebrow { font-family: var(--mono); - font-size: 10px; - color: var(--dim); + font-size: 9px; + text-transform: uppercase; + letter-spacing: .16em; + color: var(--accent); + margin-bottom: 6px; } -.detail-desc { - font-family: var(--sans); - font-size: 11px; - color: var(--muted); - margin-bottom: 4px; - cursor: text; - padding: 2px 6px; - margin-left: -6px; - border-radius: var(--r2); - transition: background var(--t-fast); -} - -.detail-desc:hover { background: var(--raised); } - -.detail-title { +.peek-idle-title { font-family: var(--sans); font-size: 15px; font-weight: 600; - margin-bottom: 12px; + color: var(--text); + margin-bottom: 4px; +} + +.peek-idle-sub { + font-family: var(--sans); + font-size: 12px; + color: var(--muted); + line-height: 1.55; +} + +.peek-shortcuts { display: flex; flex-direction: column; gap: 10px; } +.peek-sc-sec { margin-bottom: 2px; } +.peek-sc-lbl { font-family: var(--mono); font-size: 9px; text-transform: uppercase; letter-spacing: .14em; color: var(--dim); margin-bottom: 5px; } +.peek-sc-row { display: flex; align-items: center; gap: 5px; padding: 2px 0; font-family: var(--mono); font-size: 11px; color: var(--muted); } +.peek-sc-row span { color: var(--dim); margin-left: 2px; } +.peek-sc-code { font-family: var(--mono); font-size: 10px; color: var(--accent); background: var(--bg); border: 1px solid var(--border); border-radius: var(--r2); padding: 4px 8px; margin-bottom: 5px; } +.peek-sc-hint { font-family: var(--mono); font-size: 9px; color: var(--dim); padding-bottom: 3px; } +kbd { background: var(--raised); border: 1px solid var(--border); border-radius: 2px; padding: 1px 4px; font-size: 9px; font-family: var(--mono); color: var(--muted); display: inline-block; line-height: 1.4; } + +/* peek eyebrow */ +.peek-brow { + padding: 14px 20px 0; + display: flex; + align-items: center; + gap: 7px; + flex-shrink: 0; + font-family: var(--mono); + font-size: 9px; + letter-spacing: .12em; + text-transform: uppercase; + color: var(--dim); +} + +.peek-brow-g { font-size: 13px; margin-right: 1px; flex-shrink: 0; } +.peek-brow-kind { color: var(--muted); } +.peek-brow-sep { color: var(--dim); opacity: .4; } +.peek-brow-id { color: var(--dim); } +.peek-brow-ts { margin-left: auto; color: var(--dim); letter-spacing: 0; text-transform: none; white-space: nowrap; } + +/* peek title / desc / body */ +.peek-title { + padding: 9px 20px 4px; + font-family: var(--sans); + font-size: 15px; + font-weight: 600; + color: var(--text); + line-height: 1.3; + flex-shrink: 0; +} + +.peek-desc { + padding: 0 20px 10px; + font-family: var(--sans); + font-size: 12px; + color: var(--muted); + line-height: 1.55; + flex-shrink: 0; +} + +.peek-body { + padding: 10px 20px 14px; + font-family: var(--mono); + font-size: 13px; + line-height: 1.72; + color: var(--text); + white-space: pre-wrap; + word-break: break-word; + flex-shrink: 0; cursor: text; - padding: 2px 6px; - margin-left: -6px; border-radius: var(--r2); transition: background var(--t-fast); } -.detail-title:hover { background: var(--raised); } +.peek-body:hover { background: var(--raised); } +.peek-meta { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 5px; + padding: 0 20px 12px; + flex-shrink: 0; +} + +.peek-pin { color: var(--accent); font-size: 11px; } + +/* peek sections */ +.peek-sec { border-top: 1px solid var(--soft); flex-shrink: 0; } +.peek-sec-lbl { + padding: 8px 20px 5px; + font-family: var(--mono); + font-size: 9px; + text-transform: uppercase; + letter-spacing: .16em; + color: var(--dim); + display: flex; + align-items: center; + gap: 6px; +} + +.peek-sec-lang { color: var(--accent); letter-spacing: 0; text-transform: none; font-size: 9px; } +.peek-sec-status { + color: var(--ok); + letter-spacing: 0; + text-transform: none; + font-size: 9px; + border: 1px solid rgba(122,171,114,.4); + background: rgba(122,171,114,.06); + padding: 0 6px; + border-radius: var(--r1); +} + +.peek-sec-run { + margin-left: auto; + font-family: var(--mono); + font-size: 9px; + color: var(--ok); + border: 1px solid rgba(122,171,114,.4); + padding: 1px 8px; + border-radius: var(--r1); + transition: background var(--t-fast); +} + +.peek-sec-run:hover { background: rgba(122,171,114,.1); } + +.peek-sec-inner { padding: 0 20px 14px; } +.tag-pills { display: flex; flex-wrap: wrap; gap: 5px; } + +/* peek context */ +.peek-ctx { display: flex; flex-direction: column; gap: 5px; font-family: var(--mono); font-size: 11px; color: var(--muted); } +.peek-ctx-lbl { font-size: 9px; text-transform: uppercase; letter-spacing: .1em; color: var(--dim); margin-right: 5px; } +.peek-ctx-promoted { color: var(--ok); } + +/* peek card container */ +.peek-card { + margin: 12px; + border: 1px solid var(--border); + border-radius: var(--r3); + overflow: hidden; + flex-shrink: 0; +} + +.peek-card-head { + background: var(--bg); + border-bottom: 1px solid var(--soft); + padding-bottom: 0; +} + +.peek-card .peek-sec { border-top-color: var(--border); } +.peek-card .peek-sec-inner { padding: 0 16px 14px; } +.peek-card .peek-sec-lbl { padding: 8px 16px 5px; } + +/* peek code block */ +.peek-code { + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--r2); + padding: 10px 12px; + overflow-x: auto; +} + +.peek-code pre { + font-family: var(--mono); + font-size: 11px; + line-height: 1.65; + color: var(--text); + white-space: pre-wrap; + word-break: break-word; +} + +/* peek steps */ +.peek-steps { display: flex; flex-direction: column; gap: 3px; } +.peek-step { display: flex; align-items: flex-start; gap: 8px; padding: 3px 0; font-family: var(--mono); font-size: 11px; line-height: 1.45; } +.peek-step-mark { flex-shrink: 0; margin-top: 1px; } +.peek-step-text { color: var(--text); } + +/* peek decision */ +.peek-decision { padding: 0; } +.peek-dec-choice { font-family: var(--sans); font-size: 15px; font-weight: 600; color: var(--text); margin-bottom: 6px; } +.peek-dec-choice::before { content: '▸ '; color: var(--accent); } +.peek-dec-why { font-family: var(--sans); font-size: 12px; color: var(--muted); line-height: 1.55; margin-bottom: 8px; } +.peek-dec-key { color: var(--dim); font-size: 9px; text-transform: uppercase; letter-spacing: .1em; font-family: var(--mono); margin-right: 5px; } +.peek-dec-rejected { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; } +.peek-dec-rej { font-family: var(--mono); font-size: 10px; color: var(--muted); border: 1px solid var(--border); padding: 1px 6px; border-radius: var(--r1); text-decoration: line-through; opacity: .6; } + +/* peek link */ +.peek-link-url { + display: flex; + align-items: flex-start; + gap: 6px; + font-family: var(--mono); + font-size: 11px; + color: var(--event); + border: 1px solid var(--soft); + padding: 8px 10px; + border-radius: var(--r2); + background: var(--bg); + word-break: break-all; + line-height: 1.5; +} + +/* peek actions */ +.peek-acts { + display: flex; + gap: 5px; + flex-wrap: wrap; + padding: 12px 20px 18px; + border-top: 1px solid var(--soft); + flex-shrink: 0; +} + +/* peek mode pills */ +.peek-run-pill { font-family: var(--mono); font-size: 9px; color: var(--ok); border: 1px solid rgba(122,171,114,.4); background: rgba(122,171,114,.06); padding: 1px 7px; border-radius: var(--r1); } +.peek-fill-pill { font-family: var(--mono); font-size: 9px; color: var(--lineage); border: 1px solid rgba(152,120,188,.4); background: rgba(152,120,188,.06); padding: 1px 7px; border-radius: var(--r1); } +.peek-edit-pill { font-family: var(--mono); font-size: 9px; color: var(--todo); border: 1px solid rgba(212,168,75,.4); background: rgba(212,168,75,.06); padding: 1px 7px; border-radius: var(--r1); } + +/* run mode */ +.peek-run-prog-wrap { display: flex; align-items: center; gap: 10px; padding: 0 20px 14px; flex-shrink: 0; } +.peek-run-prog-track { flex: 1; height: 3px; background: var(--border); border-radius: 2px; overflow: hidden; } +.peek-run-prog { height: 100%; background: var(--ok); border-radius: 2px; transition: width var(--t-base); } +.peek-run-pct { font-family: var(--mono); font-size: 10px; color: var(--ok); min-width: 28px; } +.peek-run-steps { flex-shrink: 0; } +.peek-run-step { display: flex; align-items: flex-start; gap: 10px; padding: 7px 20px; cursor: pointer; border-left: 2px solid transparent; transition: background var(--t-fast), border-left-color var(--t-fast); } +.peek-run-step:hover { background: var(--raised); } +.peek-run-step.done .peek-run-text { text-decoration: line-through; color: var(--dim); } +.peek-run-mark { font-family: var(--mono); font-size: 12px; flex-shrink: 0; margin-top: 1px; } +.peek-run-text { font-family: var(--sans); font-size: 12px; line-height: 1.5; color: var(--text); } + +/* fill mode */ +.peek-fill-canvas { padding: 14px 20px; flex-shrink: 0; } +.peek-fill-canvas code { font-family: var(--mono); font-size: 12px; line-height: 2; color: var(--text); white-space: pre-wrap; word-break: break-word; } + +.fill-slot { display: inline-block; border-bottom: 1.5px solid var(--lineage); } +.fill-slot.active { border-color: var(--accent); border-bottom-width: 2px; } +.fill-slot.filled { border-color: var(--ok); } +.fill-slot input { background: transparent; border: none; outline: none; color: var(--lineage); font-family: var(--mono); font-size: 12px; padding: 0 2px; min-width: 30px; line-height: 2; } +.fill-slot.active input { color: var(--text); } +.fill-slot.filled input { color: var(--ok); } + +/* edit mode */ +.peek-edit-fields { padding: 12px 20px; display: flex; flex-direction: column; gap: 12px; flex-shrink: 0; } +.peek-edit-field { display: flex; flex-direction: column; gap: 4px; } +.peek-edit-lbl { font-family: var(--mono); font-size: 9px; text-transform: uppercase; letter-spacing: .14em; color: var(--dim); } +.peek-edit-in { background: var(--bg); border: 1px solid var(--border); border-radius: var(--r2); padding: 6px 9px; font-family: var(--mono); font-size: 12px; color: var(--text); outline: none; transition: border-color var(--t-fast); } +.peek-edit-in:focus { border-color: var(--accent); } +.peek-edit-ta { background: var(--bg); border: 1px solid var(--border); border-radius: var(--r2); padding: 6px 9px; font-family: var(--mono); font-size: 12px; color: var(--text); outline: none; resize: vertical; min-height: 100px; line-height: 1.55; transition: border-color var(--t-fast); } +.peek-edit-ta:focus { border-color: var(--accent); } + +/* hints bar */ +.peek-hints { display: flex; gap: 12px; padding: 10px 20px; font-family: var(--mono); font-size: 9px; color: var(--dim); border-top: 1px solid var(--soft); flex-shrink: 0; } +.peek-hints span { display: flex; align-items: center; gap: 3px; } + +/* legacy detail support (inline edit) */ .detail-field-edit { display: block; width: 100%; @@ -740,22 +975,6 @@ main { outline: none; } -.detail-body { - font-family: var(--mono); - font-size: 13px; - line-height: 1.72; - margin-bottom: 16px; - white-space: pre-wrap; - word-break: break-word; - cursor: text; - border-radius: var(--r2); - padding: 4px 6px; - margin-left: -6px; - transition: background var(--t-fast); -} - -.detail-body:hover { background: var(--raised); } - .detail-body-edit { display: block; width: 100%; @@ -777,9 +996,8 @@ main { .detail-tags { display: flex; - gap: 6px; + gap: 5px; flex-wrap: wrap; - margin-bottom: 16px; } .detail-tag { @@ -792,14 +1010,6 @@ main { border-radius: var(--r1); } -.detail-actions { - display: flex; - gap: 5px; - flex-wrap: wrap; - border-top: 1px solid var(--soft); - padding-top: 12px; -} - .action-btn { font-family: var(--sans); font-size: 11px; @@ -816,8 +1026,10 @@ main { .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.dim { opacity: .45; } .action-btn.danger { color: var(--danger); border-color: rgba(184,88,88,.4); } .action-btn.danger:hover { border-color: var(--danger); } +.action-btn kbd { font-size: 9px; background: rgba(0,0,0,.2); border: 1px solid rgba(0,0,0,.3); border-radius: 2px; padding: 0 3px; opacity: .65; } /* ── TEMPLATE SLOTS ─────────────────────────────────── */ .slot-form { margin: 16px 0; }