From aa7c9aef7d84f03eb386d6581977c9b2e0c9aa70 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Thu, 14 May 2026 17:02:11 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20implement=20nib=20design=20system=20?= =?UTF-8?q?=E2=80=94=20warm=20amber=20palette,=20dual=20themes,=20new=20ty?= =?UTF-8?q?pography?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace Tokyonight/Catppuccin blue palette with warm amber/ink identity. Dual theme support (noir + paper) via data-theme attribute with localStorage persistence. Space Grotesk for chrome, Monaspace Neon for content. Updated glyph set (Strokes: — ○ ◇) across web and CLI. --- internal/display/glyph.go | 6 +- web/app.js | 18 +- web/index.html | 14 +- web/style.css | 482 +++++++++++++++++++++----------------- 4 files changed, 299 insertions(+), 221 deletions(-) diff --git a/internal/display/glyph.go b/internal/display/glyph.go index 02604d4..f562d39 100644 --- a/internal/display/glyph.go +++ b/internal/display/glyph.go @@ -3,8 +3,8 @@ package display import "github.com/lerko/nib/internal/db" var glyphMap = map[db.Glyph]string{ - db.GlyphNote: "◦", - db.GlyphTodo: "▸", + db.GlyphNote: "—", + db.GlyphTodo: "○", db.GlyphEvent: "◇", } @@ -25,7 +25,7 @@ func DisplayGlyph(glyph db.Glyph, cardType *db.CardType) string { if g, ok := glyphMap[glyph]; ok { return g } - return "◦" + return "—" } func FormatID(id string) string { diff --git a/web/app.js b/web/app.js index 2a163c1..1db5e94 100644 --- a/web/app.js +++ b/web/app.js @@ -2,7 +2,7 @@ 'use strict'; const GLYPHS = { - note: '◦', todo: '▸', event: '◇', + note: '—', todo: '○', event: '◇', snippet: '◆', template: '◈', checklist: '☐', decision: '⚖', link: '↗', }; @@ -222,7 +222,7 @@ const groups = groupByDate(state.entities); let idx = 0; for (const g of groups) { - html += `
── ${g.label} ──
`; + html += `
${g.label}
`; for (const e of g.entities) { html += renderEntityItem(e, idx); idx++; @@ -754,6 +754,20 @@ return escHtml(s).replace(/'/g, '''); } + // ========== Theme ========== + + const themeToggle = $('#theme-toggle'); + let nibTheme = localStorage.getItem('nib:theme') || 'dark'; + document.documentElement.setAttribute('data-theme', nibTheme); + themeToggle.textContent = nibTheme === 'paper' ? '◐' : '◑'; + + themeToggle.addEventListener('click', () => { + nibTheme = nibTheme === 'dark' ? 'paper' : 'dark'; + document.documentElement.setAttribute('data-theme', nibTheme); + localStorage.setItem('nib:theme', nibTheme); + themeToggle.textContent = nibTheme === 'paper' ? '◐' : '◑'; + }); + // ========== Init ========== async function init() { diff --git a/web/index.html b/web/index.html index 88398b3..0c8e0fd 100644 --- a/web/index.html +++ b/web/index.html @@ -1,10 +1,19 @@ - + nib + + + +
@@ -17,8 +26,9 @@
- +
+
diff --git a/web/style.css b/web/style.css index e74432e..d2fe488 100644 --- a/web/style.css +++ b/web/style.css @@ -1,29 +1,63 @@ +/* ── TOKENS ─────────────────────────────────────────── */ :root { - --bg: #1a1b26; - --bg-surface: #24283b; - --bg-hover: #292e42; - --bg-selected: #33394d; - --text: #c0caf5; - --text-dim: #565f89; - --text-muted: #3b4261; - --accent: #7aa2f7; - --accent-dim: #3d59a1; - --green: #9ece6a; - --red: #f7768e; - --yellow: #e0af68; - --orange: #ff9e64; - --purple: #bb9af7; - --cyan: #7dcfff; - --border: #292e42; - --radius: 6px; - --font-mono: 'JetBrains Mono', 'Fira Code', 'SF Mono', Menlo, monospace; - --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + color-scheme: dark; + --bg: #0c0b09; + --surf: #111009; + --raised: #1a1715; + --border: #252118; + --soft: #1e1b16; + --text: #e8dfc8; + --muted: #8c8070; + --dim: #504840; + --accent: #c8942a; + --a-bg: rgba(200,148,42,.09); + --todo: #d4a84b; + --note: #6ab8b0; + --event: #6898c8; + --remind: #c8784a; + --ok: #7aab72; + --danger: #b85858; + --lineage: #9878bc; + --pin: #c8942a; + --sans: 'Space Grotesk', system-ui, sans-serif; + --mono: 'Monaspace Neon', ui-monospace, monospace; + --r1: 2px; + --r2: 4px; + --r3: 8px; + --t-fast: 80ms ease; + --t-base: 200ms ease; } -* { margin: 0; padding: 0; box-sizing: border-box; } +[data-theme="paper"] { + color-scheme: light; + --bg: #f4efe4; + --surf: #faf7f0; + --raised: #ece7db; + --border: #d4cdc0; + --soft: #e6e0d4; + --text: #1c1810; + --muted: #6a5e50; + --dim: #a09080; + --accent: #8a6018; + --a-bg: rgba(138,96,24,.08); + --todo: #7a5c00; + --note: #1a7070; + --event: #245890; + --remind: #984020; + --ok: #2a6828; + --danger: #882030; + --lineage: #5830a0; + --pin: #8a6018; +} + +/* ── RESET ──────────────────────────────────────────── */ +*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } +::-webkit-scrollbar { width: 3px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; } body { - font-family: var(--font-sans); + font-family: var(--sans); background: var(--bg); color: var(--text); font-size: 14px; @@ -38,14 +72,15 @@ body { height: 100vh; } -/* Header */ +/* ── HEADER ─────────────────────────────────────────── */ header { display: flex; align-items: center; gap: 16px; - padding: 12px 20px; + padding: 0 20px; + height: 36px; border-bottom: 1px solid var(--border); - background: var(--bg-surface); + background: var(--surf); flex-shrink: 0; } @@ -57,32 +92,33 @@ header { } .logo { - font-family: var(--font-mono); - font-size: 18px; - font-weight: 700; + font-family: var(--mono); + font-size: 15px; + font-weight: 300; color: var(--accent); - letter-spacing: -0.5px; + letter-spacing: .3em; } nav { display: flex; - gap: 4px; + gap: 2px; } .nav-btn { background: none; - border: 1px solid transparent; - color: var(--text-dim); - padding: 4px 12px; - border-radius: var(--radius); + border: none; + color: var(--dim); + padding: 4px 8px; + border-radius: var(--r1); cursor: pointer; - font-size: 13px; - font-family: var(--font-mono); - transition: all 0.15s; + font-size: 11px; + font-family: var(--sans); + font-weight: 500; + transition: color var(--t-fast), background var(--t-fast); } -.nav-btn:hover { color: var(--text); background: var(--bg-hover); } -.nav-btn.active { color: var(--accent); border-color: var(--accent-dim); background: var(--bg); } +.nav-btn:hover { color: var(--muted); } +.nav-btn.active { color: var(--accent); background: var(--a-bg); } #capture-bar { flex: 1; @@ -92,31 +128,36 @@ nav { #capture-input { width: 100%; background: var(--bg); - border: 1px solid var(--text-muted); + border: 1px solid var(--border); color: var(--text); - padding: 8px 12px; - border-radius: var(--radius); - font-family: var(--font-mono); - font-size: 13px; + padding: 4px 10px; + border-radius: var(--r2); + font-family: var(--mono); + font-size: 12px; outline: none; - transition: border-color 0.15s, box-shadow 0.15s; + transition: border-color var(--t-fast); } -#capture-input:hover { - border-color: var(--accent-dim); - box-shadow: 0 0 0 1px var(--accent-dim); +#capture-input:hover { border-color: var(--muted); } +#capture-input:focus { border-color: var(--accent); } +#capture-input::placeholder { color: var(--dim); } + +.theme-toggle { + background: none; + border: 1px solid var(--border); + border-radius: var(--r1); + color: var(--dim); + font-family: var(--mono); + font-size: 13px; + padding: 2px 8px; + cursor: pointer; + flex-shrink: 0; + transition: color var(--t-fast), border-color var(--t-fast); } -#capture-input:focus { - border-color: var(--accent); - box-shadow: 0 0 0 1px var(--accent); -} +.theme-toggle:hover { color: var(--accent); border-color: var(--accent); } -#capture-input::placeholder { - color: var(--text-dim); -} - -/* Main layout */ +/* ── MAIN LAYOUT ────────────────────────────────────── */ main { display: grid; grid-template-columns: 180px 1fr 320px; @@ -124,36 +165,38 @@ main { overflow: hidden; } -/* Tag rail */ +/* ── TAG RAIL ───────────────────────────────────────── */ #tag-rail { border-right: 1px solid var(--border); padding: 12px 0; overflow-y: auto; + background: var(--surf); } .tag-item { display: flex; justify-content: space-between; - padding: 6px 16px; + padding: 4px 16px; cursor: pointer; - font-size: 13px; - color: var(--text-dim); - transition: all 0.1s; + font-size: 11px; + color: var(--muted); + transition: color var(--t-fast), background var(--t-fast); } -.tag-item:hover { background: var(--bg-hover); color: var(--text); } -.tag-item.active { color: var(--accent); background: var(--bg-selected); } +.tag-item:hover { background: var(--raised); color: var(--text); } +.tag-item.active { color: var(--accent); background: var(--a-bg); } -.tag-name { font-family: var(--font-mono); } -.tag-name::before { content: '#'; color: var(--text-muted); } +.tag-name { font-family: var(--mono); font-size: 11px; } +.tag-name::before { content: '#'; color: var(--dim); } .tag-count { - font-size: 11px; - color: var(--text-muted); + font-family: var(--mono); + font-size: 10px; + color: var(--dim); min-width: 20px; text-align: right; } -/* Entity panel */ +/* ── ENTITY PANEL ───────────────────────────────────── */ #entity-panel { display: flex; flex-direction: column; @@ -164,32 +207,30 @@ main { display: flex; align-items: center; gap: 8px; - padding: 8px 20px; - border-bottom: 1px solid var(--border); + padding: 6px 20px; + border-bottom: 1px solid var(--soft); flex-shrink: 0; } -#month-nav:empty { - display: none; -} +#month-nav:empty { display: none; } .month-nav-btn { background: none; border: none; - color: var(--text-dim); - font-family: var(--font-mono); - font-size: 13px; + color: var(--dim); + font-family: var(--mono); + font-size: 11px; cursor: pointer; padding: 2px 6px; - border-radius: 4px; - transition: all 0.1s; + border-radius: var(--r1); + transition: color var(--t-fast), background var(--t-fast); } -.month-nav-btn:hover { color: var(--text); background: var(--bg-hover); } +.month-nav-btn:hover { color: var(--text); background: var(--raised); } .month-nav-label { - font-family: var(--font-mono); - font-size: 13px; + font-family: var(--mono); + font-size: 11px; color: var(--text); min-width: 80px; text-align: center; @@ -198,75 +239,89 @@ main { .month-nav-clear { background: none; border: none; - color: var(--text-muted); - font-family: var(--font-mono); - font-size: 11px; + 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 { overflow-y: auto; - padding: 8px 0; + padding: 4px 0; flex: 1; } .date-header { + display: flex; + align-items: center; + gap: .6rem; padding: 8px 20px 4px; - font-size: 11px; - font-family: var(--font-mono); - color: var(--text-muted); - text-transform: lowercase; - letter-spacing: 0.5px; + font-size: 10px; + font-family: var(--mono); + color: var(--dim); + text-transform: uppercase; + letter-spacing: .2em; +} + +.date-header::after { + content: ''; + flex: 1; + height: 1px; + background: var(--soft); } .entity-item { display: flex; align-items: center; - gap: 10px; - padding: 8px 20px; + gap: 8px; + padding: 6px 20px; cursor: pointer; - transition: background 0.1s; + transition: background var(--t-fast); border-left: 2px solid transparent; } -.entity-item:hover { background: var(--bg-hover); } +.entity-item:hover { background: var(--raised); } .entity-item.selected { - background: var(--bg-selected); + background: var(--surf); border-left-color: var(--accent); } .entity-glyph { - font-size: 14px; - width: 20px; + font-family: var(--mono); + font-size: 12px; + width: 14px; text-align: center; flex-shrink: 0; + font-weight: 500; } -.glyph-note { color: var(--text-dim); } -.glyph-todo { color: var(--green); } -.glyph-event { color: var(--yellow); } +.glyph-note { color: var(--dim); } +.glyph-todo { color: var(--todo); } +.glyph-event { color: var(--event); } .glyph-snippet { color: var(--accent); } -.glyph-template { color: var(--purple); } -.glyph-checklist { color: var(--orange); } -.glyph-decision { color: var(--cyan); } -.glyph-link { color: var(--red); } +.glyph-template { color: var(--lineage); } +.glyph-checklist { color: var(--remind); } +.glyph-decision { color: var(--note); } +.glyph-link { color: var(--danger); } .entity-body { flex: 1; - font-size: 13px; + font-family: var(--mono); + font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .entity-time { - font-family: var(--font-mono); - font-size: 11px; - color: var(--text-dim); + font-family: var(--mono); + font-size: 10px; + color: var(--dim); flex-shrink: 0; } @@ -277,40 +332,42 @@ main { } .entity-tag { - font-family: var(--font-mono); - font-size: 10px; - color: var(--accent-dim); - background: rgba(122, 162, 247, 0.1); + font-family: var(--mono); + font-size: 9px; + color: var(--muted); + border: 1px solid var(--border); padding: 1px 6px; - border-radius: 3px; + border-radius: var(--r1); } .entity-meta { - font-family: var(--font-mono); - font-size: 11px; - color: var(--text-muted); + font-family: var(--mono); + font-size: 10px; + color: var(--dim); flex-shrink: 0; display: flex; gap: 8px; } .use-badge { - color: var(--yellow); + color: var(--todo); font-size: 10px; } -/* Detail pane */ +/* ── DETAIL PANE ────────────────────────────────────── */ #detail-pane { border-left: 1px solid var(--border); padding: 20px; overflow-y: auto; + background: var(--surf); } .detail-empty { - color: var(--text-muted); - font-size: 13px; + color: var(--dim); + font-size: 12px; text-align: center; margin-top: 40px; + font-family: var(--mono); } .detail-header { @@ -320,44 +377,43 @@ main { margin-bottom: 16px; } -.detail-glyph { font-size: 20px; } +.detail-glyph { font-size: 16px; } .detail-id { - font-family: var(--font-mono); - font-size: 11px; - color: var(--text-muted); + font-family: var(--mono); + font-size: 10px; + color: var(--dim); } .detail-body { - font-size: 14px; + font-family: var(--mono); + font-size: 13px; line-height: 1.7; margin-bottom: 16px; white-space: pre-wrap; word-break: break-word; cursor: text; - border-radius: var(--radius); + border-radius: var(--r2); padding: 4px 6px; margin-left: -6px; - transition: background 0.1s; + transition: background var(--t-fast); } -.detail-body:hover { - background: var(--bg-hover); -} +.detail-body:hover { background: var(--raised); } .detail-body-edit { display: block; width: 100%; min-height: 80px; - font-family: var(--font-sans); - font-size: 14px; + font-family: var(--mono); + font-size: 13px; line-height: 1.7; margin-bottom: 16px; padding: 6px 8px; background: var(--bg); color: var(--text); border: 1px solid var(--accent); - border-radius: var(--radius); + border-radius: var(--r2); outline: none; resize: vertical; white-space: pre-wrap; @@ -372,38 +428,43 @@ main { } .detail-tag { - font-family: var(--font-mono); - font-size: 12px; + font-family: var(--mono); + font-size: 11px; color: var(--accent); - background: rgba(122, 162, 247, 0.1); + border: 1px solid currentColor; + border-color: color-mix(in srgb, var(--accent) 38%, transparent); + background: var(--a-bg); padding: 2px 8px; - border-radius: 4px; + border-radius: var(--r1); } .detail-actions { display: flex; - gap: 8px; + gap: 6px; flex-wrap: wrap; } .action-btn { - background: var(--bg-hover); + background: none; border: 1px solid var(--border); - color: var(--text); - padding: 6px 14px; - border-radius: var(--radius); + color: var(--muted); + padding: 4px 12px; + border-radius: var(--r1); cursor: pointer; - font-size: 12px; - font-family: var(--font-mono); - transition: all 0.15s; + font-size: 11px; + font-family: var(--mono); + display: inline-flex; + align-items: center; + gap: 4px; + transition: color var(--t-fast), border-color var(--t-fast); } .action-btn:hover { border-color: var(--accent); color: var(--accent); } -.action-btn.primary { background: var(--accent-dim); border-color: var(--accent); color: white; } -.action-btn.danger { border-color: var(--red); color: var(--red); } -.action-btn.danger:hover { background: rgba(247, 118, 142, 0.1); } +.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); } -/* Template slot form */ +/* ── TEMPLATE SLOTS ─────────────────────────────────── */ .slot-form { margin: 16px 0; } .slot-field { @@ -414,9 +475,9 @@ main { } .slot-label { - font-family: var(--font-mono); - font-size: 12px; - color: var(--purple); + font-family: var(--mono); + font-size: 11px; + color: var(--lineage); min-width: 80px; } @@ -426,15 +487,16 @@ main { border: 1px solid var(--border); color: var(--text); padding: 4px 8px; - border-radius: 4px; - font-family: var(--font-mono); - font-size: 12px; + border-radius: var(--r2); + font-family: var(--mono); + font-size: 11px; outline: none; + transition: border-color var(--t-fast); } -.slot-input:focus { border-color: var(--purple); } +.slot-input:focus { border-color: var(--lineage); } -/* Checklist */ +/* ── CHECKLIST ──────────────────────────────────────── */ .checklist-step { display: flex; align-items: center; @@ -442,25 +504,18 @@ main { padding: 4px 0; } -.checklist-step input[type="checkbox"] { - accent-color: var(--green); -} +.checklist-step input[type="checkbox"] { accent-color: var(--ok); } +.checklist-step.done span { text-decoration: line-through; color: var(--muted); } -.checklist-step.done span { - text-decoration: line-through; - color: var(--text-dim); -} - -/* Decision card */ -.decision-field { - margin-bottom: 12px; -} +/* ── DECISION CARD ──────────────────────────────────── */ +.decision-field { margin-bottom: 12px; } .decision-label { - font-family: var(--font-mono); - font-size: 11px; - color: var(--cyan); - margin-bottom: 4px; + font-family: var(--mono); + font-size: 10px; + color: var(--note); + text-transform: uppercase; + letter-spacing: .1em; } .decision-value { @@ -468,7 +523,7 @@ main { color: var(--text); } -/* Modal */ +/* ── MODAL ──────────────────────────────────────────── */ .modal { display: none; } .modal.visible { display: flex; } @@ -484,17 +539,17 @@ main { top: 50%; left: 50%; transform: translate(-50%, -50%); - background: var(--bg-surface); + background: var(--surf); border: 1px solid var(--border); - border-radius: 12px; + border-radius: var(--r3); padding: 24px; z-index: 101; min-width: 320px; } .modal-content h3 { - font-family: var(--font-mono); - font-size: 14px; + font-family: var(--mono); + font-size: 13px; color: var(--text); margin-bottom: 16px; font-weight: 500; @@ -503,27 +558,28 @@ main { .type-picker { display: flex; flex-direction: column; - gap: 6px; + gap: 4px; } .type-btn { display: flex; align-items: center; gap: 12px; - padding: 10px 16px; + padding: 8px 14px; background: var(--bg); border: 1px solid var(--border); color: var(--text); - border-radius: var(--radius); + border-radius: var(--r2); cursor: pointer; - font-size: 13px; - transition: all 0.15s; + font-size: 12px; + font-family: var(--mono); + transition: border-color var(--t-fast), background var(--t-fast); } -.type-btn:hover { border-color: var(--accent); background: var(--bg-hover); } -.type-btn.suggested { border-color: var(--accent-dim); background: rgba(122, 162, 247, 0.05); } +.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: 16px; width: 24px; text-align: center; } +.type-glyph { font-size: 14px; width: 20px; text-align: center; } .modal-close { display: block; @@ -532,36 +588,36 @@ main { padding: 6px; background: none; border: none; - color: var(--text-muted); - font-size: 11px; + color: var(--dim); + font-size: 10px; cursor: pointer; - font-family: var(--font-mono); + font-family: var(--mono); + transition: color var(--t-fast); } -/* Load more */ +.modal-close:hover { color: var(--muted); } + +/* ── LOAD MORE ──────────────────────────────────────── */ .load-more-wrap { padding: 12px 20px; text-align: center; } .load-more-btn { - background: var(--bg-hover); + background: none; border: 1px solid var(--border); - color: var(--text-dim); - padding: 6px 24px; - border-radius: var(--radius); + color: var(--dim); + padding: 4px 20px; + border-radius: var(--r1); cursor: pointer; - font-family: var(--font-mono); - font-size: 12px; - transition: all 0.15s; + font-family: var(--mono); + font-size: 11px; + transition: color var(--t-fast), border-color var(--t-fast); } -.load-more-btn:hover { - border-color: var(--accent); - color: var(--accent); -} +.load-more-btn:hover { border-color: var(--accent); color: var(--accent); } -/* Absorb modal */ +/* ── ABSORB MODAL ───────────────────────────────────── */ .absorb-list { max-height: 300px; overflow-y: auto; @@ -570,25 +626,23 @@ main { .absorb-source-item { display: flex; align-items: center; - gap: 10px; - padding: 8px 12px; + gap: 8px; + padding: 6px 12px; cursor: pointer; - border-radius: var(--radius); - transition: background 0.1s; + border-radius: var(--r2); + transition: background var(--t-fast); } -.absorb-source-item:hover { - background: var(--bg-hover); -} +.absorb-source-item:hover { background: var(--raised); } .absorb-source-item .entity-body { - font-size: 13px; + font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -/* Responsive */ +/* ── RESPONSIVE ─────────────────────────────────────── */ @media (max-width: 900px) { main { grid-template-columns: 1fr; } #tag-rail { display: none; } @@ -597,11 +651,11 @@ main { inset: 0; top: auto; height: 50vh; - background: var(--bg-surface); + background: var(--surf); border-top: 1px solid var(--border); border-left: none; transform: translateY(100%); - transition: transform 0.2s; + transition: transform var(--t-base); z-index: 50; } #detail-pane.visible { transform: translateY(0); }