Notes Without Folders (Visual Board View)
A visual board view you can drop into a note-taking app (or any page with a list of notes). Instead of burying notes in nested folders, you place them as cards on a canvas and let spatial memory + tags do the organizing.
What it does:
- Renders your notes as draggable cards on a board (infinite-ish canvas with pan).
- Saves card positions automatically in
localStorage(per board). - Includes a quick tag filter (type
#tagor plain text). - No framework, no dependencies, no build step.
Install:
- Paste the HTML container anywhere:
<div id="noteBoard"></div> - Paste the script below before
</body> - Replace the
NOTESarray with your real notes (or hydrate it from your backend).
How to use:
- Drag cards to position them.
- Hold Space + drag to pan the board.
- Use the filter box to show notes by keyword or
#tag.
<!-- 1) Drop-in container -->
<div id="noteBoard"></div>
<script>
(function(){
/**
* Visual Board Notes (No Folders) — Drop-in
* - Draggable note cards
* - Space+drag to pan
* - Saves positions in localStorage
* - Filter by text or #tag
*/
// ========= YOUR NOTES =========
// Required fields: id, title, body, tags[]
var NOTES = [
{ id:"n1", title:"Client A — kickoff", body:"Discuss scope, deliverables, timelines. Next: send proposal.", tags:["work","clientA","meeting"] },
{ id:"n2", title:"Random idea", body:"A board view that replaces nested folders. Tags + spatial memory.", tags:["ideas","notes"] },
{ id:"n3", title:"Grocery", body:"Eggs, coffee, rice, hot sauce.", tags:["personal","todo"] },
{ id:"n4", title:"Bug: replies sorting", body:"Replies tab not ordering by last reply. Fix query and test.", tags:["dev","bugs"] }
];
// Board identity (change per board/view)
var BOARD_KEY = "noteBoard:default";
// ========= UTIL =========
function $(sel, root){ return (root||document).querySelector(sel); }
function el(tag, attrs){
var n = document.createElement(tag);
if (attrs) for (var k in attrs) if (Object.prototype.hasOwnProperty.call(attrs,k)) {
if (k === "text") n.textContent = attrs[k];
else if (k === "html") n.innerHTML = attrs[k];
else n.setAttribute(k, attrs[k]);
}
return n;
}
function clamp(n,min,max){ return Math.max(min, Math.min(max,n)); }
function safeJsonParse(s, fallback){
try { return JSON.parse(s); } catch(e){ return fallback; }
}
function toKey(id){ return BOARD_KEY + ":pos:" + id; }
function loadPos(id){
var j = safeJsonParse(localStorage.getItem(toKey(id)) || "", null);
if (!j || typeof j.x !== "number" || typeof j.y !== "number") return null;
return j;
}
function savePos(id, x, y){
localStorage.setItem(toKey(id), JSON.stringify({x:x,y:y,t:Date.now()}));
}
// ========= STYLES =========
var css = `
:root{
--bg:#0b0f16; --panel:#111a26; --panel2:#0c1420;
--text:#e8eef8; --muted:rgba(232,238,248,.72);
--line:rgba(255,255,255,.10); --accent:#2a8cff;
--shadow:0 18px 55px rgba(0,0,0,.45);
--r:16px;
}
.nb-wrap{background:var(--bg);border:1px solid var(--line);border-radius:var(--r);overflow:hidden;box-shadow:var(--shadow)}
.nb-top{display:flex;gap:10px;flex-wrap:wrap;align-items:center;padding:12px;background:linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.02))}
.nb-title{font:900 14px/1 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:var(--text)}
.nb-hint{font:600 12px/1.2 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:var(--muted)}
.nb-input{
flex:1;min-width:220px;
padding:10px 12px;border-radius:12px;border:1px solid var(--line);
background:var(--panel2);color:var(--text);outline:none;
font:700 13px/1 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
}
.nb-btn{
border:0;border-radius:12px;padding:10px 12px;
background:var(--accent);color:#06101a;font-weight:900;cursor:pointer;
}
.nb-stage{
position:relative;height:70vh;min-height:520px;background:
radial-gradient(circle at 1px 1px, rgba(255,255,255,.08) 1px, transparent 0) 0 0/22px 22px,
linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,0));
overflow:hidden;
cursor:grab;
}
.nb-stage.panning{cursor:grabbing}
.nb-world{position:absolute;left:0;top:0;transform:translate(0,0)}
.nb-card{
position:absolute;width:260px;
background:rgba(17,26,38,.92);
border:1px solid rgba(255,255,255,.12);
border-radius:16px;box-shadow:0 16px 50px rgba(0,0,0,.42);
padding:12px;
user-select:none;
}
.nb-card:active{transform:scale(1.01)}
.nb-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start}
.nb-card h3{margin:0;font:900 13px/1.2 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:var(--text)}
.nb-tags{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}
.nb-tag{
font:800 11px/1 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
color:rgba(232,238,248,.82);
background:rgba(42,140,255,.14);
border:1px solid rgba(42,140,255,.25);
padding:4px 8px;border-radius:999px;
}
.nb-body{margin-top:8px;color:var(--muted);font:650 12px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif}
.nb-mini{opacity:.75;font:700 11px/1 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif}
`;
var style = el("style"); style.textContent = css; document.head.appendChild(style);
// ========= BUILD UI =========
var host = document.getElementById("noteBoard");
if (!host) return;
var wrap = el("div", {class:"nb-wrap"});
var top = el("div", {class:"nb-top"});
var ttl = el("div", {class:"nb-title", text:"Visual Board Notes"});
var hint = el("div", {class:"nb-hint", text:"Drag cards • Space+drag to pan • Filter by text or #tag"});
var input= el("input", {class:"nb-input", placeholder:"Filter… (try: #work or meeting)"});
var reset= el("button", {class:"nb-btn", type:"button", text:"Reset layout"});
top.appendChild(ttl);
top.appendChild(hint);
top.appendChild(input);
top.appendChild(reset);
var stage = el("div", {class:"nb-stage"});
var world = el("div", {class:"nb-world"});
stage.appendChild(world);
wrap.appendChild(top);
wrap.appendChild(stage);
host.appendChild(wrap);
// ========= PAN STATE =========
var pan = {x: 0, y: 0};
var panning = false;
var panStart = {x:0,y:0,px:0,py:0};
var spaceDown = false;
function setWorldTransform(){
world.style.transform = "translate(" + pan.x + "px," + pan.y + "px)";
}
window.addEventListener("keydown", function(e){
if (e.code === "Space") spaceDown = true;
});
window.addEventListener("keyup", function(e){
if (e.code === "Space") spaceDown = false;
});
stage.addEventListener("mousedown", function(e){
if (!spaceDown) return;
panning = true;
stage.classList.add("panning");
panStart.x = e.clientX;
panStart.y = e.clientY;
panStart.px = pan.x;
panStart.py = pan.y;
e.preventDefault();
});
window.addEventListener("mousemove", function(e){
if (!panning) return;
pan.x = panStart.px + (e.clientX - panStart.x);
pan.y = panStart.py + (e.clientY - panStart.y);
setWorldTransform();
});
window.addEventListener("mouseup", function(){
if (!panning) return;
panning = false;
stage.classList.remove("panning");
});
// ========= CARD RENDER =========
var cards = [];
function defaultLayout(i){
// neat staggered grid
var col = i % 4;
var row = Math.floor(i / 4);
return { x: 40 + col * 290, y: 40 + row * 210 };
}
function makeCard(note, i){
var c = el("div", {class:"nb-card"});
c.setAttribute("data-id", note.id);
var head = el("div", {class:"nb-head"});
var h3 = el("h3"); h3.textContent = note.title || "Untitled";
var mini = el("div", {class:"nb-mini", text: (note.tags && note.tags.length) ? "#" + note.tags[0] : ""});
head.appendChild(h3);
head.appendChild(mini);
var body = el("div", {class:"nb-body"});
body.textContent = (note.body || "").slice(0, 200);
var tags = el("div", {class:"nb-tags"});
(note.tags || []).slice(0, 8).forEach(function(t){
tags.appendChild(el("span", {class:"nb-tag", text:"#"+t}));
});
c.appendChild(head);
c.appendChild(body);
c.appendChild(tags);
// position
var pos = loadPos(note.id) || defaultLayout(i);
c.style.left = pos.x + "px";
c.style.top = pos.y + "px";
// drag behavior
var dragging = false;
var start = {x:0,y:0,ox:0,oy:0};
c.addEventListener("mousedown", function(e){
if (spaceDown) return; // let stage pan
dragging = true;
start.x = e.clientX;
start.y = e.clientY;
start.ox = parseFloat(c.style.left) || 0;
start.oy = parseFloat(c.style.top) || 0;
c.style.zIndex = String(1000 + i);
e.preventDefault();
});
window.addEventListener("mousemove", function(e){
if (!dragging) return;
var nx = start.ox + (e.clientX - start.x);
var ny = start.oy + (e.clientY - start.y);
c.style.left = nx + "px";
c.style.top = ny + "px";
});
window.addEventListener("mouseup", function(){
if (!dragging) return;
dragging = false;
var nx = parseFloat(c.style.left) || 0;
var ny = parseFloat(c.style.top) || 0;
savePos(note.id, nx, ny);
});
return c;
}
function render(list){
world.innerHTML = "";
cards = [];
for (var i=0;i<list.length;i++){
var c = makeCard(list[i], i);
cards.push(c);
world.appendChild(c);
}
setWorldTransform();
}
// ========= FILTER =========
function matches(note, q){
q = (q || "").trim().toLowerCase();
if (!q) return true;
// tag filter: "#work"
if (q[0] === "#") {
var t = q.slice(1);
return (note.tags || []).some(function(x){ return String(x).toLowerCase() === t; });
}
// text match
var hay = (note.title||"") + " " + (note.body||"") + " " + (note.tags||[]).join(" ");
return hay.toLowerCase().indexOf(q) !== -1;
}
input.addEventListener("input", function(){
var q = input.value;
var filtered = NOTES.filter(function(n){ return matches(n, q); });
render(filtered);
});
// reset positions
reset.addEventListener("click", function(){
if (!confirm("Reset saved card positions for this board?")) return;
NOTES.forEach(function(n){ localStorage.removeItem(toKey(n.id)); });
pan.x = 0; pan.y = 0;
input.value = "";
render(NOTES);
});
// init
render(NOTES);
})();
</script>
Comments (0)
No comments yet — be the first.