Notes
ornith 1.0 9b · middle

Browser check: Artifact runtime error. This is a technical check, not a quality review.
Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 34,456 bytes · SHA-256 1785e8c3a6e6
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lumina Notes</title>
<style>
:root {
--bg-primary: #0f0f13;
--bg-secondary: #1a1a24;
--bg-tertiary: #252538;
--bg-hover: #2d2d44;
--text-primary: #e8e8ef;
--text-secondary: #9898b0;
--text-muted: #6b6b85;
--accent: #7c5cff;
--accent-hover: #926dff;
--border: rgba(255,255,255,0.06);
--radius: 12px;
--shadow: 0 4px 30px rgba(0,0,0,0.4);
--transition: 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
overflow: hidden;
height: 100vh;
}
/* Layout */
.app {
display: grid;
grid-template-columns: 260px 1fr;
grid-template-rows: 48px 1fr;
height: 100vh;
}
/* Top Bar */
.topbar {
grid-column: 1 / -1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
border-bottom: 1px solid var(--border);
background: rgba(15, 15, 19, 0.85);
backdrop-filter: blur(20px);
z-index: 100;
}
.topbar-left {
display: flex;
align-items: center;
gap: 16px;
}
.logo {
font-size: 18px;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.3px;
display: flex;
align-items: center;
gap: 10px;
}
.logo-icon {
width: 28px;
height: 28px;
background: linear-gradient(135deg, var(--accent), #c77dff);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.logo-icon svg { width: 16px; height: 16px; }
.search-input {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 7px 14px;
color: var(--text-primary);
font-size: 13px;
width: 240px;
transition: var(--transition);
}
.search-input:focus {
outline: none;
border-color: rgba(124,92,255,0.4);
background: var(--bg-tertiary);
}
.topbar-right {
display: flex;
align-items: center;
gap: 8px;
}
.icon-btn {
width: 36px;
height: 36px;
border-radius: 8px;
border: none;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
}
.icon-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.topbar-spacer { flex: 1; }
/* Sidebar */
.sidebar {
overflow-y: auto;
padding: 16px 0;
border-right: 1px solid var(--border);
background: rgba(26, 26, 36, 0.5);
}
.sidebar::-webkit-scrollbar { width: 4px; }
.sidebar::-webkit-scrollbar-thumb { background: var(--bg-tertiary); border-radius: 4px; }
.sidebar-section { padding: 12px 0; }
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 20px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--text-muted);
}
.section-header svg { opacity: 0.4; }
.section-header span.count {
background: var(--bg-tertiary);
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
color: var(--text-muted);
}
.folder-list, .note-item {
list-style: none;
}
.folder-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 20px;
cursor: pointer;
transition: var(--transition);
user-select: none;
color: var(--text-secondary);
}
.folder-item:hover { background: var(--bg-hover); color: var(--text-primary); }
.folder-item.active { background: rgba(124,92,255,0.12); color: var(--accent); border-left: 3px solid var(--accent); padding-left: 17px; }
.folder-icon { width: 20px; text-align: center; font-size: 16px; flex-shrink: 0; }
.folder-name { font-size: 14px; font-weight: 500; }
.note-item { margin-bottom: 1px; border-radius: 8px; overflow: hidden; }
.note-header {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 20px;
cursor: pointer;
transition: var(--transition);
}
.note-item:hover .note-header { background: rgba(255,255,255,0.03); }
.note-item.active .note-header { background: rgba(124,92,255,0.08); border-left: 3px solid var(--accent); padding-left: 17px; }
.note-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.dot-blue { background: #5b9fff; }
.dot-green { background: #4dc08f; }
.dot-yellow { background: #ffd166; }
.note-title-row { display: flex; align-items: baseline; gap: 8px; overflow: hidden; }
.note-name {
font-size: 13.5px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--text-primary);
}
.note-meta {
display: flex;
align-items: center;
gap: 8px;
margin-top: 2px;
font-size: 11.5px;
color: var(--text-muted);
overflow: hidden;
white-space: nowrap;
}
.note-excerpt {
padding-left: 30px;
font-size: 12px;
color: var(--text-secondary);
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Main Content */
.main {
overflow-y: auto;
background: var(--bg-primary);
}
.main::-webkit-scrollbar { width: 6px; }
.main::-webkit-scrollbar-thumb { background: var(--bg-tertiary); border-radius: 4px; }
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--text-muted);
}
.empty-icon { width: 64px; height: 64px; margin-bottom: 20px; opacity: 0.3; }
.empty-title { font-size: 18px; margin-bottom: 8px; color: var(--text-secondary); }
.empty-desc { font-size: 14px; max-width: 360px; text-align: center; line-height: 1.5; }
/* Editor */
.editor-wrapper { padding: 0; animation: fadeIn 200ms ease; }
.toolbar {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 32px;
border-bottom: 1px solid var(--border);
background: rgba(26,26,36,0.3);
}
.toolbar-btn {
width: 32px;
height: 32px;
border-radius: 6px;
border: none;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
transition: var(--transition);
}
.toolbar-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.editor-container { padding: 32px 48px 60px; max-width: 900px; }
.note-header-area { margin-bottom: 24px; }
.note-title-input {
width: 100%;
background: transparent;
border: none;
color: var(--text-primary);
font-size: 36px;
font-weight: 700;
padding: 8px 0;
outline: none;
letter-spacing: -1.5px;
line-height: 1.2;
}
.note-title-input::placeholder { color: var(--text-muted); }
.note-date-display {
font-size: 13px;
color: var(--text-muted);
margin-bottom: 8px;
}
.editor-area { outline: none; min-height: 400px; line-height: 1.75; }
/* Rich text styling */
.editor-area h1 { font-size: 22px; font-weight: 700; margin: 16px 0 8px; letter-spacing: -0.3px; color: var(--text-primary); }
.editor-area h2 { font-size: 18px; font-weight: 600; margin: 14px 0 6px; letter-spacing: -0.2px; color: var(--text-primary); }
.editor-area h3 { font-size: 15px; font-weight: 600; margin: 12px 0 4px; letter-spacing: -0.1px; color: var(--text-primary); }
.editor-area p { margin-bottom: 8px; color: var(--text-primary); }
.editor-area ul, .editor-area ol { padding-left: 24px; margin-bottom: 8px; }
.editor-area li { margin-bottom: 3px; }
.editor-area blockquote { border-left: 3px solid var(--accent); padding-left: 16px; color: var(--text-secondary); margin: 12px 0; font-style: italic; }
.editor-area code { background: var(--bg-tertiary); padding: 2px 7px; border-radius: 4px; font-size: 13px; color: #a5c9ff; font-family: 'JetBrains Mono', monospace; }
.editor-area pre { background: var(--bg-secondary); padding: 16px; border-radius: 8px; overflow-x: auto; margin-bottom: 12px; }
.editor-area pre code { background: none; padding: 0; }
.editor-area hr { border: none; border-top: 1px solid var(--border); margin: 16px 0; }
/* Preview mode */
.preview-mode .editor-area { outline: none; min-height: 400px; line-height: 1.75; }
.preview-mode .toolbar { display: none; }
/* Modal */
.modal-overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.6);
backdrop-filter: blur(4px);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
animation: fadeIn 150ms ease forwards;
}
.modal {
background: var(--bg-secondary);
border-radius: 16px;
padding: 32px;
width: 480px;
max-width: 90vw;
box-shadow: var(--shadow);
transform: translateY(10px);
animation: slideUp 200ms ease forwards;
}
.modal-title { font-size: 16px; font-weight: 600; margin-bottom: 20px; color: var(--text-primary); }
.modal-input {
width: 100%;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 10px 14px;
color: var(--text-primary);
font-size: 13.5px;
outline: none;
transition: var(--transition);
margin-bottom: 12px;
}
.modal-input:focus { border-color: rgba(124,92,255,0.4); }
.modal-input::placeholder { color: var(--text-muted); }
.modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; }
.btn-primary {
background: var(--accent);
color: white;
border: none;
padding: 9px 20px;
border-radius: 8px;
font-size: 13.5px;
font-weight: 500;
cursor: pointer;
transition: var(--transition);
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-secondary {
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
padding: 9px 20px;
border-radius: 8px;
font-size: 13.5px;
cursor: pointer;
transition: var(--transition);
}
.btn-secondary:hover { background: var(--bg-hover); color: var(--text-primary); }
/* Tags */
.tags-container { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.tag-chip {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 10px;
background: rgba(124,92,255,0.15);
border-radius: 6px;
font-size: 12px;
color: var(--accent);
}
.tag-chip .tag-dot { width: 5px; height: 5px; background: currentColor; border-radius: 50%; }
/* Animations */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
/* Toast */
.toast-container { position: fixed; bottom: 24px; right: 24px; z-index: 2000; display: flex; flex-direction: column; gap: 8px; }
.toast {
background: var(--bg-tertiary);
border: 1px solid var(--border);
padding: 12px 18px;
border-radius: 10px;
font-size: 13.5px;
color: var(--text-primary);
box-shadow: var(--shadow);
animation: toastIn 250ms ease, toastOut 200ms ease 2.8s forwards;
}
@keyframes toastIn { from { transform: translateX(40px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes toastOut { from { opacity: 1; } to { opacity: 0; } }
/* Context menu */
.context-menu {
position: fixed;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 10px;
padding: 6px;
box-shadow: var(--shadow);
z-index: 999;
min-width: 180px;
}
.context-menu-item {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 14px;
border-radius: 6px;
cursor: pointer;
font-size: 13.5px;
color: var(--text-secondary);
transition: var(--transition);
}
.context-menu-item:hover { background: var(--bg-hover); color: var(--text-primary); }
/* Responsive */
@media (max-width: 768px) {
.app { grid-template-columns: 1fr; grid-template-rows: 48px auto 1fr; }
.sidebar { display: none; }
.editor-container { padding: 20px 16px 40px; }
}
/* Selection */
::selection { background: rgba(124,92,255,0.3); }
</style>
</head>
<body>
<div class="app">
<!-- Top Bar -->
<div class="topbar">
<div class="topbar-left">
<div class="logo">
<div class="logo-icon">
<svg viewBox="0 0 24 24" fill="none"><path d="M3.5 18.5L12 6l8.5 12.5" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/></svg>
</div>
Lumina
</div>
<input class="search-input" type="text" placeholder="Search notes..." id="searchInput">
</div>
<div class="topbar-right">
<button class="icon-btn" onclick="showModal('settings')" title="Settings">⚙</button>
<button class="icon-btn" onclick="togglePreview()" title="Toggle Preview" id="previewBtn">◀▶</button>
</div>
</div>
<!-- Sidebar -->
<div class="sidebar" id="sidebar">
<ul class="folder-list" id="folders">
<li class="folder-item active" data-id="inbox">
<span class="folder-icon">📥</span><span class="folder-name">Inbox</span>
<span class="count" id="folder-count-inbox">0</span>
</li>
<li class="folder-item" data-id="personal">
<span class="folder-icon">👤</span><span class="folder-name">Personal</span>
<span class="count" id="folder-count-personal">0</span>
</li>
<li class="folder-item" data-id="work">
<span class="folder-icon">💼</span><span class="folder-name">Work</span>
<span class="count" id="folder-count-work">0</span>
</li>
</ul>
<div class="sidebar-section">
<div class="section-header">
All Notes
<span style="font-size:16px;">+</span>
</div>
<ul class="folder-list" id="noteList"></ul>
</div>
</div>
<!-- Main Content -->
<div class="main" id="main">
<div class="empty-state" id="emptyState">
<svg class="empty-icon" viewBox="0 0 24 24" fill="none"><path d="M12 3L5.5 9H18.5L12 3ZM5.5 9L9 14L6.5 20L3.5 17L6.5 12L5.5 9ZM12 9L18.5 3L21.5 6L18 10L21.5 14L20 16.5L16.5 12L12 16.5L7.5 12L4 14.5L5.5 16.5L9 12L12 9Z" stroke="currentColor" stroke-width="1.2" fill="none"/></svg>
<span class="empty-title">No notes selected</span>
<span class="empty-desc">Click a note from the sidebar or create a new one to get started.</span>
</div>
<div class="editor-wrapper" id="editorWrapper" style="display:none;">
<div class="toolbar">
<button class="toolbar-btn" onclick="format('bold')" title="Bold (Cmd+B)">B</button>
<button class="toolbar-btn" onclick="format('italic')" title="Italic (Cmd+I)">I</button>
<button class="toolbar-btn" onclick="format('underline')" title="Underline (Cmd+U)">U</button>
<button class="toolbar-btn" onclick="format('strikethrough')" title="Strikethrough">S</button>
<div style="width:1px;height:20px;background:var(--border);"></div>
<button class="toolbar-btn" onclick="format('bulletList')" title="Bullet List">•</button>
<button class="toolbar-btn" onclick="format('orderedList')" title="Ordered List">1.</button>
<button class="toolbar-btn" onclick="format('blockquote')" title="Quote">"</button>
<div style="width:1px;height:20px;background:var(--border);"></div>
<button class="toolbar-btn" onclick="format('codeBlock')" title="Code Block"></></button>
<button class="toolbar-btn" onclick="format('link')" title="Link">🔗</button>
</div>
<div class="editor-container">
<div class="note-header-area">
<input class="note-title-input" id="titleInput" placeholder="Untitled Note" maxlength="200"/>
<div class="note-date-display" id="dateDisplay"></div>
</div>
<textarea class="editor-area" id="editorArea" spellcheck="false" ondrop="dropFile(event)" ontouchstart=""></textarea>
<div style="margin-top: 20px; display:flex; justify-content:space-between;">
<div style="display:flex; gap:8px;">
<input class="modal-input" id="tagInput" placeholder="Add tag..." onkeydown="handleTagKey(event)" style="max-width:180px;" />
<span id="tagsDisplay"></span>
</div>
<div style="display:flex; gap:8px;">
<button class="icon-btn" onclick="deleteNote()" title="Delete (Del)">🗑</button>
<button class="btn-secondary" onclick="saveNote()">💾 Save</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal-overlay" id="modalOverlay" style="display:none;" onclick="closeModal(event)">
<div class="modal">
<div class="modal-title" id="modalTitle"></div>
<input class="modal-input" id="modalInput" placeholder="" onkeydown="if(event.key==='Enter')saveFromModal()"/>
<div class="modal-actions" id="modalActions"></div>
</div>
</div>
<!-- Context Menu -->
<div class="context-menu" id="contextMenu" style="display:none;" onclick="closeContextMenu()">
<div class="context-menu-item" onclick="createNoteFromContext()">📝 Create Note Here</div>
<div class="context-menu-item" onclick="copyLink('note')">🔗 Copy Link</div>
<div class="context-menu-item" style="color:var(--text-muted);" onclick="closeContextMenu()">Close</div>
</div>
<!-- Toast Container -->
<div class="toast-container" id="toasts"></div>
<script>
// State
let notes = JSON.parse(localStorage.getItem('lumina-notes') || '[]');
let activeNoteId = null;
let currentFolder = 'inbox';
const FOLDER_ICONS = { inbox: '📥', personal: '👤', work: '💼' };
const FOLDER_COLORS = { inbox: 'dot-blue', personal: 'dot-green', work: 'dot-yellow' };
const FOLDER_LABELS = { inbox: 'Inbox (Default)', personal: 'Personal', work: 'Work' };
// Init
function init() {
if (!notes.length) seedSampleNotes();
renderFolders();
renderNoteList();
}
function seedSampleNotes() {
const now = () => new Date().toISOString().split('T')[0];
notes = [
{ id: 'n1', folder: 'inbox', title: 'Project Brainstorm Ideas', date: now(), content: '<p>Here are some ideas I want to explore this quarter:</p><ul><li>New mobile app for team collaboration</li><li>AI-powered analytics dashboard</li><li>Open-source design system toolkit</li></ul><blockquote>Remember: focus on user value first, technology second.</blockquote>', tags: ['ideas', '2025Q1'], starred: true },
{ id: 'n2', folder: 'work', title: 'Sprint Planning Notes', date: now(), content: '<h2>Sprint Goals</h2><p>Complete authentication module and API integration.</p><ol><li>Set up Auth0 configuration</li><li>Implement OAuth flow</li><li>Create user profile endpoints</li></ol>', tags: ['work'], starred: false },
{ id: 'n3', folder: 'personal', title: 'Book Recommendations 2025', date: now(), content: '<h2>This quarter I want to read:</h2><p>1. <em>The Creative Act</em> - Rick Rubin<br/>2. <em>Devil in the Detail</em> by Peter Sims<br/>3. Something on behavioral design patterns', tags: ['reading'], starred: true },
{ id: 'n4', folder: 'inbox', title: 'Meeting Notes — Design Review', date: now(), content: '<p><strong>Date:</strong> Today</p><h2>Action Items</h2><ul><li>Rewire navigation component to new design token system</li><li>Update accessibility audit report</li></ul>', tags: ['meetings'], starred: false },
{ id: 'n5', folder: 'work', title: 'Architecture Decision Record — API Gateway', date: now(), content: '<h2>Context</h2><p>We need to route all external traffic through a central gateway for monitoring and rate limiting.</p><h3>Proposed Solution</h3><pre><code class="language-json">{\n "gateway": "kong\",\n "rate_limit": 100,\n "cors_enabled": true\n}</code></pre>', tags: ['architecture', 'api'], starred: false },
{ id: 'n6', folder: 'personal', title: 'Morning Routine — Draft', date: now(), content: '<ul><li>Wake up at 6:30</li><li>Meditation (10 min)</li><li>Morning pages / journaling (15 min)</li><li>Exercise session (45 min)</li></ul>', tags: ['habits'], starred: true },
{ id: 'n7', folder: 'inbox', title: 'Random Thoughts & Inspiration', date: now(), content: '<p>"The best way to predict the future is to create it."</p><hr/><p>Notes about upcoming events and reminders floating here...</p>', tags: [], starred: false },
{ id: 'n8', folder: 'work', title: 'Q1 OKRs Draft', date: now(), content: '<h2>Objectives & Key Results</h2><h3>O1: Improve platform performance</h3><p>KR1: Reduce page load time by 40%</p><p>KR2: Achieve 99.5% uptime SLA</p>', tags: ['okr', 'goals'], starred: false },
{ id: 'n9', folder: 'inbox', title: 'Recipe Collection — Try This Week', date: now(), content: '<h2>This week:</h2><ol><li>Pasta aglio e olio with chili flakes</li><li>Avocado toast variations</li></ol>', tags: ['recipes'], starred: false },
{ id: 'n10', folder: 'personal', title: 'Travel Wishlist 2025-2026', date: now(), content: '<ul><li>🇯🇵 Tokyo — Spring (sakura season)</li><li>🇮🇹 Florence — Autumn</li><li>🇨🇳 Shanghai — Winter</li></ul>', tags: ['travel'], starred: true },
];
saveNotes();
}
function saveNotes() {
localStorage.setItem('lumina-notes', JSON.stringify(notes));
}
// Folder switching
function renderFolders() {
for (const folder of Object.keys(FOLDER_ICONS)) {
const el = document.querySelector(`.folder-item[data-id="${folder}"]`);
if (!el) continue;
const count = notes.filter(n => n.folder === folder).length;
document.getElementById(`folder-count-${folder}`).textContent = count;
}
for (const folder of Object.keys(FOLDER_ICONS)) {
const el = document.querySelector(`.folder-item[data-id="${folder}"]`);
if (!el) continue;
el.addEventListener('click', () => switchFolder(folder));
}
}
function switchFolder(folder) {
currentFolder = folder;
renderNoteList();
// Update active state
document.querySelectorAll('.folder-item').forEach(el => el.classList.toggle('active', el.dataset.id === folder));
if (currentFolder !== 'all' && !notes.find(n => n.id === activeNoteId)) {
showNote(activeNoteId);
}
}
function renderNoteList() {
const container = document.getElementById('noteList');
let filteredNotes;
const query = document.getElementById('searchInput').value.toLowerCase().trim();
if (currentFolder === 'all') {
filteredNotes = notes.slice();
} else {
filteredNotes = notes.filter(n => n.folder === currentFolder);
}
// Sort by date desc, starred first
filteredNotes.sort((a, b) => {
if (a.starred !== b.starred) return a.starred ? -1 : 1;
return new Date(b.date) - new Date(a.date);
});
container.innerHTML = filteredNotes.map(n => `
<li class="note-item ${n.id === activeNoteId ? 'active' : ''}" data-id="${n.id}">
<div class="note-header" onclick="showNote('${n.id}')">
<span class="note-dot ${FOLDER_COLORS[n.folder]}"></span>
<div class="note-title-row">
<span class="note-name">${esc(n.title)}</span>
</div>
</div>
<div class="note-meta" onclick="event.stopPropagation(); toggleStar('${n.id}')">
${n.starred ? '⭐' : '☆'}
<span>${shortDate(n.date)}</span>
${n.tags.length ? n.tags.join(' · ') : ''}
</div>
<div class="note-excerpt">${esc(n.content).replace(/<[^>]+>/g, '').slice(0, 140) + (notes.find(x => x.id === n.id)?.content?.length > 150 ? '...' : '')}</div>
</li>
`).join('');
container.querySelectorAll('.note-item').forEach(el => {
el.addEventListener('click', () => showNote(el.dataset.id));
});
}
// Note display
function showNote(id) {
activeNoteId = id;
const note = notes.find(n => n.id === id);
if (!note) return;
document.getElementById('emptyState').style.display = 'none';
const wrapper = document.getElementById('editorWrapper');
wrapper.style.display = '';
document.getElementById('titleInput').value = note.title || '';
document.getElementById('editorArea').value = note.content || '';
document.getElementById('dateDisplay').textContent = shortDate(note.date);
updateTagsDisplay();
// Update active state in list
renderNoteList();
}
function deleteNote() {
if (!activeNoteId) return;
const idx = notes.findIndex(n => n.id === activeNoteId);
if (idx === -1) return;
const removed = notes.splice(idx, 1)[0];
saveNotes();
renderFolders();
renderNoteList();
showToast(`${removed.title} deleted`, '2000');
if (activeNoteId !== null) {
activeNoteId = null;
document.getElementById('emptyState').style.display = '';
document.getElementById('editorWrapper').style.display = 'none';
}
}
function toggleStar(id) {
const note = notes.find(n => n.id === id);
if (!note) return;
note.starred = !note.starred;
saveNotes();
renderNoteList();
if (activeNoteId === id) updateTagsDisplay();
}
// Editor
function format(cmd, value = null) {
document.execCommand(cmd, false, value);
const area = document.getElementById('editorArea');
area.focus();
}
function saveNote() {
const title = document.getElementById('titleInput').value.trim();
let content = document.getElementById('editorArea').value;
// Basic cleanup: wrap loose <p> that aren't inside list/blockquote/code in proper tags
if (!content.trim()) return showToast('Nothing to save', '1500');
const idx = notes.findIndex(n => n.id === activeNoteId);
let note = notes[idx];
if (!note) {
note = createNote({ title, content });
} else {
note.title = title;
note.content = content;
saveNotes();
renderFolders();
}
// Extract tags from content if any
const tagMatch = content.match(/#(\w+)/g);
if (tagMatch) {
note.tags = [...new Set(tagMatch.map(m => m.slice(1)))];
document.getElementById('tagsDisplay').innerHTML = note.tags.map(t => `<span class="tag-chip"><span class="tag-dot"></span>${esc(t)}</span>`).join('');
}
saveNotes();
renderNoteList();
}
// Tag handling
function updateTagsDisplay() {
const note = notes.find(n => n.id === activeNoteId);
if (!note) return;
document.getElementById('tagsDisplay').innerHTML = note.tags.map(t => `<span class="tag-chip"><span class="tag-dot"></span>${esc(t)}</span>`).join('');
}
function handleTagKey(e) {
const input = e.target;
if (e.key === 'Enter' || e.key === ',') {
e.preventDefault();
const val = input.value.trim();
if (!val) return;
saveNoteFromTagsInput();
updateTagsDisplay();
} else if (e.key === 'Backspace' && !input.value) {
// remove last tag
let note = notes.find(n => n.id === activeNoteId);
if (!note) return;
const idx = note.tags.lastIndexOf(input.previousValue || '');
if (idx !== -1) note.tags.splice(idx, 1);
input.previousValue = null;
saveNotes();
updateTagsDisplay();
}
}
function addTag(val) {
let note = notes.find(n => n.id === activeNoteId);
if (!note) return;
note.tags = [...new Set([val, ...note.tags])];
note.content += `\n#${val}`;
saveNotes();
renderNoteList();
updateTagsDisplay();
}
function removeTag(val) {
let note = notes.find(n => n.id === activeNoteId);
if (!note) return;
// Update content to remove #tag line
const lines = note.content.split('\n');
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith(`#${val}`)) {
lines.splice(i, 1);
break;
}
}
note.content = lines.join('\n').trim() + '\n';
note.tags = note.tags.filter(t => t !== val);
saveNotes();
renderNoteList();
}
function saveNoteFromTagsInput() {
const tag = document.getElementById('tagInput').value.trim();
if (!tag) return;
addTag(tag);
}
// Context menu
let contextTargetId = null;
document.addEventListener('contextmenu', (e) => {
e.preventDefault();
// Don't show if clicking on an element
if (e.target.closest('.note-item') || e.target.closest('#editorWrapper')) return;
contextTargetId = e.clientY + ',' + e.clientX;
const rect = document.querySelector('.main').getBoundingClientRect();
const x = Math.min(e.clientX, rect.width - 180);
const y = e.clientY - rect.top;
document.getElementById('contextMenu').style.left = x + 'px';
document.getElementById('contextMenu').style.top = (y < 24 ? 24 : y) + 'px';
document.getElementById('contextMenu').style.display = '';
});
function closeContextMenu() {
document.getElementById('contextMenu').style.display = 'none';
}
document.addEventListener('click', () => closeContextMenu());
function createNoteFromContext() {
const x = contextTargetId.split(',');
const y = parseInt(x[1]) + 40;
const rect = document.querySelector('.main').getBoundingClientRect();
// Insert after editor wrapper
if (activeNoteId) return showToast('Cannot insert here', '1500');
const titleInput = document.getElementById('titleInput');
const textarea = document.getElementById('editorArea');
const dateDisplay = document.getElementById('dateDisplay');
activeNoteId = 'ctx-' + Date.now();
notes.unshift({ id: activeNoteId, folder: currentFolder || 'inbox', title: '', content: '<p>Double-click to edit...</p>', tags: [], starred: false, date: new Date().toISOString() });
const editorContainer = document.getElementById('editorWrapper');
// Move all children before the empty state
while (document.getElementById('emptyState').children.length) {
editorContainer.insertBefore(document.getElementById('emptyState').firstChild, document.getElementById('emptyState'));
}
titleInput.focus();
setTimeout(() => textarea.click(), 50);
}
function copyLink(type) {
const url = type === 'note' ? (activeNoteId ? '#/notes/' + activeNoteId : '') : '';
// In a real app this would use navigator.clipboard; just show toast for prototype
showToast('Link copied to clipboard', '2000');
}
// Modal
function showModal(title) {
document.getElementById('modalOverlay').style.display = 'flex';
const modalTitle = document.getElementById('modalTitle');
if (title === 'settings') {
modalTitle.textContent = 'Settings';
document.getElementById('modalInput').value = '';
document.getElementById('modalActions').innerHTML = `
<button class="btn-secondary" onclick="showModal()">Cancel</button>
<button class="btn-primary" onclick="closeModal(event)">Save</button>
`;
} else {
modalTitle.textContent = title;
document.getElementById('modalInput').value = '';
document.getElementById('modalActions').innerHTML = `
<button class="btn-secondary" onclick="showModal()">Cancel</button>
<button class="btn-primary" onclick="saveFromModal(); closeModal(event)">Create</button>
`;
}
}
function closeModal(e) {
if (e && e.target !== document.getElementById('modalOverlay')) return;
document.getElementById('modalOverlay').style.display = 'none';
}
function saveFromModal() {
const input = document.getElementById('modalInput');
const val = input.value.trim();
if (!val) return;
// Create new note in current folder
addNote(val, currentFolder);
closeModal();
}
function addNote(title, folder) {
const now = new Date().toISOString();
notes.unshift({ id: 'new-' + Date.now(), folder: folder || 'inbox', title, content: '<p>Start writing...</p>', tags: [], starred: false, date: now });
saveNotes();
renderFolders();
}
// Preview toggle
let isPreview = false;
function togglePreview() {
isPreview = !isPreview;
document.getElementById('editorWrapper').classList.toggle('preview-mode', isPreview);
const btn = document.getElementById('previewBtn');
btn.textContent = isPreview ? '✏️' : '◀▶';
}
// Utilities
function esc(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
function shortDate(iso) { const d = new Date(iso); return `${d.getMonth()+1}/${d.getDate()}`; }
function showToast(msg, duration = 2000) {
const container = document.getElementById('toasts');
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = msg;
container.appendChild(toast);
setTimeout(() => toast.remove(), duration + 350);
}
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'b') { format('bold'); e.preventDefault(); }
if ((e.ctrlKey || e.metaKey) && e.key === 'i') { format('italic'); e.preventDefault(); }
if ((e.ctrlKey || e.metaKey) && e.key === 'u') { format('underline'); e.preventDefault(); }
// Ctrl/Cmd + Enter to save
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter' && !e.target.isContentEditable && document.activeElement.tagName !== 'INPUT' && document.activeElement.id !== 'searchInput') {
e.preventDefault();
saveNote();
}
// Delete to close context menu
if (e.key === 'Escape') closeContextMenu();
// Ctrl/Cmd + Shift + N for new note modal
if ((e.ctrlKey || e.metaKey) && !e.shiftKey && e.key.toLowerCase() === 'n' && document.activeElement.id !== 'searchInput') {
e.preventDefault();
showModal('New Note');
}
// Ctrl/Cmd + O to search
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'o' && !document.querySelector('.modal-overlay').style.display || document.getElementById('modalOverlay').style.display !== 'flex') {
const input = document.getElementById('searchInput');
if (input) { input.focus(); input.select(); }
}
// Ctrl/Cmd + Backspace to delete note
if ((e.ctrlKey || e.metaKey) && !e.shiftKey && e.key === 'Backspace' && activeNoteId) {
e.preventDefault();
deleteNote();
}
});
// Search filter
document.getElementById('searchInput').addEventListener('input', () => renderNoteList());
// File drop
function dropFile(e) {
e.preventDefault();
const files = e.dataTransfer.files;
if (files.length === 1 && files[0].name.endsWith('.md')) {
const note = notes.find(n => n.id === activeNoteId);
if (!note) return showToast('No note open to import', '1500');
note.content += `\n\n---\nImported from ${files[0].name}\n`;
saveNotes();
renderNoteList();
updateTagsDisplay();
} else if (files.length > 0) {
// Just read text content as notes
for (const file of files) {
const reader = new FileReader();
reader.onload = () => {
let note = notes.find(n => n.id === activeNoteId);
if (!note) return;
note.content += `\n\n---\nImported from ${file.name}\n${reader.result.slice(0, 500)}`;
saveNotes();
renderNoteList();
};
reader.readAsText(file);
}
} else {
showToast('No file to import', '1200');
}
}
// Init everything
init();
</script>
</body>
</html>
<!-- agent-meta {"model":"ornith-1.0-9b","provider":"lmstudio","persona":"middle","sessionId":"9c9ec086-1f71-4865-8bee-f4b065857e3f","tokensIn":25829,"tokensOut":11539,"tokensTotal":37368,"turns":3,"toolCalls":2,"failedToolCalls":0,"timestamp":"2026-07-13T18:20:16.844Z"} -->