<!
doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>QuickNotes — Simple Notepad</title>
<!-- Minimal UI, responsive -->
<style>
:root{
--bg: #f4f6f8; --pane: #fff; --muted:#6b7280; --accent:#0b6eff; --
danger:#e63946;
--glass: rgba(255,255,255,0.6);
}
[data-theme="dark"]{
--bg:#0b0f14; --pane:#07101a; --muted:#9aa3b2; --glass: rgba(0,0,0,0.4); --
accent:#4da6ff;
}
*{box-sizing:border-box}
body{
margin:0; min-height:100vh; font-family:Inter,Segoe UI,system-ui,Arial;
background:linear-gradient(180deg,var(--bg),#e9eef6 60%); color: #0f1724;
}
.app{
display:grid; grid-template-columns:320px 1fr; gap:16px; padding:16px; max-
width:1200px; margin:24px auto;
}
header{
grid-column:1/-1; display:flex; gap:12px; align-items:center; justify-
content:space-between;
margin-bottom:6px;
}
.brand{display:flex; gap:12px; align-items:center}
.logo{
width:44px;height:44px;border-radius:8px;background:linear-
gradient(135deg,var(--accent),#7ad0ff);
display:flex;align-items:center;justify-content:center;color:white;font-
weight:700;font-size:18px;
}
.controls{display:flex; gap:8px; align-items:center;}
button{
background:var(--pane); border:1px solid rgba(15,23,36,0.06); padding:8px
10px;border-radius:8px; cursor:pointer;
}
[Link]{background:transparent;border:1px solid rgba(15,23,36,0.06)}
[Link]{border-color:var(--danger); color:var(--danger)}
.sidebar{background:var(--pane); padding:12px; border-radius:12px; height:75vh;
overflow:auto}
.search{display:flex; gap:8px; margin-bottom:10px}
input[type="search"], input[type="text"]{
width:100%; padding:8px 10px; border-radius:8px; border:1px solid
rgba(15,23,36,0.06); background:transparent;
}
.note-list{display:flex;flex-direction:column; gap:6px}
.note-item{padding:10px;border-radius:8px;cursor:pointer;display:flex;justify-
content:space-between;align-items:center}
.[Link]{background:linear-gradient(90deg, rgba(11,110,255,0.08),
rgba(122,208,255,0.03));border:1px solid rgba(11,110,255,0.12)}
.main{background:var(--pane); padding:12px; border-radius:12px; height:75vh;
display:flex; flex-direction:column; gap:8px}
.title-row{display:flex; gap:8px; align-items:center}
.title-row input{font-size:18px; font-weight:600; border:0;
background:transparent; outline:none; padding:6px}
.editor{flex:1; display:grid; grid-template-columns:1fr 1fr; gap:8px; min-
height:320px}
textarea{
width:100%; height:100%; resize:none; border-radius:8px; padding:12px;
border:1px solid rgba(15,23,36,0.06); background:transparent;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono",
monospace; font-size:15px; line-height:1.45;
}
.preview{border-radius:8px; border:1px solid rgba(15,23,36,0.06); padding:12px;
overflow:auto; background:linear-gradient(180deg,var(--glass),transparent)}
.meta{display:flex; gap:8px; align-items:center; flex-wrap:wrap}
.tag{background:rgba(0,0,0,0.06);padding:4px 8px;border-radius:999px;font-
size:12px}
.small{font-size:13px;color:var(--muted)}
.footer{display:flex; justify-content:space-between; align-items:center; gap:8px}
.note-actions{display:flex; gap:6px; align-items:center}
.hide-mobile{display:inline-block}
@media (max-width:880px){
.app{grid-template-columns:1fr; padding:12px}
.sidebar{height:220px; order:2}
.main{height:auto; order:1}
.editor{grid-template-columns:1fr}
.hide-mobile{display:none}
}
</style>
</head>
<body>
<div class="app" id="app" data-theme="light">
<header>
<div class="brand">
<div class="logo">QN</div>
<div>
<div style="font-weight:700">QuickNotes</div>
<div class="small">Lightweight offline notepad — autosaves to your
browser</div>
</div>
</div>
<div class="controls">
<div class="small">Theme</div>
<select id="themeToggle" aria-label="Toggle theme">
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<button id="newBtn" title="New note (Ctrl/Cmd+N)">New</button>
<button id="saveBtn" title="Save (Ctrl/Cmd+S)">Save</button>
<button id="exportBtn" title="Export note as .md">Export</button>
<label style="margin:0" class="ghost">
<input id="importInput" type="file" accept=".md,text/markdown"
style="display:none">
<span style="padding:8px 10px; display:inline-block;
cursor:pointer">Import</span>
</label>
</div>
</header>
<aside class="sidebar" id="sidebar" aria-label="Notes list">
<div class="search">
<input id="search" type="search" placeholder="Search notes (Ctrl/Cmd+F)"
aria-label="Search notes">
<button id="clearSearch" title="Clear search">✕</button>
</div>
<div style="display:flex; gap:8px; margin-bottom:8px; align-items:center">
<div class="small">Notes</div>
<div class="small" id="notesCount">0</div>
</div>
<div class="note-list" id="noteList" role="list"></div>
<div style="margin-top:10px" class="small">Tip: use keyboard shortcuts — <span
class="tag">Ctrl/Cmd+N</span> <span class="tag">Ctrl/Cmd+S</span> <span
class="tag">Ctrl/Cmd+F</span></div>
</aside>
<main class="main" id="main">
<div class="title-row">
<input id="noteTitle" type="text" placeholder="Untitled note" aria-
label="Note title">
<div class="meta">
<div class="tag" id="noteUpdated">—</div>
<div style="width:8px"></div>
<button id="deleteBtn" class="warn" title="Delete note">Delete</button>
<button id="togglePreview" class="ghost" title="Toggle
preview">Preview</button>
</div>
</div>
<div class="editor" id="editorWrap">
<textarea id="editor" placeholder="Start typing your note..."></textarea>
<div class="preview" id="preview" style="display:none"></div>
</div>
<div class="footer">
<div class="small" id="status">Saved</div>
<div class="note-actions">
<button id="importExportAll" title="Export all notes as JSON
(backup)">Backup</button>
<button id="restoreBackup" title="Import backup JSON">Restore</button>
<div class="small hide-mobile">LocalStorage used — notes are private to
this browser</div>
</div>
</div>
</main>
</div>
<!-- Minimal markdown renderer (marked-like but tiny) -->
<script>
(() => {
// Utilities
const qs = s => [Link](s);
const qsa = s => [Link]([Link](s));
const uid = () =>
'n_'+[Link]().toString(36)+'_'+[Link]().toString(36).slice(2,8);
// Storage keys
const STORAGE_KEY = 'quicknotes_v1';
// Data structure: { notes: {id:{id,title,content,updated}}, order:[id,id...] }
let store = {notes: {}, order: []};
let activeId = null;
let autosaveTimer = null;
const AUTOSAVE_DELAY = 600; // ms
// DOM
const noteListEl = qs('#noteList');
const editorEl = qs('#editor');
const titleEl = qs('#noteTitle');
const statusEl = qs('#status');
const updatedEl = qs('#noteUpdated');
const notesCountEl = qs('#notesCount');
const previewEl = qs('#preview');
const editorWrap = qs('#editorWrap');
// Tiny safe Markdown renderer (supports headings, bold, italics, lists, code
blocks, links)
function renderMarkdown(md){
if(!md) return '<div class="small" style="opacity:.7">Empty preview</div>';
// Escape HTML
let html = [Link](/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
// Code block ```lang
html = [Link](/```([^\n]*)\n([\s\S]*?)```/g, (m,lang,code) => {
return '<pre><code>'+[Link](/</g,'<')+'</code></pre>';
});
// Inline code
html = [Link](/`([^`]+)`/g,'<code>$1</code>');
// Headings
html = [Link](/^###### (.*$)/gim,'<h6>$1</h6>');
html = [Link](/^##### (.*$)/gim,'<h5>$1</h5>');
html = [Link](/^#### (.*$)/gim,'<h4>$1</h4>');
html = [Link](/^### (.*$)/gim,'<h3>$1</h3>');
html = [Link](/^## (.*$)/gim,'<h2>$1</h2>');
html = [Link](/^# (.*$)/gim,'<h1>$1</h1>');
// Bold and italics
html = [Link](/\*\*([^*]+)\*\*/g,'<strong>$1</strong>');
html = [Link](/\*([^*]+)\*/g,'<em>$1</em>');
// Links
html = [Link](/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank"
rel="noopener noreferrer">$1</a>');
// Lists
html = [Link](/^\s*-\s+(.*)/gim,'<li>$1</li>');
html = [Link](/(<li>[\s\S]*<\/li>)/g, (m) => '<ul>'+m+'</ul>');
// Paragraphs
html = [Link](/^(?!<h|<ul|<pre|<li|<code)(.+)$/gim,'<p>$1</p>');
return html;
}
// Persistence
function loadStore(){
try{
const raw = [Link](STORAGE_KEY);
if(raw){
store = [Link](raw);
// Basic migration safety
if(![Link]) store = {notes:{},order:[]};
} else {
// seed example note
const id = uid();
const now = [Link]();
store = {
notes: {
[id]: {id, title: 'Welcome to QuickNotes', content: '# QuickNotes\n\
nThis is a demo note. Type away.\n\n- Autosaves automatically.\n- Use
**Ctrl/Cmd+N** for new note, **Ctrl/Cmd+S** to save.\n', updated: now}
},
order:[id]
};
activeId = id;
saveStore();
}
}catch(e){
[Link]('Failed to load store', e);
store = {notes:{},order:[]};
}
}
function saveStore(){
[Link](STORAGE_KEY, [Link](store));
}
// UI rendering
function renderList(filter=''){
[Link] = '';
const ids = [Link]().reverse(); // newest first
let shown = 0;
[Link](id => {
const n = [Link][id];
if(!n) return;
const tiny = ([Link] || '').split('\n').find(l => [Link]()) || '';
const match = ([Link] + ' ' +
tiny).toLowerCase().includes([Link]());
if(!match) return;
shown++;
const item = [Link]('div');
[Link] = 'note-item' + (id===activeId ? ' active' : '');
[Link] = id;
[Link] = `<div>
<div style="font-weight:600">${escapeHtml([Link]||'Untitled')}</div>
<div class="small" style="color:var(--muted)">${timeAgo([Link])}</div>
</div>
<div class="small" style="max-width:120px; text-align:right; color:var(--
muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">
${escapeHtml([Link](0,80))}
</div>`;
[Link]('click', () => selectNote(id));
[Link](item);
});
[Link] = [Link]([Link]).length;
if(shown===0){
const e = [Link]('div');
[Link]='small';
[Link]='.6';
[Link]='8px';
[Link] = 'No notes match your search.';
[Link](e);
}
}
function selectNote(id){
if(![Link][id]) return;
activeId = id;
[Link] = [Link][id].title || '';
[Link] = [Link][id].content || '';
[Link] = 'Updated ' + new
Date([Link][id].updated).toLocaleString();
renderList(qs('#search').value);
renderPreview();
setStatus('Loaded');
}
function newNote(){
const id = uid();
const now = [Link]();
[Link][id] = {id, title: 'Untitled note', content: '', updated: now};
[Link](id);
saveStore();
selectNote(id);
renderList();
[Link]();
setStatus('Created note');
}
function deleteNote(id){
if(!id || ![Link][id]) return;
const conf = confirm('Delete this note? This cannot be undone.');
if(!conf) return;
delete [Link][id];
[Link] = [Link](x=>x!==id);
saveStore();
// pick another note
const next = [Link][[Link]-1];
activeId = next || null;
if(activeId) selectNote(activeId); else {
// empty state
[Link] = '';
[Link] = '';
[Link] = '—';
renderList();
setStatus('Deleted');
}
}
function saveActiveNote(){
if(!activeId) return;
const n = [Link][activeId];
[Link] = [Link]() || 'Untitled note';
[Link] = [Link];
[Link] = [Link]();
saveStore();
renderList(qs('#search').value);
[Link] = 'Updated ' + new Date([Link]).toLocaleString();
setStatus('Saved');
}
// autosave after pause
function scheduleAutosave(){
setStatus('Editing...');
if(autosaveTimer) clearTimeout(autosaveTimer);
autosaveTimer = setTimeout(() => {
saveActiveNote();
}, AUTOSAVE_DELAY);
}
function setStatus(text){
[Link] = text;
// small visual timeout for transient messages
if(text !== 'Saved' && text !== 'Editing...'){
setTimeout(()=>{ if([Link]===text) [Link]='Saved'
}, 1500);
}
}
// Helpers
function escapeHtml(s){ return [Link](/[&<>"']/g,
c=>({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
function timeAgo(ts){
const d = [Link]() - ts;
if(d < 60000) return 'just now';
if(d < 3600000) return [Link](d/60000)+'m ago';
if(d < 86400000) return [Link](d/3600000)+'h ago';
return new Date(ts).toLocaleString();
}
// Preview rendering
let previewVisible = false;
function renderPreview(){
[Link] = renderMarkdown([Link]);
}
// Search
qs('#search').addEventListener('input', e => renderList([Link]));
qs('#clearSearch').addEventListener('click', ()=>{ qs('#search').value='';
renderList(''); qs('#search').focus(); });
// Buttons
qs('#newBtn').addEventListener('click', newNote);
qs('#saveBtn').addEventListener('click', ()=>{ saveActiveNote(); });
qs('#deleteBtn').addEventListener('click', ()=>{ if(activeId)
deleteNote(activeId); });
qs('#togglePreview').addEventListener('click', ()=>{
previewVisible = !previewVisible;
[Link] = previewVisible ? 'block' : 'none';
qs('#togglePreview').textContent = previewVisible ? 'Hide preview' : 'Preview';
if(previewVisible) renderPreview();
});
// Live editing
[Link]('input', ()=>{
scheduleAutosave();
if(previewVisible) renderPreview();
});
[Link]('input', ()=>{
scheduleAutosave();
renderList(qs('#search').value);
});
// Keyboard shortcuts
[Link]('keydown', (e)=>{
const meta = [Link] || [Link];
if(meta && [Link]()==='s'){
[Link]();
saveActiveNote();
} else if(meta && [Link]()==='n'){
[Link]();
newNote();
} else if(meta && [Link]()==='f'){
[Link]();
qs('#search').focus();
qs('#search').select();
}
});
// Export single note
qs('#exportBtn').addEventListener('click', ()=>{
if(!activeId) return alert('No note selected');
const n = [Link][activeId];
const blob = new Blob([[Link]||''], {type:'text/markdown;charset=utf-8'});
const a = [Link]('a');
[Link] = [Link](blob);
const name = ([Link]||'note').replace(/[^\w\- ]+/g,'').slice(0,40) || 'note';
[Link] = name + '.md';
[Link](a); [Link](); [Link]();
setStatus('Exported');
});
// Import single .md file
qs('#importInput').addEventListener('change', (ev)=>{
const f = [Link][0];
if(!f) return;
const reader = new FileReader();
[Link] = () => {
const content = [Link];
const id = uid();
const now = [Link]();
[Link][id] = {id, title: [Link](/\.(md|markdown)$/i,'') ||
'Imported note', content, updated: now};
[Link](id);
saveStore();
renderList();
selectNote(id);
setStatus('Imported');
};
[Link](f);
[Link] = ''; // reset
});
// Backup all notes (export JSON)
qs('#importExportAll').addEventListener('click', ()=>{
const blob = new Blob([[Link](store, null, 2)],
{type:'application/json'});
const a = [Link]('a');
[Link] = [Link](blob);
[Link] = '[Link]';
[Link](a); [Link](); [Link]();
setStatus('Backup downloaded');
});
// Restore from backup
qs('#restoreBackup').addEventListener('click', ()=>{
const input = [Link]('input');
[Link]='file'; [Link]='application/json';
[Link] = e => {
const f = [Link][0];
if(!f) return;
const r = new FileReader();
[Link] = () => {
try{
const parsed = [Link]([Link]);
if(parsed && [Link]){
store = parsed;
saveStore();
renderList();
const pick = [Link][[Link]-1];
if(pick) selectNote(pick);
setStatus('Restored from backup');
} else alert('Invalid backup file');
}catch(err){ alert('Failed to import: '+[Link]) }
};
[Link](f);
};
[Link]();
});
// Theme toggle + remember
const themeSelect = qs('#themeToggle');
const appEl = qs('#app');
const savedTheme = [Link]('quicknotes_theme') || 'light';
[Link] = savedTheme;
[Link] = savedTheme;
[Link]('change', (e)=>{
[Link] = [Link];
[Link]('quicknotes_theme', [Link]);
});
// Small helper to initialize
function init(){
loadStore();
renderList();
// pick last active (most recent)
activeId = [Link][[Link]-1] || [Link]([Link])[0] ||
null;
if(activeId) selectNote(activeId);
else {
newNote();
}
}
// Initialize app
init();
// Expose for console (handy for debugging)
[Link] = {
store, saveStore, selectNote, newNote
};
// tiny accessibility / focus helpers
[Link]('keydown', (e)=>{ if([Link]==='Enter')
[Link](); });
// helper to ensure preview updates on load
if(previewVisible) renderPreview();
})();
</script>
</body>
</html>