Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width,initial-scale=1" /> | |
<title>Question Sticky Board</title> | |
<!-- Google fonts --> | |
<link | |
href="https://fonts.googleapis.com/css2?family=Crimson+Text:wght@400;600&family=Libre+Baskerville:wght@400;700&family=PT+Mono&display=swap" | |
rel="stylesheet" | |
/> | |
<style> | |
/* ───────────────────────────────────────── | |
COLOR SYSTEM (light / dark via variables) | |
───────────────────────────────────────── */ | |
:root{ | |
--desk-bg: #fbf9f5; | |
--desk-dot: #e2dccd; | |
--paper-bg: #fffefa; | |
--paper-text: #222; | |
--shadow: rgba(0,0,0,.35); | |
--perforation: #d0c6b7; | |
--board-line: rgba(201,190,170,.18); | |
/* sticky colors */ | |
--note-yellow: #fff6a8; | |
--note-mint: #d8f7e6; | |
--note-blush: #ffe3e0; | |
--note-blue: #e6f0ff; | |
--note-border: rgba(0,0,0,.08); | |
--note-bar: rgba(0,0,0,.04); | |
--note-text: #2b2b2b; | |
--btn-fg: #444; | |
--btn-fg-dim: #666; | |
--btn-bg-hover: rgba(0,0,0,.08); | |
} | |
body.dark{ | |
--desk-bg: #2c2a27; | |
--desk-dot: #3a3733; | |
--paper-bg: #302e3b; /* slightly deeper to add contrast with tiles */ | |
--paper-text: #e9e7e2; | |
--shadow: rgba(0,0,0,.55); | |
--perforation: #6d6456; | |
--board-line: rgba(110,103,94,.28); | |
--note-yellow: #6b6434; | |
--note-mint: #3f5a50; | |
--note-blush: #5b3f3c; | |
--note-blue: #3e4c63; | |
--note-border: rgba(0,0,0,.25); | |
--note-bar: rgba(255,255,255,.05); | |
--note-text: #f1efe9; | |
--btn-fg: #ddd; | |
--btn-fg-dim: #aaa; | |
--btn-bg-hover: rgba(255,255,255,.1); | |
} | |
/* ───────────────────────────────────────── | |
GLOBAL “DESK” BACKGROUND | |
───────────────────────────────────────── */ | |
html,body{height:100%} | |
body{ | |
margin:0; | |
background: var(--desk-bg); | |
background-image: radial-gradient(var(--desk-dot) 1px,transparent 1px); | |
background-size:14px 14px; | |
font-family:'Crimson Text','Times New Roman',serif; | |
color:var(--paper-text); | |
-webkit-font-smoothing:antialiased; | |
} | |
/* ───────────────────────────────────────── | |
PAPER CONTAINER | |
───────────────────────────────────────── */ | |
.container{ | |
max-width:1100px; | |
margin:40px auto; | |
padding:34px 34px 40px 50px; | |
background:var(--paper-bg); | |
color:var(--paper-text); | |
border:1px solid rgba(0,0,0,.05); | |
border-radius:12px 12px 10px 10px; | |
position:relative; | |
box-shadow:0 18px 40px -22px var(--shadow), | |
inset 0 2px 6px rgba(0,0,0,.06); | |
} | |
/* perforation holes */ | |
.container::before{ | |
content:''; | |
position:absolute;top:26px;bottom:26px;left:30px;width:9px; | |
background-image:radial-gradient(circle var(--perforation) 0%,var(--perforation) 2px,transparent 3px); | |
background-size:9px 28px; | |
background-repeat:repeat-y; | |
pointer-events:none; | |
} | |
/* curled corner */ | |
.container::after{ | |
content:'';position:absolute;top:0;right:0;width:110px;height:110px; | |
background: | |
linear-gradient(135deg,rgba(0,0,0,.08) 0%,rgba(0,0,0,0) 42%), | |
linear-gradient(135deg,var(--paper-bg) 0%,var(--paper-bg) 50%,rgba(255,255,255,0) 51%); | |
border-bottom-left-radius:12px; | |
pointer-events:none; | |
} | |
/* theme toggle */ | |
#themeToggle{ | |
position:absolute;top:12px;right:14px;z-index:10; | |
font-size:20px;background:none;border:none;cursor:pointer; | |
transition:transform .25s; | |
user-select:none; | |
} | |
#themeToggle:hover{transform:rotate(20deg)scale(1.15)} | |
/* header */ | |
.header{text-align:center;margin-bottom:18px;padding-bottom:14px;border-bottom:1px solid rgba(0,0,0,.05)} | |
h1{font-family:'Libre Baskerville',serif;margin:0;font-size:28px;letter-spacing:.4px} | |
.subtitle{font-family:'PT Mono',monospace;font-size:14px;color:#666;margin-top:6px;letter-spacing:1px} | |
/* board */ | |
#board{ | |
min-height:520px; | |
position:relative; | |
padding:18px 6px 10px 6px; | |
display:grid; | |
gap:16px; | |
overflow:visible; | |
/* NEW: prevent short images from becoming tall boxes */ | |
align-items:start; | |
/* NEW: dynamic columns | |
--cols: 1 -> one column (full width), 2 -> two columns (50/50), | |
else auto-fit with a sensible min width */ | |
grid-template-columns: repeat(var(--cols, auto-fit), minmax(var(--minCol, 220px), 1fr)); | |
} | |
#board::before{ | |
content:''; | |
position:absolute;inset:0 6px; | |
background:repeating-linear-gradient( | |
0deg, | |
transparent,transparent 2.8em, | |
var(--board-line) 2.8em,var(--board-line) 2.85em); | |
pointer-events:none;z-index:1;border-radius:8px; | |
} | |
#board > *{position:relative;z-index:2} | |
/* placeholder */ | |
.placeholder{ | |
color:#888;font-style:italic;text-align:center; | |
padding:120px 20px;user-select:none;grid-column:1/-1 | |
} | |
/* sticky notes */ | |
.sticky{ | |
position:relative; | |
border:1px solid var(--note-border); | |
border-radius:10px; | |
color:var(--note-text); | |
box-shadow: | |
0 10px 26px -16px var(--shadow), | |
inset 0 1px 0 rgba(255,255,255,.35); | |
transform:rotate(var(--tilt,0deg)) scale(1); | |
transition:transform .12s ease, box-shadow .12s ease, opacity .16s ease; | |
animation:pop .18s ease-out; | |
/* NEW: ensure the tile itself doesn't stretch via grid */ | |
align-self:start; | |
} | |
.sticky:hover{ | |
transform:rotate(var(--tilt,0deg)) translateY(-2px) scale(1.01); | |
box-shadow: | |
0 14px 32px -18px var(--shadow), | |
inset 0 1px 0 rgba(255,255,255,.45); | |
} | |
@keyframes pop{from{transform:scale(.96);opacity:0}to{transform:scale(1);opacity:1}} | |
.sticky.note-yellow{background:linear-gradient(180deg,var(--note-yellow),color-mix(in oklab,var(--note-yellow) 85%, #000 15%))} | |
.sticky.note-mint {background:linear-gradient(180deg,var(--note-mint), color-mix(in oklab,var(--note-mint) 85%, #000 15%))} | |
.sticky.note-blush {background:linear-gradient(180deg,var(--note-blush), color-mix(in oklab,var(--note-blush) 85%, #000 15%))} | |
.sticky.note-blue {background:linear-gradient(180deg,var(--note-blue), color-mix(in oklab,var(--note-blue) 85%, #000 15%))} | |
.sticky .bar{ | |
display:flex;align-items:center;justify-content:space-between; | |
padding:6px 8px 4px 10px; | |
background:var(--note-bar); | |
border-top-left-radius:10px;border-top-right-radius:10px; | |
font-family:'PT Mono',monospace; | |
font-size:12px;letter-spacing:.3px | |
} | |
.badge{ | |
background:rgba(0,0,0,.08); | |
padding:2px 6px;border-radius:999px;font-weight:600; | |
} | |
body.dark .badge{background:rgba(255,255,255,.12)} | |
.sticky img{ | |
width:100%; | |
height:auto; | |
display:block; | |
border-bottom-left-radius:10px;border-bottom-right-radius:10px; | |
background: linear-gradient(180deg,rgba(255,255,255,.25),rgba(255,255,255,0)); | |
/* NEW: keep gigantic/tall screenshots sane on viewport */ | |
max-height:80vh; | |
} | |
/* delete button */ | |
.btn-del{ | |
position:absolute;top:6px;right:6px; | |
width:26px;height:26px;border-radius:50%; | |
border:1px solid var(--note-border); | |
background:rgba(255,255,255,.35); | |
color:var(--btn-fg);font-weight:700;line-height:24px;text-align:center; | |
cursor:pointer;backdrop-filter:saturate(120%) blur(2px); | |
display:flex;align-items:center;justify-content:center; | |
opacity:.85;transition:background .12s, transform .12s, opacity .12s; | |
} | |
.btn-del:hover{background:var(--btn-bg-hover);transform:scale(1.06);opacity:1} | |
.btn-del:active{transform:scale(.96)} | |
/* processing badge */ | |
.processing{ | |
position:fixed;top:20px;right:20px;background:#333;color:#fff; | |
padding:10px 20px;border-radius:6px;font-family:'PT Mono',monospace; | |
font-size:14px;opacity:0;transition:opacity .25s;z-index:2000 | |
} | |
.processing.show{opacity:.9} | |
/* instructions */ | |
.instructions{ text-align:center;font-family:'PT Mono',monospace;font-size:14px;color:#666;font-style:italic;margin-top:16px } | |
/* responsive & print */ | |
@media(max-width:768px){ | |
.container{margin:20px 16px;padding:24px} | |
h1{font-size:24px} | |
/* CHANGED: still use dynamic columns on mobile, but with a smaller min */ | |
#board{ | |
grid-template-columns: repeat(var(--cols, auto-fit), minmax(180px,1fr)); | |
} | |
} | |
@media print{ | |
body{background:#fff} | |
.container{box-shadow:none;border:none} | |
.header,.processing,.instructions,#themeToggle{display:none} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<!-- theme icon --> | |
<button id="themeToggle" title="Toggle dark / light">🌙</button> | |
<div class="header"> | |
<h1>Question Sticky Board</h1> | |
<div class="subtitle">press ctrl+v anywhere to paste images of questions</div> | |
</div> | |
<div id="board"> | |
<div class="placeholder"> | |
Press <kbd>Ctrl</kbd>+<kbd>V</kbd> (or <kbd>⌘</kbd>+<kbd>V</kbd>) to paste screenshots/images.<br/> | |
You can also drag & drop images here. | |
</div> | |
</div> | |
<div class="instructions"> | |
Tip: each image becomes a sticky. Click the tiny × to delete. | |
</div> | |
</div> | |
<div class="processing">Processing…</div> | |
<script> | |
/* ======= helpers ======= */ | |
const board = document.getElementById('board'); | |
const processingNode = document.querySelector('.processing'); | |
const themeBtn = document.getElementById('themeToggle'); | |
let noteCount = 0; | |
function showProcessing(){ processingNode.classList.add('show') } | |
function hideProcessing(){ setTimeout(()=>processingNode.classList.remove('show'),250) } | |
function removePlaceholder(){ | |
const ph = board.querySelector('.placeholder'); | |
if(ph) ph.remove(); | |
} | |
function ensurePlaceholder(){ | |
if(!board.querySelector('.sticky') && !board.querySelector('.placeholder')){ | |
const ph = document.createElement('div'); | |
ph.className = 'placeholder'; | |
ph.innerHTML = `Press <kbd>Ctrl</kbd>+<kbd>V</kbd> (or <kbd>⌘</kbd>+<kbd>V</kbd>) to paste screenshots/images.<br/>You can also drag & drop images here.`; | |
board.appendChild(ph); | |
} | |
} | |
/* NEW: set grid columns smartly based on number of stickies */ | |
function updateLayout(){ | |
const count = board.querySelectorAll('.sticky').length; | |
if(count === 1){ | |
// 1 image → full width (one column) | |
board.style.setProperty('--cols', '1'); | |
board.style.setProperty('--minCol', '0px'); | |
}else if(count === 2){ | |
// 2 images → half/half | |
board.style.setProperty('--cols', '2'); | |
board.style.setProperty('--minCol', '0px'); | |
}else{ | |
// 3+ → responsive, nice min width | |
board.style.setProperty('--cols', 'auto-fit'); | |
board.style.setProperty('--minCol', '220px'); | |
} | |
} | |
/* random tilt & color */ | |
function randTilt(){ | |
const d = (Math.random()*4.4 - 2.2).toFixed(2); | |
return d + 'deg'; | |
} | |
function pickColorClass(){ | |
const choices = ['note-yellow','note-mint','note-blush','note-blue']; | |
return choices[Math.floor(Math.random()*choices.length)]; | |
} | |
/* ======= create sticky from an image URL ======= */ | |
function createSticky(src, isBlobUrl=false){ | |
removePlaceholder(); | |
noteCount++; | |
const wrap = document.createElement('div'); | |
const colorClass = pickColorClass(); | |
wrap.className = `sticky ${colorClass}`; | |
wrap.style.setProperty('--tilt', randTilt()); | |
// header bar | |
const bar = document.createElement('div'); | |
bar.className = 'bar'; | |
bar.innerHTML = `<span class="badge">Q${noteCount}</span><span style="opacity:.7">pasted</span>`; | |
// image | |
const img = document.createElement('img'); | |
img.alt = `Question ${noteCount}`; | |
img.src = src; | |
// delete button | |
const btn = document.createElement('button'); | |
btn.className = 'btn-del'; | |
btn.setAttribute('aria-label','Delete sticky'); | |
btn.textContent = '×'; | |
btn.addEventListener('click',()=>{ | |
wrap.style.opacity = '0'; | |
wrap.style.transform = 'scale(.96)'; | |
setTimeout(()=>{ | |
if(isBlobUrl) URL.revokeObjectURL(src); | |
wrap.remove(); | |
ensurePlaceholder(); | |
updateLayout(); // NEW: update grid when items are removed | |
},140); | |
}); | |
wrap.appendChild(bar); | |
wrap.appendChild(img); | |
wrap.appendChild(btn); | |
board.prepend(wrap); // newest on top | |
updateLayout(); // NEW: update grid after adding | |
} | |
/* ======= handle pasted images ======= */ | |
function filesFromClipboard(dataTransfer){ | |
const items = dataTransfer?.items || []; | |
const files = []; | |
for(const it of items){ | |
if(it.kind === 'file' && it.type.startsWith('image/')){ | |
const f = it.getAsFile(); | |
if(f) files.push(f); | |
} | |
} | |
return files; | |
} | |
async function handleImages(files){ | |
if(!files.length){ | |
processingNode.textContent = 'No images found in clipboard'; | |
showProcessing(); hideProcessing(); | |
processingNode.textContent = 'Processing…'; | |
return; | |
} | |
showProcessing(); | |
for(const file of files){ | |
const url = URL.createObjectURL(file); | |
createSticky(url, true); | |
} | |
hideProcessing(); | |
} | |
/* ======= paste listener ======= */ | |
document.addEventListener('paste', e=>{ | |
const files = filesFromClipboard(e.clipboardData); | |
if(files.length){ e.preventDefault(); handleImages(files); } | |
}); | |
/* ======= drag & drop ======= */ | |
board.addEventListener('dragover', e=>{ | |
e.preventDefault(); | |
board.style.outline = '2px dashed rgba(0,0,0,.2)'; | |
}); | |
board.addEventListener('dragleave', ()=>{ | |
board.style.outline = 'none'; | |
}); | |
board.addEventListener('drop', e=>{ | |
e.preventDefault(); | |
board.style.outline = 'none'; | |
const dtFiles = [...(e.dataTransfer?.files || [])].filter(f=>f.type.startsWith('image/')); | |
handleImages(dtFiles); | |
}); | |
/* ======= theme toggler ======= */ | |
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)'); | |
const savedTheme = localStorage.getItem('note-theme'); | |
initTheme(); | |
themeBtn.addEventListener('click',()=>{ | |
document.body.classList.toggle('dark'); | |
updateIcon(); | |
localStorage.setItem('note-theme', document.body.classList.contains('dark') ? 'dark' : 'light'); | |
}); | |
function initTheme(){ | |
if(savedTheme){ | |
document.body.classList.toggle('dark', savedTheme==='dark'); | |
}else if(prefersDark.matches){ | |
document.body.classList.add('dark'); | |
} | |
updateIcon(); | |
} | |
function updateIcon(){ | |
themeBtn.textContent = document.body.classList.contains('dark') ? '☀️' : '🌙'; | |
} | |
/* ======= smooth fade in ======= */ | |
document.addEventListener('DOMContentLoaded',()=>{ | |
const sheet=document.querySelector('.container'); | |
sheet.style.opacity='0'; | |
setTimeout(()=>{ | |
sheet.style.transition='opacity .6s ease'; | |
sheet.style.opacity='1'; | |
updateLayout(); // NEW: set initial grid | |
},80); | |
}); | |
</script> | |
</body> | |
</html> |