Notes
ornith 1.0 9b · basic

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 40,337 bytes · SHA-256 4df47e22aa48
<!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: #ffffff;
--bg-secondary: #f7f8fa;
--bg-tertiary: #eef0f4;
--text-primary: #1a1a2e;
--text-secondary: #556079;
--text-muted: #8b98aa;
--accent: #6c5ce7;
--accent-light: rgba(108, 92, 231, 0.1);
--border: #e2e6ea;
--sidebar-width: 260px;
--right-panel-width: 280px;
--radius: 10px;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.04);
--shadow-md: 0 4px 16px rgba(0,0,0,0.06);
--hover-bg: rgba(108,92,231,0.05);
--active-border: #6c5ce7;
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: var(--font-body);
background: var(--bg-primary);
color: var(--text-primary);
height: 100vh;
overflow: hidden;
}
/* --- Layout --- */
.app-container {
display: grid;
grid-template-columns: var(--sidebar-width) 1fr var(--right-panel-width);
min-height: 100vh;
}
.left-sidebar, .editor-area {
background: var(--bg-primary);
border-right: 1px solid var(--border);
}
.editor-area {
overflow-y: auto;
padding: 2rem 3rem;
}
.right-panel {
background: var(--bg-secondary);
border-left: 1px solid var(--border);
overflow-y: auto;
}
/* --- Scrollbar --- */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--bg-tertiary); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #d4d9e1; }
/* --- Sidebar Items --- */
.sidebar-header {
padding: 20px 20px 16px;
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
font-size: 1.3rem;
font-weight: 700;
color: var(--accent);
letter-spacing: -0.5px;
}
.search-box {
flex: 1;
margin: 12px 16px 24px;
position: relative;
}
.search-box input {
width: 100%;
padding: 9px 14px 9px 38px;
border: 1.5px solid var(--border);
border-radius: 8px;
font-size: 0.85rem;
color: var(--text-primary);
background: var(--bg-secondary);
transition: all 0.2s;
outline: none;
}
.search-box input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-light);
}
.search-box::before {
content: '🔍';
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
font-size: 0.8rem;
}
/* --- Note Group --- */
.note-group {
border-left: 3px solid var(--accent);
margin: 0 16px 24px;
}
.group-header {
padding: 16px 18px 8px;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-muted);
font-weight: 600;
}
.note-item {
padding: 12px 18px;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s;
position: relative;
}
.note-item:hover {
background: var(--hover-bg);
}
.note-item.active {
background: var(--active-border);
color: white;
}
.note-title {
font-size: 0.9rem;
font-weight: 600;
margin-bottom: 4px;
line-height: 1.35;
}
.note-item.active .note-title { color: white; }
.note-preview {
font-size: 0.8rem;
color: var(--text-secondary);
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.note-item.active .note-preview { color: rgba(255,255,255,0.7); }
/* --- Add Note Button --- */
.add-note-btn {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
color: var(--text-secondary);
}
.add-note-btn:hover {
background: var(--hover-bg);
color: var(--accent);
}
.note-item.active + .add-note-btn,
.add-note-btn:hover:not(.active) {
color: var(--accent);
}
.btn-icon { font-size: 1.2rem; opacity: 0.5; transition: opacity 0.2s; }
.add-note-btn:hover .btn-icon { opacity: 1; }
/* --- Editor Area --- */
.editor-area { background: var(--bg-primary); }
.editor-header {
padding-bottom: 1rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--border);
}
.breadcrumb {
font-size: 0.8rem;
color: var(--text-muted);
margin-bottom: 4px;
}
.note-title-editor {
width: 100%;
border: none;
font-size: 2rem;
font-weight: 700;
color: var(--text-primary);
background: transparent;
padding: 4px 0;
outline: none;
line-height: 1.3;
letter-spacing: -0.5px;
}
.note-meta {
display: flex;
gap: 1rem;
margin-top: 8px;
font-size: 0.78rem;
color: var(--text-muted);
}
.meta-tag { padding: 2px 8px; border-radius: 6px; background: var(--accent-light); color: var(--accent); font-weight: 500; }
.meta-date { opacity: 0.7; }
/* --- Markdown Editor --- */
#markdown-input {
width: 100%;
min-height: calc(100vh - 280px);
border: none;
padding: 1rem 0;
font-family: var(--font-mono);
font-size: 0.95rem;
line-height: 1.75;
color: var(--text-primary);
background: transparent;
resize: none;
outline: none;
}
/* --- Rendered Output --- */
#output-panel {
display: none;
padding-bottom: 2rem;
}
.render-block { margin-bottom: 1.5em; }
h1, h2, h3, h4 { color: var(--text-primary); line-height: 1.3; margin-top: 0.8em; }
h1 { font-size: 2rem; border-bottom: 2px solid var(--border); padding-bottom: 8px; }
h2 { font-size: 1.5rem; border-left: 4px solid var(--accent); padding-left: 12px; margin-top: 0.6em; }
h3 { font-size: 1.2rem; color: var(--text-secondary); }
p { line-height: 1.75; color: var(--text-primary); }
.code-block {
background: #f4f5f8;
border-radius: 8px;
padding: 16px;
font-size: 0.9rem;
overflow-x: auto;
border: 1px solid var(--border);
margin-bottom: 1em;
}
.code-block code { font-family: var(--font-mono); color: #d63384; }
pre code { background: none; padding: 0; }
ul, ol { padding-left: 1.5rem; line-height: 1.75; }
li { margin-bottom: 0.2em; }
blockquote {
border-left: 3px solid var(--accent);
background: var(--accent-light);
padding: 12px 16px;
margin: 0.8em 0;
font-size: 0.95rem;
color: var(--text-secondary);
border-radius: 0 8px 8px 0;
}
a { color: var(--accent); text-decoration: none; border-bottom: 1px dashed transparent; transition: all 0.2s; }
a:hover { border-bottom-color: var(--accent); }
img { max-width: 100%; border-radius: 8px; margin: 1em 0; }
hr { border: none; border-top: 2px solid var(--border); margin: 1.5em 0; }
table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid var(--border); }
th { color: var(--text-secondary); font-weight: 600; background: var(--bg-secondary); }
/* --- Right Panel --- */
.panel-header {
padding: 24px 24px 16px;
border-bottom: 1px solid var(--border);
margin-bottom: 1rem;
}
.panel-title { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 1px; color: var(--text-muted); font-weight: 600; }
/* --- Tags Section --- */
.tag-section { padding: 24px; border-bottom: 1px solid var(--border); margin-bottom: 1rem; }
.add-tag-input { width: 100%; padding: 8px 14px; border: 1.5px solid var(--border); border-radius: 8px; font-size: 0.85rem; color: var(--text-primary); outline: none; background: var(--bg-secondary); margin-bottom: 12px; transition: all 0.2s; }
.add-tag-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-light); }
.tag-list { display: flex; flex-wrap: wrap; gap: 8px; }
.tag-chip { padding: 5px 14px; background: var(--accent-light); color: var(--accent); border-radius: 20px; font-size: 0.8rem; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; gap: 6px; }
.tag-chip:hover { background: var(--accent) !important; color: white !important; }
/* --- Metadata Section --- */
.meta-row { padding: 8px 0; font-size: 0.85rem; }
.meta-label { display: flex; align-items: center; gap: 6px; margin-bottom: 2px; color: var(--text-muted); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px; }
/* --- Create Modal --- */
.modal-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.4); backdrop-filter: blur(4px);
display: flex; align-items: center; justify-content: center; z-index: 1000; opacity: 0; pointer-events: none; transition: opacity 0.3s;
}
.modal-overlay.open { opacity: 1; pointer-events: auto; }
.modal-card {
background: var(--bg-primary); border-radius: 16px; padding: 2rem; width: 480px; max-height: 70vh; overflow-y: auto; box-shadow: var(--shadow-md); transform: scale(0.95); transition: transform 0.3s;
}
.modal-overlay.open .modal-card { transform: scale(1); }
.modal-title { font-size: 1.2rem; font-weight: 700; color: var(--text-primary); margin-bottom: 0.5rem; }
.modal-subtitle { font-size: 0.85rem; color: var(--text-muted); margin-bottom: 1.5rem; }
.form-group { margin-bottom: 1.2rem; }
.form-label { display: block; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); margin-bottom: 6px; font-weight: 600; }
.form-input, .form-textarea {
width: 100%; padding: 10px 14px; border: 1.5px solid var(--border); border-radius: 8px; font-size: 0.9rem; color: var(--text-primary); background: var(--bg-secondary); outline: none; transition: all 0.2s;
font-family: var(--font-body);
}
.form-input:focus, .form-textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-light); }
.form-textarea { min-height: 80px; resize: vertical; line-height: 1.5; }
/* --- Action Buttons --- */
.btn {
padding: 10px 24px; border-radius: 8px; font-size: 0.9rem; font-weight: 600; cursor: pointer; transition: all 0.2s; border: none;
}
.btn-primary { background: var(--accent); color: white; }
.btn-primary:hover { background: #5a4bd7; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(108,92,231,0.3); }
.btn-outline { background: transparent; color: var(--text-secondary); border: 1.5px solid var(--border); margin-left: 8px; }
.btn-outline:hover { border-color: var(--accent); color: var(--accent); }
/* --- Editor Toolbar --- */
.editor-toolbar { display: flex; gap: 2px; padding: 6px 0; }
.toolbar-btn {
width: 34px; height: 34px; border-radius: 6px; border: none; background: transparent; cursor: pointer; font-size: 1rem; color: var(--text-secondary); display: flex; align-items: center; justify-content: center; transition: all 0.2s;
}
.toolbar-btn:hover { background: var(--hover-bg); color: var(--accent); }
/* --- Empty State --- */
.empty-state {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center;
display: none;
}
.empty-icon { font-size: 3rem; margin-bottom: 1rem; opacity: 0.4; }
.empty-text { color: var(--text-muted); font-size: 0.95rem; line-height: 1.6; }
.empty-sub { color: var(--text-muted); font-size: 0.85rem; margin-top: 4px; opacity: 0.7; }
/* --- Quick Actions (sidebar bottom) --- */
.quick-actions { padding: 20px 16px; border-top: 1px solid var(--border); font-size: 0.85rem; }
.qa-item { display: flex; align-items: center; gap: 10px; padding: 8px 0; color: var(--text-secondary); cursor: pointer; transition: all 0.2s; border-left: 3px solid transparent; margin-left: -16px; }
.qa-item:hover { color: var(--accent); background: var(--hover-bg); }
/* --- Toast --- */
.toast-container { position: fixed; bottom: 24px; right: 24px; z-index: 2000; display: flex; flex-direction: column; gap: 8px; }
.toast { background: #1a1a2e; color: white; padding: 12px 20px; border-radius: 10px; font-size: 0.85rem; box-shadow: var(--shadow-md); animation: slideIn 0.3s ease forwards; opacity: 0; transform: translateY(20px); }
.toast.show { opacity: 1; transform: translateY(0); animation: fadeOut 0.4s ease 2.6s forwards; }
@keyframes slideIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
/* --- Selection styling for markdown input --- */
#markdown-input::selection { background: rgba(108,92,231,0.15); }
/* --- Code highlight colors --- */
.hljs-comment { color: #6a737d; font-style: italic; }
.hljs-keyword { color: #c73e43; }
.hljs-string { color: #032851; background: #f8f9fa; padding: 1px 4px; border-radius: 3px; }
.hljs-number { color: #0550ae; font-weight: 600; }
/* --- Responsive --- */
@media (max-width: 1200px) {
:root { --sidebar-width: 240px; --right-panel-width: 0; }
}
@media (max-width: 768px) {
.app-container { grid-template-columns: 1fr !important; }
.left-sidebar, .right-panel { display: none !important; }
.editor-area { padding: 1rem; overflow-y: auto !important; min-height: 100vh !important; border-right: none; }
}
/* --- Font loading --- */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
/* --- Tags with color coding --- */
.tag-color-1 { background: #e8f5e9 !important; color: #2e7d32 !important; }
.tag-color-2 { background: #e3f2fd !important; color: #1565c0 !important; }
.tag-color-3 { background: #fff3e0 !important; color: #ef6c00 !important; }
.tag-color-4 { background: #fce4ec !important; color: #c62828 !important; }
</style>
</head>
<body>
<div class="app-container">
<!-- LEFT SIDEBAR -->
<div class="left-sidebar">
<div class="sidebar-header">
<span class="logo">◈ Lumina Notes</span>
</div>
<div class="search-box">
<input type="text" placeholder="Search notes..." id="sidebar-search">
</div>
<div id="note-groups">
<!-- Dynamically rendered -->
</div>
<div class="add-note-btn" id="btn-add-note">
<span>+ New Note</span>
<span class="btn-icon">+</span>
</div>
<div class="quick-actions">
<div class="qa-item"><span style="font-size:1.2rem;">📋</span> Export Notes</div>
<div class="qa-item"><span style="font-size:1.2rem;">⚡</span> Quick Capture</div>
<div class="qa-item"><span style="font-size:1.2rem;">🌙</span> Toggle Theme</div>
</div>
</div>
<!-- EDITOR AREA -->
<div class="editor-area" id="editor-area">
<div id="empty-state" class="empty-state" style="display:block;">
<div class="empty-icon">◈</div>
<div class="empty-text">Select a note to start writing,<br>or create one below.</div>
<div class="empty-sub">Your thoughts, perfectly organized.</div>
</div>
<!-- Header -->
<div class="editor-header" id="header-section" style="display:none;">
<div class="breadcrumb" id="breadcrumb"></div>
<input type="text" class="note-title-editor" id="title-input" placeholder="Untitled Note">
<div class="note-meta" id="meta-info" style="display:none;"></div>
</div>
<!-- Toolbar -->
<div class="editor-toolbar" id="toolbar-section" style="display:none;">
<button class="toolbar-btn" title="Bold" onclick="insertMarkdown('**')"><b>B</b></button>
<button class="toolbar-btn" title="Italic" onclick="insertMarkdown('*')"><i>I</i></button>
<button class="toolbar-btn" title="Strikethrough" onclick="insertMarkdown('~~')"><s>S</s></button>
<div style="width:1px;background:var(--border);margin:0 4px;"></div>
<button class="toolbar-btn" title="Heading 1" onclick="insertMarkdown('# ')">H1</button>
<button class="toolbar-btn" title="Heading 2" onclick="insertMarkdown('## ')">H2</button>
<button class="toolbar-btn" title="Bullet List" onclick="insertMarkdown('- ')">•</button>
<button class="toolbar-btn" title="Numbered List" onclick="insertMarkdown('1. ')">1.</button>
<div style="width:1px;background:var(--border);margin:0 4px;"></div>
<button class="toolbar-btn" title="Code Block" onclick="insertMarkdown('```')"><code>⌨</code></button>
<button class="toolbar-btn" title="Blockquote" onclick="insertMarkdown('> ')">"</button>
<div style="width:1px;background:var(--border);margin:0 4px;"></div>
<button class="toolbar-btn" title="Link" onclick="insertMarkdown('[text](url)')">🔗</button>
<button class="toolbar-btn" title="Image" onclick="insertMarkdown('')">🖼️</button>
</div>
<!-- Markdown Input -->
<textarea id="markdown-input" placeholder="Write your thoughts here... # Title **Bold** and *italic* text - List item "># Welcome to Lumina Notes
Start by selecting or creating a note. You can write in **Markdown** format, use headings, code blocks, lists — everything you need for powerful notes.
</textarea>
<div id="output-panel"></div>
</div>
<!-- RIGHT PANEL -->
<div class="right-panel" style="display:none;" id="right-panel">
<div class="panel-header">
<span class="panel-title">Metadata</span>
</div>
<div class="tag-section">
<input type="text" class="add-tag-input" placeholder="Add a tag..." id="new-tag-input" style="margin-top:0;">
<div class="tag-list" id="note-tags"></div>
</div>
<div class="panel-header"><span class="panel-title">Properties</span></div>
<div id="meta-properties">
<div class="meta-row">
<div class="meta-label">Created</div>
<span id="prop-created"></span>
</div>
<div class="meta-row">
<div class="meta-label">Modified</div>
<span id="prop-modified"></span>
</div>
</div>
</div>
</div>
<!-- Create Modal -->
<div class="modal-overlay" id="create-modal">
<div class="modal-card">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem;">
<h3 class="modal-title" style="margin:0;">New Note</h3>
<button class="btn btn-outline" onclick="closeModal()" style="margin-left:auto;padding:6px 14px;font-size:0.8rem;">✕</button>
</div>
<div class="form-group">
<label class="form-label">Title</label>
<input type="text" class="form-input" id="modal-title" placeholder="Enter note title...">
</div>
<div class="form-group">
<label class="form-label">Group / Folder</label>
<select class="form-input" id="modal-folder" style="cursor:pointer;">
<option value="Inbox">📥 Inbox</option>
<option value="Ideas">💡 Ideas</option>
<option value="Projects">🚀 Projects</option>
<option value="Journal">📔 Journal</option>
</select>
</div>
<div class="form-group" style="margin-top:1rem;">
<label class="form-label">Quick Capture (Markdown)</label>
<textarea class="form-textarea" id="modal-content" placeholder="# Title Type your note content here..."></textarea>
</div>
<div style="display:flex;gap:8px;margin-top:1.5rem;">
<button class="btn btn-primary" onclick="createNoteFromModal()" style="flex:1;">Create Note</button>
</div>
</div>
</div>
<!-- Toast container -->
<div class="toast-container" id="toasts"></div>
<script>
// ==================== DATA STORE ====================
const defaultGroups = [
{ name: 'Inbox', notes: [], color: '#6c5ce7' },
{ name: 'Ideas', notes: [], color: '#00b894' },
{ name: 'Projects', notes: [], color: '#fdcb6e' },
{ name: 'Journal', notes: [], color: '#e17055' }
];
let allNotes = [];
let activeNoteId = null;
let searchFilter = '';
// ==================== INIT ====================
function init() {
// Load from localStorage if available
const stored = localStorage.getItem('lumina_notes');
if (stored) {
try { allNotes = JSON.parse(stored); } catch(e) {}
} else {
seedSampleData();
}
renderSidebar();
bindEvents();
}
function seedSampleData() {
const now = new Date().toISOString();
allNotes = [
{ id: 'n1', title: 'Getting Started Guide', content: '# Welcome to Lumina Notes\n\nThis is your **personal knowledge base**. Here\'s what you can do:\n\n## Features\n\n- ✍️ Write in rich **Markdown**\n- 📁 Organize notes into groups\n- 🔖 Tag your thoughts for easy retrieval\n- ⚡ Capture ideas instantly with the toolbar buttons\n- 🔍 Search across all your notes\n\n> "Notes are good. The best ones are the ones you\'ll actually read." — Naval Ravikant\n\n## Quick Tips\n\n1. Use `**bold**` and *italic* for emphasis\n2. Press **Ctrl+K** to search quickly\n3. Drag-and-drop files into notes (coming soon)\n4. Tag important notes with colored labels\n\n---\n\nBuilt with ❤️ using modern web technologies.',
group: 'Inbox', tags: ['getting-started'], created: new Date(Date.now() - 86400000).toISOString(), modified: now, starred: true },
{ id: 'n2', title: 'Project Roadmap Q1 2025', content: '# Project Roadmap\n\n## Current Sprint\n\n### Completed ✅\n- User authentication flow\n- Dashboard layout optimization\n- Dark mode implementation\n\n### In Progress 🔄\n- Analytics integration\n- API rate limiting\n\n### Planned 🔜\n- Real-time collaboration\n- Mobile PWA support\n\n> "The expert in anything was once a beginner." — Helen Hayes',
group: 'Projects', tags: ['roadmap','planning'], created: new Date(Date.now() - 172800000).toISOString(), modified: now, starred: false },
{ id: 'n3', title: 'Book Club Notes', content: '# Book Club Discussion\n\n## Reading "Atomic Habits" by James Clear\n\n### Key Takeaways\n\n> *"You do not rise to the level of your goals. You fall to the level of your systems."* — James Clear\n\n1. **Identity-based habits** are more powerful than goal-based ones\n2. Environment design matters more than willpower\n3. The 2-minute rule: start small, then expand\n4. Track your behavior — make it visible\n\n### Discussion Questions\n- How do you currently form new habits?\n- What environment changes could help you in 2025?\n',
group: 'Journal', tags: ['books','reading'], created: new Date(Date.now() - 604800000).toISOString(), modified: now, starred: true },
{ id: 'n4', title: '#! Daily Journal — October 15, 2024', content: '# Daily Reflection\n\n## Today\'s Highlights\n\n- Finished the new design system mockups\n- Had a great conversation about career growth at work\n- Started reading "Zero to One" by Peter Thiel\n\n> *"The best time to plant a tree was 20 years ago. The second best time is now."* — Chinese Proverb',
group: 'Journal', tags: ['reflection','daily'], created: new Date(Date.now() - 43200000).toISOString(), modified: now, starred: false },
{ id: 'n5', title: 'JavaScript Patterns', content: '# Common JS Patterns\n\n## Module Pattern\n```javascript\nconst myModule = (function() {\n let privateVar = "secret";\n return {\n publicMethod() {\n console.log(privateVar);\n }\n };\n})();\n```\n\n## Observer Pattern\n```javascript\nclass Observable {\n constructor() { this._observers = []; }\n subscribe(observer) { this._observers.push(observer); }\n notify(data) { this._observers.forEach(o => o.update(data)); }\n}\n```\n\n> *"Simplicity is the soul of efficiency."* — Austin Freeman',
group: 'Ideas', tags: ['coding','javascript'], created: new Date(Date.now() - 259200000).toISOString(), modified: now, starred: false },
{ id: 'n6', title: 'Travel Wishlist', content: '# Places to Visit\n\n## Bucket List\n\n- 🏔️ [ ] Patagonia, Argentina — Hiking & nature\n- 🌊 [ ] Amalfi Coast, Italy — Mediterranean vibes\n- 🏜️ [ ] Sahara Desert, Morocco — Camel trek\n- ⛰️ [ ] Banff National Park, Canada — Mountains & lakes\n- 🏝️ [ ] Okinawa, Japan — Island culture',
group: 'Inbox', tags: ['travel','wishlist'], created: new Date(Date.now() - 1209600000).toISOString(), modified: now, starred: true },
{ id: 'n7', title: 'Meeting Notes: Product Sync', content: '# Product Sync — Oct 14\n\n## Attendees\n- Sarah (PM), Mike (Dev), Lisa (Design)\n\n## Decisions\n\n1. MVP scope confirmed for Q1 launch\n2. Figma handoff by Friday\n3. Backend API docs needed before sprint planning\n\n## Action Items\n\n| Owner | Task | Deadline |\n|-------|------|----------|\n| Mike | Write API specs | Oct 20 |\n| Lisa | Finalize components | Oct 18 |\n| Sarah | Update roadmap | Oct 17 |\n',
group: 'Projects', tags: ['meetings','product'], created: new Date(Date.now() - 460800000).toISOString(), modified: now, starred: false },
{ id: 'n8', title: 'Recipe Collection', content: '# My Cookbook\n\n## Breakfast\n\n### Avocado Toast with Everything Seasoning\n```markdown
Ingredients:\n- 2 slices sourdough bread\n- 1 ripe avocado\n- Salt, pepper, everything seasoning\n\nSteps:\n1. Toast the bread until golden\n2. Mash avocado with a fork\n3. Spread on toast and season generously',
group: 'Journal', tags: ['recipes','food'], created: new Date(Date.now() - 9500000).toISOString(), modified: now, starred: false }
];
// Assign colors to groups that don't have them
const existingGroups = [];
defaultGroups.forEach(g => { if (!g.notes) g.notes = []; });
}
// ==================== RENDERING ====================
function renderSidebar() {
const container = document.getElementById('note-groups');
// Group notes by folder
const groupsByFolder = {};
allNotes.forEach(note => {
const group = note.group || 'Unfiled';
if (!groupsByFolder[group]) groupsByFolder[group] = [];
groupsByFolder[group].push(note);
});
// Also ensure Inbox exists for new notes
if (!allNotes.some(n => n.group === 'Inbox')) {
groupsByFolder['Inbox'] = [];
}
let html = '';
Object.entries(groupsByFolder).forEach(([groupName, groupNotes]) => {
const color = defaultGroups.find(g => g.name === groupName)?.color || '#8b98aa';
if (groupNotes.length === 0) return; // Skip empty groups
html += `<div class="note-group" style="border-left-color: ${color}">`;
html += `<div class="group-header" style="color:${color};">${groupName}</div>`;
groupNotes.forEach(note => {
const isActive = activeNoteId === note.id;
const preview = truncateMarkdown(note.content, 100);
html += `
<div class="note-item${isActive ? ' active' : ''}" data-id="${note.id}">`;
html += `<div class="note-title">${escapeHtml(note.title)}</div>`;
html += `<div class="note-preview">${escapeHtml(preview)}</div>`;
// Tags preview
if (note.tags && note.tags.length > 0) {
const tagPreview = note.tags.slice(0, 2).map(t => `#${t}`).join(' ');
html += `<span style="font-size:0.7rem;color:rgba(255,255,255,0.6);margin-left:4px;">${tagPreview}</span>`;
}
html += `</div>`;
});
html += '</div>';
});
// Add Unfiled if needed
const hasUnfiled = Object.keys(groupsByFolder).some(g => g === 'Unfiled');
container.innerHTML = html;
}
function truncateMarkdown(text, maxLen) {
let processed = text.replace(/^#+\s/gm, '').replace(/\*\*/g, '').replace(/`/g, '');
if (processed.length > maxLen) processed = processed.substring(0, maxLen - 3) + '...';
return processed;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// ==================== EDITOR FUNCTIONS ====================
function bindEvents() {
// Note selection
document.getElementById('note-groups').addEventListener('click', (e) => {
const noteItem = e.target.closest('.note-item');
if (!noteItem) return;
activeNoteId = noteItem.dataset.id;
renderSidebar();
loadNote(activeNoteId);
});
// Title input
document.getElementById('title-input').addEventListener('input', () => {
const note = findNoteById(activeNoteId);
if (note) updateNoteData(activeNoteId, { title: document.getElementById('title-input').value, modified: new Date().toISOString() });
});
// Markdown input → live render
const mdInput = document.getElementById('markdown-input');
const outputPanel = document.getElementById('output-panel');
function updateOutput() {
const content = mdInput.value;
const rendered = markdownToHtml(content);
if (activeNoteId) {
outputPanel.innerHTML = rendered;
outputPanel.style.display = 'block';
// Scroll to top on title change
if (!content.startsWith('#')) {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
} else {
outputPanel.style.display = 'none';
}
renderSidebar();
}
mdInput.addEventListener('input', updateOutput);
// Search
document.getElementById('sidebar-search').addEventListener('input', (e) => {
searchFilter = e.target.value.toLowerCase().trim();
renderSidebar();
});
// Add note button
document.getElementById('btn-add-note').addEventListener('click', () => {
openCreateModal();
});
// Tag input in right panel
document.getElementById('new-tag-input').addEventListener('input', (e) => {
if (e.target.value.length > 3) {
e.preventDefault();
const tag = e.target.value.trim().toLowerCase();
addTagToNote(tag);
e.target.value = '';
}
});
// Quick capture shortcut
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
const searchBox = document.querySelector('#sidebar-search input');
searchBox.focus();
searchBox.select();
}
});
// Theme toggle
document.querySelectorAll('.qa-item').forEach(item => {
if (item.textContent.includes('Toggle Theme')) {
item.addEventListener('click', () => {
const body = document.body;
body.classList.toggle('dark-theme');
renderSidebar();
});
}
});
}
function loadNote(id) {
activeNoteId = id;
if (!id) return;
const note = findNoteById(id);
if (!note) return;
// Show header
document.getElementById('empty-state').style.display = 'none';
document.getElementById('header-section').style.display = 'block';
document.getElementById('toolbar-section').style.display = 'flex';
// Load content into textarea
document.getElementById('title-input').value = note.title || '';
document.getElementById('markdown-input').value = note.content || '';
// Update metadata
const modifiedDate = new Date(note.modified).toLocaleDateString();
const createdDate = new Date(note.created).toLocaleDateString();
let metaHtml = `<span class="meta-date">📅 ${modifiedDate}</span>`;
if (note.starred) metaHtml += ' <span>⭐ Starred</span>';
document.getElementById('meta-info').innerHTML = metaHtml;
document.getElementById('meta-info').style.display = 'flex';
// Load tags
loadTags(note);
// Update properties
document.getElementById('prop-created').textContent = createdDate;
document.getElementById('prop-modified').textContent = modifiedDate;
// Show right panel
if (document.getElementById('right-panel').style.display === 'none') {
document.getElementById('right-panel').style.display = 'block';
}
renderSidebar();
}
function findNoteById(id) {
return allNotes.find(n => n.id === id);
}
function updateNoteData(id, fields) {
const note = findNoteById(id);
if (!note) return;
Object.assign(note, fields);
if (fields.modified) note.modified = new Date(fields.modified).toISOString();
else note.modified = new Date().toISOString();
// Save to localStorage
saveNotes();
}
function deleteNote(id) {
allNotes = allNotes.filter(n => n.id !== id);
saveNotes();
activeNoteId = null;
document.getElementById('empty-state').style.display = 'block';
document.getElementById('header-section').style.display = 'none';
document.getElementById('toolbar-section').style.display = 'none';
document.getElementById('markdown-input').value = '';
document.getElementById('output-panel').innerHTML = '';
renderSidebar();
}
function insertMarkdown(text) {
const input = document.getElementById('markdown-input');
const start = input.selectionStart;
const end = input.selectionEnd;
const selectedText = input.value.substring(start, end);
let insertText = text + (selectedText ? ' ' : '');
input.value = input.value.substring(0, start) + insertText + input.value.substring(end);
input.focus();
// Move cursor after inserted text
const newCursorPos = start + insertText.length;
setTimeout(() => { input.selectionStart = input.selectionEnd = newCursorPos; }, 0);
}
// ==================== MARKDOWN RENDERER ====================
function markdownToHtml(md) {
let html = '';
// Code blocks first (process before other formatting)
html = md.replace(/```(\w*)\n([\s\S]*?)```/g, '<div class="code-block"><pre><code>$1</code></pre></div>');
// Inline code
html = html.replace(/`([^`]+)`/g, '<code style="background:rgba(0,0,0,0.06);padding:2px 6px;border-radius:4px;font-size:0.85em;">$1</code>');
// Headers (process from h1 to h4)
html = html.replace(/^(#{1,4})\s+(.+)$/gm, '<h$1>$2</h$1>');
// Blockquotes
html = html.replace(/^>\s*(.+)$/gm, '<blockquote>$1</blockquote>');
// Horizontal rules
html = html.replace(/^---$/gm, '<hr>');
// Bold + Italic (process bold first)
html = html.replace(/\*\*\*([^\n]+)\*\*/g, '<strong><em>$1</em></strong>');
html = html.replace(/\*\*([^\n]+)\*\*/g, '<strong>$1</strong>');
html = html.replace(/\*([^\n]+)\*/g, '<em>$1</em>');
// Links
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
// Images
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">');
// Tables: detect and convert simple markdown tables
html = processTables(html);
// Unordered lists (-)
html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
// Ordered lists (1. or numbered)
html = html.replace(/^\d+\.\s+(.+)$/gm, '<li style="list-style-type:decimal;">$1</li>');
// Paragraphs (wrap remaining text in p tags, splitting by double newlines)
html = splitParagraphs(html);
return html;
}
function processTables(mdHtml) {
const tableRegex = /^(.+)\n\|(.+)\n\|([^\-]+)[\-]+\|/gm;
let result = '';
let lastIndex = 0;
function findNextTable() {
const match = mdHtml.match(tableRegex);
if (match) return match.index;
return null;
}
while ((idx = findNextTable()) !== null) {
result += mdHtml.substring(lastIndex, idx);
// Parse table rows
let parsedHeader = '';
let parsedBody = '';
const headerLine = mdHtml.match(tableRegex)[1];
const rowLines = [];
for (let i = idx + 4; i < mdHtml.length - 20; i++) {
if (mdHtml[i] === '|' && mdHtml.substring(i+1, i+5).includes('\n')) break;
if (i >= idx + 4 && i <= idx + 6) continue; // Skip header and separator
const line = mdHtml.substring(Math.max(idx + 7, i), Math.min(idx + 120, i));
rowLines.push(line);
if (line.includes('---')) break;
}
// Parse rows into HTML table
let tableHtml = '<table>';
const headerCells = headerLine.split('|').slice(1, -1).map(c => c.trim()).join(',');
tableHtml += `<tr><th>${headerCells}</th></tr>`;
for (const row of rowLines) {
if (!row.includes('---')) {
const cells = row.split('|').slice(1, -1).map(c => c.trim());
tableHtml += `<tr>${cells.map(c => `<td>${c}</td>`).join('')}</tr>`;
}
}
tableHtml += '</table>';
result += tableHtml;
lastIndex = idx + 4;
}
result += mdHtml.substring(lastIndex);
return result;
}
function splitParagraphs(html) {
const lines = html.split('\n');
let result = '';
let paragraphLines = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (!line.trim()) {
if (paragraphLines.length > 0) {
result += '<p>' + paragraphLines.join('') + '</p>';
paragraphLines = [];
}
result += '\n';
} else {
paragraphLines.push(line);
}
}
if (paragraphLines.length > 0) {
result += '<p>' + paragraphLines.join('') + '</p>';
}
return result;
}
function loadTags(note) {
const container = document.getElementById('note-tags');
if (!note.tags || note.tags.length === 0) {
container.innerHTML = '';
return;
}
let html = '<div class="tag-list">';
note.tags.forEach((tag, index) => {
const colors = ['tag-color-1', 'tag-color-2', 'tag-color-3', 'tag-color-4'];
const colorClass = colors[index % colors.length];
html += `<span class="tag-chip ${colorClass}" onclick="removeTagFromNote('${note.id}', '${tag}')">#${tag}</span>`;
});
html += '</div>';
container.innerHTML = html;
}
function addTagToNote(tag) {
const noteId = activeNoteId;
if (!noteId) return;
const note = findNoteById(noteId);
if (note.tags && !note.tags.includes(tag)) {
note.tags.push(tag);
updateNoteData(noteId, { tags: [...note.tags] });
loadTags(note);
// Update sidebar too
renderSidebar();
}
}
function removeTagFromNote(id, tag) {
const note = findNoteById(id);
if (!note) return;
if (note.tags && note.tags.includes(tag)) {
note.tags = note.tags.filter(t => t !== tag);
updateNoteData(id, { tags: [...note.tags] });
// Reload current note
loadNote(id);
}
}
// ==================== MODAL ====================
function openCreateModal() {
document.getElementById('create-modal').classList.add('open');
setTimeout(() => document.getElementById('modal-title').focus(), 100);
}
function closeModal() {
document.getElementById('create-modal').classList.remove('open');
}
function createNoteFromModal() {
const title = document.getElementById('modal-title').value.trim();
const folder = document.getElementById('modal-folder').value;
const content = document.getElementById('modal-content').value.trim();
if (!title && !content) {
showToast('Please add a title or some content');
return;
}
const now = new Date().toISOString();
const id = 'n' + Date.now().toString(36);
const newNote = {
id,
title: title || 'Untitled Note',
content: content || '',
group: folder,
tags: [],
created: now,
modified: now,
starred: false
};
allNotes.push(newNote);
saveNotes();
closeModal();
// Auto-load the new note
activeNoteId = id;
loadNote(id);
showToast('Note created successfully');
}
// ==================== TOAST NOTIFICATIONS ====================
function showToast(message) {
const container = document.getElementById('toasts');
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
container.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 450);
}, 2600);
}
// ==================== PERSISTENCE ====================
function saveNotes() {
try {
localStorage.setItem('lumina_notes', JSON.stringify(allNotes));
} catch(e) {
console.warn('Storage full, saving limited data');
}
}
// ==================== KEYBOARD SHORTCUTS ====================
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey)) {
switch(e.key.toLowerCase()) {
case 'n':
e.preventDefault();
openCreateModal();
break;
case 's':
e.preventDefault();
showToast('Notes saved automatically');
saveNotes();
break;
}
}
});
// ==================== START ====================
init();
</script>
</body>
</html>
<!-- agent-meta {"model":"ornith-1.0-9b","provider":"lmstudio","persona":"basic","sessionId":"c9200f22-f83b-42ef-8a0e-05735abbdb72","tokensIn":217,"tokensOut":12632,"tokensTotal":12849,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-07-13T17:38:48.539Z"} -->