Pinboard / index.html
SolarumAsteridion's picture
Update index.html
43f431b verified
raw
history blame
14.7 kB
<!DOCTYPE html>
<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 &amp; 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 &amp; 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>