AdhyanshVerma's picture
download
raw
15.8 kB
**index.html**
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Sticky Notes Board</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header class="toolbar" role="banner">
<div class="toolbar-inner">
<button id="addNoteBtn" class="add-btn" type="button" aria-label="Add new sticky note">
<span class="plus" aria-hidden="true">+</span> Add Note
</button>
<div class="search-wrap">
<span class="search-icon" aria-hidden="true"></span>
<input type="search" id="searchInput" class="search-input"
placeholder="Search notes…" autocomplete="off"
aria-label="Search sticky notes">
</div>
</div>
</header>
<main id="board" class="board" aria-label="Sticky notes board"></main>
<script src="app.js"></script>
</body>
</html>
```
**styles.css**
```css
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
overflow: hidden;
background: #f7f7f5;
}
body { overscroll-behavior: none; }
.board {
position: fixed;
inset: 0;
top: 64px;
background-color: #f1f1ee;
background-image: radial-gradient(#c9c9c4 1px, transparent 1px);
background-size: 22px 22px;
overflow: hidden;
touch-action: none;
}
/* Toolbar */
.toolbar {
position: fixed;
top: 0; left: 0; right: 0;
height: 64px;
z-index: 1000;
background: rgba(255,255,255,0.92);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-bottom: 1px solid #e0e0dc;
box-shadow: 0 1px 4px rgba(0,0,0,0.04);
}
.toolbar-inner {
height: 100%;
display: flex;
align-items: center;
gap: 14px;
padding: 0 18px;
max-width: 100%;
}
.add-btn {
display: inline-flex;
align-items: center;
gap: 6px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 600;
color: #1f2937;
background: linear-gradient(180deg, #ffe066, #ffd43b);
padding: 10px 16px;
border-radius: 10px;
box-shadow: 0 1px 3px rgba(0,0,0,0.15);
transition: transform .12s ease, box-shadow .12s ease;
white-space: nowrap;
}
.add-btn:hover { transform: translateY(-1px); box-shadow: 0 3px 6px rgba(0,0,0,0.18); }
.add-btn:active { transform: translateY(0); }
.add-btn .plus { font-size: 18px; line-height: 1; }
.search-wrap {
position: relative;
flex: 1;
max-width: 420px;
margin-left: auto;
}
.search-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #9aa0a6;
font-size: 16px;
pointer-events: none;
}
.search-input {
width: 100%;
padding: 10px 14px 10px 34px;
border: 1px solid #e0e0dc;
border-radius: 10px;
font-size: 14px;
background: #fff;
outline: none;
transition: border-color .15s ease, box-shadow .15s ease;
}
.search-input:focus {
border-color: #ffd43b;
box-shadow: 0 0 0 3px rgba(255,212,59,0.25);
}
/* Note */
.note {
position: absolute;
min-width: 150px;
min-height: 150px;
width: 220px;
height: 220px;
border-radius: 6px;
box-shadow: 0 4px 14px rgba(0,0,0,0.18), 0 1px 3px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
user-select: none;
touch-action: none;
transition: box-shadow .15s ease, opacity .2s ease;
outline: none;
}
.note:focus-within, .note.dragging {
box-shadow: 0 10px 28px rgba(0,0,0,0.28), 0 2px 6px rgba(0,0,0,0.15);
}
.note.dimmed { opacity: 0.22; pointer-events: none; }
.note.yellow { background-color: #fff59d; }
.note.pink { background-color: #ff8fa3; }
.note.blue { background-color: #90caf9; }
.note.green { background-color: #a5d6a7; }
.note-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 8px 4px;
gap: 6px;
cursor: grab;
flex: 0 0 auto;
}
.note.dragging .note-header { cursor: grabbing; }
.timestamp {
font-size: 10px;
color: rgba(0,0,0,0.55);
font-weight: 600;
letter-spacing: .2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.controls {
display: flex;
align-items: center;
gap: 4px;
opacity: 0;
transition: opacity .15s ease;
}
.note:hover .controls,
.note:focus-within .controls { opacity: 1; }
.palette {
display: flex;
gap: 3px;
padding: 2px;
background: rgba(255,255,255,0.45);
border-radius: 10px;
}
.swatch {
width: 14px;
height: 14px;
border-radius: 50%;
border: 1.5px solid rgba(255,255,255,0.85);
cursor: pointer;
padding: 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.swatch.active { outline: 2px solid rgba(0,0,0,0.55); outline-offset: 1px; }
.swatch.yellow { background: #fff59d; }
.swatch.pink { background: #ff8fa3; }
.swatch.blue { background: #90caf9; }
.swatch.green { background: #a5d6a7; }
.delete-btn {
border: none;
background: rgba(0,0,0,0.18);
color: #fff;
width: 20px;
height: 20px;
border-radius: 50%;
cursor: pointer;
font-size: 14px;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
transition: background .15s ease;
}
.delete-btn:hover { background: rgba(0,0,0,0.55); }
.note-content {
flex: 1;
width: 100%;
border: none;
background: transparent;
resize: none;
padding: 4px 12px 12px;
font-family: inherit;
font-size: 14px;
line-height: 1.4;
color: #1f2937;
outline: none;
user-select: text;
touch-action: text;
}
.note-content::placeholder { color: rgba(0,0,0,0.35); }
@media (max-width: 560px) {
.toolbar-inner { gap: 8px; padding: 0 10px; }
.add-btn { padding: 9px 12px; font-size: 13px; }
.search-wrap { max-width: none; }
.note { width: 180px; height: 180px; }
}
```
**app.js**
```js
(function () {
'use strict';
const STORAGE_KEY = 'stickyBoard.v1';
const COLORS = ['yellow', 'pink', 'blue', 'green'];
const TOOLBAR_H = 64;
const board = document.getElementById('board');
const addBtn = document.getElementById('addNoteBtn');
const searchInput = document.getElementById('searchInput');
let notes = []; // {id,x,y,color,content,z,createdAt}
let topZ = 1;
let dragState = null;
// ---------- Storage ----------
function save() {
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(notes)); }
catch (e) { /* ignore quota errors */ }
}
function load() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return [];
const arr = JSON.parse(raw);
return Array.isArray(arr) ? arr : [];
} catch (e) { return []; }
}
// ---------- Helpers ----------
function uid() {
return 'n_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 7);
}
function bringToFront(note) {
note.z = ++topZ;
const el = note.__el;
if (el) el.style.zIndex = note.z;
save();
}
function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }
function randomSpawnPos(w, h) {
const vw = window.innerWidth;
const vh = window.innerHeight;
const maxX = Math.max(0, vw - w);
const maxY = Math.max(TOOLBAR_H + 8, vh - h);
const x = Math.floor(Math.random() * (maxX + 1));
const y = Math.floor(Math.random() * (maxY - TOOLBAR_H + 1)) + TOOLBAR_H + 8;
return { x, y };
}
function formatTime(ts) {
const d = new Date(ts);
return d.toLocaleString(undefined, {
month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
});
}
// ---------- Rendering ----------
function renderNote(note) {
const el = document.createElement('section');
el.className = 'note ' + note.color;
el.style.left = note.x + 'px';
el.style.top = note.y + 'px';
el.style.zIndex = note.z;
el.setAttribute('tabindex', '0');
el.setAttribute('role', 'group');
el.setAttribute('aria-label', 'Sticky note created ' + formatTime(note.createdAt));
el.dataset.id = note.id;
const header = document.createElement('div');
header.className = 'note-header';
const ts = document.createElement('span');
ts.className = 'timestamp';
ts.textContent = formatTime(note.createdAt);
const controls = document.createElement('div');
controls.className = 'controls';
const palette = document.createElement('div');
palette.className = 'palette';
palette.setAttribute('role', 'group');
palette.setAttribute('aria-label', 'Note color');
COLORS.forEach(c => {
const sw = document.createElement('button');
sw.type = 'button';
sw.className = 'swatch ' + c + (c === note.color ? ' active' : '');
sw.dataset.color = c;
sw.setAttribute('aria-label', c.charAt(0).toUpperCase() + c.slice(1) + ' color');
sw.setAttribute('aria-pressed', c === note.color ? 'true' : 'false');
sw.addEventListener('click', function (e) {
e.stopPropagation();
setColor(note, c);
});
palette.appendChild(sw);
});
const del = document.createElement('button');
del.type = 'button';
del.className = 'delete-btn';
del.setAttribute('aria-label', 'Delete note');
del.textContent = '×';
del.addEventListener('click', function (e) {
e.stopPropagation();
deleteNote(note);
});
controls.appendChild(palette);
controls.appendChild(del);
header.appendChild(ts);
header.appendChild(controls);
const content = document.createElement('textarea');
content.className = 'note-content';
content.value = note.content || '';
content.placeholder = 'Write something…';
content.setAttribute('aria-label', 'Note content');
content.setAttribute('aria-multiline', 'true');
let saveTimer = null;
content.addEventListener('input', function () {
note.content = content.value;
clearTimeout(saveTimer);
saveTimer = setTimeout(save, 300);
});
content.addEventListener('focus', function () { bringToFront(note); });
el.appendChild(header);
el.appendChild(content);
// Keyboard on note container
el.addEventListener('keydown', function (e) {
if (e.key === 'Enter' && document.activeElement !== content) {
e.preventDefault();
content.focus();
} else if (e.key === 'Escape' && document.activeElement === content) {
content.blur();
el.focus();
}
});
el.addEventListener('focus', function () { bringToFront(note); }, true);
attachDrag(note, el, header);
note.__el = el;
note.__content = content;
board.appendChild(el);
return el;
}
function setColor(note, color) {
note.color = color;
const el = note.__el;
COLORS.forEach(c => el.classList.remove(c));
el.classList.add(color);
el.querySelectorAll('.swatch').forEach(sw => {
const active = sw.dataset.color === color;
sw.classList.toggle('active', active);
sw.setAttribute('aria-pressed', active ? 'true' : 'false');
});
save();
}
function deleteNote(note) {
const idx = notes.indexOf(note);
if (idx > -1) notes.splice(idx, 1);
if (note.__el && note.__el.parentNode) note.__el.parentNode.removeChild(note.__el);
save();
}
function createNote(opts) {
const w = 220, h = 220;
const pos = opts && opts.x != null ? { x: opts.x, y: opts.y } : randomSpawnPos(w, h);
const note = {
id: opts && opts.id ? opts.id : uid(),
x: pos.x,
y: pos.y,
color: (opts && opts.color) || 'yellow',
content: (opts && opts.content) || '',
z: ++topZ,
createdAt: (opts && opts.createdAt) || Date.now()
};
notes.push(note);
renderNote(note);
save();
return note;
}
// ---------- Drag (mouse + touch, no HTML5 DnD) ----------
function attachDrag(note, el, handle) {
function isInteractive(target) {
return target.closest('textarea, button, .controls, .palette');
}
function getPoint(e) {
if (e.touches && e.touches.length) {
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
}
return { x: e.clientX, y: e.clientY };
}
function onStart(e) {
if (e.button !== undefined && e.button !== 0) return;
if (isInteractive(e.target)) return;
// Don't drag while editing text
if (document.activeElement === note.__content) return;
e.preventDefault();
const p = getPoint(e);
const rect = el.getBoundingClientRect();
dragState = {
note: note,
el: el,
offsetX: p.x - rect.left,
offsetY: p.y - rect.top,
pointerId: e.pointerId
};
el.classList.add('dragging');
bringToFront(note);
document.addEventListener('mousemove', onMove, { passive: false });
document.addEventListener('mouseup', onEnd);
document.addEventListener('touchmove', onMove, { passive: false });
document.addEventListener('touchend', onEnd);
document.addEventListener('touchcancel', onEnd);
}
function onMove(e) {
if (!dragState) return;
e.preventDefault();
const p = getPoint(e);
const vw = window.innerWidth;
const vh = window.innerHeight;
const w = el.offsetWidth;
const h = el.offsetHeight;
// Boundary: keep at least 40px visible on each axis
let nx = p.x - dragState.offsetX;
let ny = p.y - dragState.offsetY;
nx = clamp(nx, -(w - 40), vw - 40);
ny = clamp(ny, TOOLBAR_H, vh - 40);
note.x = nx;
note.y = ny;
el.style.left = nx + 'px';
el.style.top = ny + 'px';
}
function onEnd() {
if (!dragState) return;
el.classList.remove('dragging');
save();
dragState = null;
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onEnd);
document.removeEventListener('touchmove', onMove);
document.removeEventListener('touchend', onEnd);
document.removeEventListener('touchcancel', onEnd);
}
handle.addEventListener('mousedown', onStart);
handle.addEventListener('touchstart', onStart, { passive: false });
}
// ---------- Search ----------
function applyFilter() {
const q = searchInput.value.trim().toLowerCase();
notes.forEach(n => {
const el = n.__el;
if (!el) return;
const match = !q || (n.content || '').toLowerCase().includes(q);
el.classList.toggle('dimmed', !match);
});
}
searchInput.addEventListener('input', applyFilter);
// ---------- Add button ----------
addBtn.addEventListener('click', function () {
const n = createNote();
setTimeout(() => { if (n.__content) n.__content.focus(); }, 0);
});
// ---------- Init ----------
function init() {
const loaded = load();
if (loaded.length === 0) {
// seed a welcome note
createNote({
content: 'Welcome! Click "Add Note" to create a sticky.\nDrag from the header. Hover for color & delete.',
color: 'yellow',
x: Math.max(20, window.innerWidth / 2 - 110),
y: 90
});
} else {
// restore z ordering
loaded.sort((a, b) => a.z - b.z);
loaded.forEach(n => {
if (n.z > topZ) topZ = n.z;
notes.push(n);
renderNote(n);
});
}
applyFilter();
}
// Keep notes within bounds on resize
window.addEventListener('resize', function () {
const vw = window.innerWidth;
const vh = window.innerHeight;
notes.forEach(n => {
const w = n.__el ? n.__el.offsetWidth : 220;
const h = n.__el ? n.__el.offsetHeight : 220;
n.x = clamp(n.x, -(w - 40), vw - 40);
n.y = clamp(n.y, TOOLBAR_H, vh - 40);
if (n.__el) {
n.__el.style.left = n.x + 'px';
n.__el.style.top = n.y + 'px';
}
});
save();
});
init();
})();
```

Xet Storage Details

Size:
15.8 kB
·
Xet hash:
8f69e73239181a82450a292a95f5754e77a726a3f04773125c6a9dffd1f02aa1

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.