SolarumAsteridion commited on
Commit
d197106
·
verified ·
1 Parent(s): c744306

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +256 -149
index.html CHANGED
@@ -3,28 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width,initial-scale=1" />
6
- <title>LaTeX Notepad</title>
7
-
8
- <!-- ──────── MathJax ──────── -->
9
- <script>
10
- window.MathJax = {
11
- tex: {
12
- inlineMath: [['$', '$'], ['\\(', '\\)']],
13
- displayMath: [['$$', '$$'], ['\\[', '\\]']],
14
- processEscapes: true,
15
- processEnvironments: true,
16
- },
17
- options: { skipHtmlTags: ['script','noscript','style','textarea','pre'] }
18
- };
19
- </script>
20
- <script
21
- id="MathJax-script"
22
- async
23
- src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"
24
- ></script>
25
-
26
- <!-- marked.js for Markdown -->
27
- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
28
 
29
  <!-- Google fonts -->
30
  <link
@@ -36,7 +15,7 @@ window.MathJax = {
36
  /* ─────────────────────────────────────────
37
  COLOR SYSTEM (light / dark via variables)
38
  ───────────────────────────────────────── */
39
- :root {
40
  --desk-bg: #fbf9f5;
41
  --desk-dot: #e2dccd;
42
 
@@ -44,18 +23,25 @@ window.MathJax = {
44
  --paper-text: #222;
45
  --shadow: rgba(0,0,0,.35);
46
 
47
- --line: rgba(201,190,170,.18);
48
  --perforation: #d0c6b7;
49
 
50
- --tbl-border: #d7cebf;
51
- --blockquote-bg: #fbf8f1;
52
- --blockquote-bar: #ccbfae;
53
- --code-bg: #f4f2ec;
54
- --code-border: #e6e0d2;
55
- --inline-code-bg: #f2efe8;
56
- }
57
 
58
- body.dark {
 
 
 
 
 
 
 
 
59
  --desk-bg: #2c2a27;
60
  --desk-dot: #3a3733;
61
 
@@ -63,15 +49,22 @@ body.dark {
63
  --paper-text: #e9e7e2;
64
  --shadow: rgba(0,0,0,.55);
65
 
66
- --line: rgba(110,103,94,.28);
67
  --perforation: #6d6456;
68
 
69
- --tbl-border: #555048;
70
- --blockquote-bg: #38332e;
71
- --blockquote-bar: #6a604e;
72
- --code-bg: #3a3530;
73
- --code-border: #514b42;
74
- --inline-code-bg: #4a443d;
 
 
 
 
 
 
 
 
75
  }
76
 
77
  /* ─────────────────────────────────────────
@@ -85,17 +78,16 @@ body{
85
  background-size:14px 14px;
86
  font-family:'Crimson Text','Times New Roman',serif;
87
  color:var(--paper-text);
88
- line-height:1.8;
89
  -webkit-font-smoothing:antialiased;
90
  }
91
 
92
  /* ─────────────────────────────────────────
93
- PAPER SHEET
94
  ───────────────────────────────────────── */
95
  .container{
96
- max-width:840px;
97
  margin:40px auto;
98
- padding:40px 60px 60px;
99
  background:var(--paper-bg);
100
  color:var(--paper-text);
101
  border:1px solid rgba(0,0,0,.05);
@@ -103,7 +95,6 @@ body{
103
  position:relative;
104
  box-shadow:0 18px 40px -22px var(--shadow),
105
  inset 0 2px 6px rgba(0,0,0,.06);
106
- background-size:160px 160px,100% 100%;
107
  }
108
 
109
  /* perforation holes */
@@ -121,77 +112,113 @@ body{
121
  background:
122
  linear-gradient(135deg,rgba(0,0,0,.08) 0%,rgba(0,0,0,0) 42%),
123
  linear-gradient(135deg,var(--paper-bg) 0%,var(--paper-bg) 50%,rgba(255,255,255,0) 51%);
124
- background-size:100% 100%;
125
  border-bottom-left-radius:12px;
126
- transform:translate(1px,-1px);
127
  pointer-events:none;
128
  }
129
 
130
- /* dark-mode gradient uses the *same* var so stays consistent */
131
-
132
- /* ───────── theme toggle ───────── */
133
  #themeToggle{
134
- position:absolute;top:12px;right:14px;
135
  font-size:20px;background:none;border:none;cursor:pointer;
136
  transition:transform .25s;
137
  user-select:none;
138
  }
139
-
140
- /* put this anywhere after the existing #themeToggle rule */
141
- #themeToggle{
142
- position:absolute; /* you already have this */
143
- z-index:10; /* NEW – lift it above the curl */
144
- }
145
-
146
-
147
  #themeToggle:hover{transform:rotate(20deg)scale(1.15)}
148
 
149
- /* ───────── header ───────── */
150
- .header{text-align:center;margin-bottom:34px;padding-bottom:18px;border-bottom:1px solid rgba(0,0,0,.05)}
151
- h1{font-family:'Libre Baskerville',serif;margin:0;font-size:30px;letter-spacing:.5px}
152
  .subtitle{font-family:'PT Mono',monospace;font-size:14px;color:#666;margin-top:6px;letter-spacing:1px}
153
 
154
- /* ───────── content area ───────── */
155
- #content{
156
- min-height:520px;font-size:18px;position:relative;
157
- padding:10px 0 10px 26px;overflow-wrap:break-word;hyphens:auto;
 
 
 
 
 
158
  }
159
- #content::before{
160
- content:'';position:absolute;inset:0;
 
161
  background:repeating-linear-gradient(
162
  0deg,
163
- transparent,transparent 2.65em,
164
- var(--line) 2.65em,var(--line) 2.7em);
165
- pointer-events:none;z-index:1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  }
167
- #content *{position:relative;z-index:2}
168
- .placeholder{color:#888;font-style:italic;text-align:center;padding:110px 20px;user-select:none}
169
-
170
- /* ───────── markdown tweaks ───────── */
171
- blockquote{
172
- border-left:4px solid var(--blockquote-bar);
173
- margin:20px 0;padding:15px 26px;
174
- background:var(--blockquote-bg);font-style:italic
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  }
176
- code{font-family:'PT Mono',monospace;background:var(--inline-code-bg);
177
- padding:2px 6px;border-radius:3px;font-size:.9em}
178
- pre{background:var(--code-bg);padding:16px 20px;border:1px solid var(--code-border);
179
- border-radius:6px;overflow-x:auto;font-family:'PT Mono',monospace}
180
-
181
- /* lists */
182
- ol{counter-reset:item;padding-left:0;list-style:none}
183
- ol>li{counter-increment:item;margin:.5em 0 .5em 2em}
184
- ol>li::before{content:counter(item)')';display:inline-block;width:1.5em;margin-left:-2em;text-align:right;font-weight:600}
185
- ol ol>li::before{content:counter(item,lower-alpha)')'}
186
-
187
- /* ───────── TABLES ───────── */
188
- table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums;margin:1.2em 0}
189
- thead tr{border-bottom:1px solid var(--tbl-border)}
190
- tbody tr:not(:last-child){border-bottom:1px solid var(--tbl-border)}
191
- th,td{padding:.55em .8em;text-align:right}
192
- th{font-weight:600}
193
-
194
- /* ───────── processing badge ───────── */
195
  .processing{
196
  position:fixed;top:20px;right:20px;background:#333;color:#fff;
197
  padding:10px 20px;border-radius:6px;font-family:'PT Mono',monospace;
@@ -199,11 +226,14 @@ th{font-weight:600}
199
  }
200
  .processing.show{opacity:.9}
201
 
 
 
 
202
  /* responsive & print */
203
  @media(max-width:768px){
204
- .container{margin:20px 16px;padding:28px}
205
- #content{font-size:16px}
206
  h1{font-size:24px}
 
207
  }
208
  @media print{
209
  body{background:#fff}
@@ -219,100 +249,177 @@ th{font-weight:600}
219
  <button id="themeToggle" title="Toggle dark / light">🌙</button>
220
 
221
  <div class="header">
222
- <h1>LaTeX Notepad</h1>
223
- <div class="subtitle">press ctrl+v anywhere to render</div>
224
  </div>
225
 
226
- <div id="content">
227
  <div class="placeholder">
228
- Press <kbd>Ctrl</kbd>+<kbd>V</kbd> (or <kbd>⌘</kbd>+<kbd>V</kbd>) to paste and render Markdown / LaTeX
 
229
  </div>
230
  </div>
231
 
232
- <div class="instructions" style="text-align:center;font-family:'PT Mono',monospace;font-size:14px;color:#666;font-style:italic;margin-top:22px;">
233
- Tip: you can paste raw Markdown or TeX it will be rendered instantly ✨
234
  </div>
235
  </div>
236
 
237
  <div class="processing">Processing…</div>
238
 
239
  <script>
240
- /* ======= processing badge helpers ======= */
241
- const content = document.getElementById('content');
242
  const processingNode = document.querySelector('.processing');
 
243
 
244
- function showProcessing(){processingNode.classList.add('show')}
245
- function hideProcessing(){setTimeout(()=>processingNode.classList.remove('show'),300)}
246
 
247
- /* ======= markdown + latex pipeline ======= */
248
- function processContent(text){
249
- showProcessing();
250
 
251
- const store=[], PL=i=>`%%LATEX_${i}%%`; let idx=0;
252
- const keep=m=>(store.push(m),PL(idx++));
 
 
253
 
254
- text = text
255
- .replace(/\\\[[\s\S]*?\\\]/g, keep) // \[ ... \]
256
- .replace(/\$\$[\s\S]*?\$\$/g, keep) // $$ ... $$
257
- .replace(/\\\([\s\S]*?\\\)/g, keep) // \( ... \)
258
- .replace(/\$([^\$\n]+?)\$/g, keep); // $ ... $
 
 
 
 
259
 
260
- let html = marked.parse(text);
261
- store.forEach((latex,i)=>{html=html.replaceAll(PL(i),latex)});
262
- content.innerHTML = html;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
 
264
- if(window.MathJax?.typesetPromise){
265
- MathJax.typesetPromise([content]).then(hideProcessing)
266
- .catch(e=>{console.error('MathJax error:',e);hideProcessing()});
267
- }else{hideProcessing()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  }
269
 
270
  /* ======= paste listener ======= */
271
- document.addEventListener('paste',e=>{
272
- e.preventDefault();
273
- const txt=e.clipboardData.getData('text/plain');
274
- if(txt.trim())processContent(txt);
275
  });
276
 
277
- /* small “bounce” on placeholder click */
278
- content.addEventListener('click',()=>{
279
- const ph=content.querySelector('.placeholder');
280
- if(ph){
281
- ph.style.transform='scale(.97)';
282
- ph.style.transition='transform .12s';
283
- setTimeout(()=>ph.style.transform='scale(1)',120);
284
- }
285
  });
286
-
287
- /* smooth fade in */
288
- document.addEventListener('DOMContentLoaded',()=>{
289
- const sheet=document.querySelector('.container');
290
- sheet.style.opacity='0';
291
- setTimeout(()=>{sheet.style.transition='opacity .6s ease';sheet.style.opacity='1'},80);
 
 
292
  });
293
 
294
  /* ======= theme toggler ======= */
295
- const btn = document.getElementById('themeToggle');
296
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
297
  const savedTheme = localStorage.getItem('note-theme');
298
 
299
  initTheme();
300
- btn.addEventListener('click',()=>{
301
  document.body.classList.toggle('dark');
302
  updateIcon();
303
- localStorage.setItem('note-theme',document.body.classList.contains('dark')?'dark':'light');
304
  });
305
  function initTheme(){
306
  if(savedTheme){
307
- document.body.classList.toggle('dark',savedTheme==='dark');
308
  }else if(prefersDark.matches){
309
  document.body.classList.add('dark');
310
  }
311
  updateIcon();
312
  }
313
  function updateIcon(){
314
- btn.textContent=document.body.classList.contains('dark')?'☀️':'🌙';
315
  }
 
 
 
 
 
 
 
316
  </script>
317
  </body>
318
  </html>
 
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>Question Sticky Board</title>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  <!-- Google fonts -->
9
  <link
 
15
  /* ─────────────────────────────────────────
16
  COLOR SYSTEM (light / dark via variables)
17
  ───────────────────────────────────────── */
18
+ :root{
19
  --desk-bg: #fbf9f5;
20
  --desk-dot: #e2dccd;
21
 
 
23
  --paper-text: #222;
24
  --shadow: rgba(0,0,0,.35);
25
 
 
26
  --perforation: #d0c6b7;
27
 
28
+ --board-line: rgba(201,190,170,.18);
29
+
30
+ /* sticky colors */
31
+ --note-yellow: #fff6a8;
32
+ --note-mint: #d8f7e6;
33
+ --note-blush: #ffe3e0;
34
+ --note-blue: #e6f0ff;
35
 
36
+ --note-border: rgba(0,0,0,.08);
37
+ --note-bar: rgba(0,0,0,.04);
38
+ --note-text: #2b2b2b;
39
+
40
+ --btn-fg: #444;
41
+ --btn-fg-dim: #666;
42
+ --btn-bg-hover: rgba(0,0,0,.08);
43
+ }
44
+ body.dark{
45
  --desk-bg: #2c2a27;
46
  --desk-dot: #3a3733;
47
 
 
49
  --paper-text: #e9e7e2;
50
  --shadow: rgba(0,0,0,.55);
51
 
 
52
  --perforation: #6d6456;
53
 
54
+ --board-line: rgba(110,103,94,.28);
55
+
56
+ --note-yellow: #6b6434;
57
+ --note-mint: #3f5a50;
58
+ --note-blush: #5b3f3c;
59
+ --note-blue: #3e4c63;
60
+
61
+ --note-border: rgba(0,0,0,.25);
62
+ --note-bar: rgba(255,255,255,.05);
63
+ --note-text: #f1efe9;
64
+
65
+ --btn-fg: #ddd;
66
+ --btn-fg-dim: #aaa;
67
+ --btn-bg-hover: rgba(255,255,255,.1);
68
  }
69
 
70
  /* ─────────────────────────────────────────
 
78
  background-size:14px 14px;
79
  font-family:'Crimson Text','Times New Roman',serif;
80
  color:var(--paper-text);
 
81
  -webkit-font-smoothing:antialiased;
82
  }
83
 
84
  /* ─────────────────────────────────────────
85
+ PAPER CONTAINER
86
  ───────────────────────────────────────── */
87
  .container{
88
+ max-width:1100px;
89
  margin:40px auto;
90
+ padding:34px 34px 40px 50px;
91
  background:var(--paper-bg);
92
  color:var(--paper-text);
93
  border:1px solid rgba(0,0,0,.05);
 
95
  position:relative;
96
  box-shadow:0 18px 40px -22px var(--shadow),
97
  inset 0 2px 6px rgba(0,0,0,.06);
 
98
  }
99
 
100
  /* perforation holes */
 
112
  background:
113
  linear-gradient(135deg,rgba(0,0,0,.08) 0%,rgba(0,0,0,0) 42%),
114
  linear-gradient(135deg,var(--paper-bg) 0%,var(--paper-bg) 50%,rgba(255,255,255,0) 51%);
 
115
  border-bottom-left-radius:12px;
 
116
  pointer-events:none;
117
  }
118
 
119
+ /* theme toggle */
 
 
120
  #themeToggle{
121
+ position:absolute;top:12px;right:14px;z-index:10;
122
  font-size:20px;background:none;border:none;cursor:pointer;
123
  transition:transform .25s;
124
  user-select:none;
125
  }
 
 
 
 
 
 
 
 
126
  #themeToggle:hover{transform:rotate(20deg)scale(1.15)}
127
 
128
+ /* header */
129
+ .header{text-align:center;margin-bottom:18px;padding-bottom:14px;border-bottom:1px solid rgba(0,0,0,.05)}
130
+ h1{font-family:'Libre Baskerville',serif;margin:0;font-size:28px;letter-spacing:.4px}
131
  .subtitle{font-family:'PT Mono',monospace;font-size:14px;color:#666;margin-top:6px;letter-spacing:1px}
132
 
133
+ /* board */
134
+ #board{
135
+ min-height:520px;
136
+ position:relative;
137
+ padding:18px 6px 10px 6px;
138
+ display:grid;
139
+ grid-template-columns:repeat(auto-fill,minmax(220px,1fr));
140
+ gap:16px;
141
+ overflow:visible;
142
  }
143
+ #board::before{
144
+ content:'';
145
+ position:absolute;inset:0 6px;
146
  background:repeating-linear-gradient(
147
  0deg,
148
+ transparent,transparent 2.8em,
149
+ var(--board-line) 2.8em,var(--board-line) 2.85em);
150
+ pointer-events:none;z-index:1;border-radius:8px;
151
+ }
152
+ #board > *{position:relative;z-index:2}
153
+
154
+ /* placeholder */
155
+ .placeholder{
156
+ color:#888;font-style:italic;text-align:center;
157
+ padding:120px 20px;user-select:none;grid-column:1/-1
158
+ }
159
+
160
+ /* sticky notes */
161
+ .sticky{
162
+ position:relative;
163
+ border:1px solid var(--note-border);
164
+ border-radius:10px;
165
+ color:var(--note-text);
166
+ box-shadow:
167
+ 0 10px 26px -16px var(--shadow),
168
+ inset 0 1px 0 rgba(255,255,255,.35);
169
+ transform:rotate(var(--tilt,0deg)) scale(1);
170
+ transition:transform .12s ease, box-shadow .12s ease, opacity .16s ease;
171
+ animation:pop .18s ease-out;
172
  }
173
+ .sticky:hover{
174
+ transform:rotate(var(--tilt,0deg)) translateY(-2px) scale(1.01);
175
+ box-shadow:
176
+ 0 14px 32px -18px var(--shadow),
177
+ inset 0 1px 0 rgba(255,255,255,.45);
178
+ }
179
+ @keyframes pop{from{transform:scale(.96);opacity:0}to{transform:scale(1);opacity:1}}
180
+
181
+ .sticky.note-yellow{background:linear-gradient(180deg,var(--note-yellow),color-mix(in oklab,var(--note-yellow) 85%, #000 15%))}
182
+ .sticky.note-mint {background:linear-gradient(180deg,var(--note-mint), color-mix(in oklab,var(--note-mint) 85%, #000 15%))}
183
+ .sticky.note-blush {background:linear-gradient(180deg,var(--note-blush), color-mix(in oklab,var(--note-blush) 85%, #000 15%))}
184
+ .sticky.note-blue {background:linear-gradient(180deg,var(--note-blue), color-mix(in oklab,var(--note-blue) 85%, #000 15%))}
185
+
186
+ .sticky .bar{
187
+ display:flex;align-items:center;justify-content:space-between;
188
+ padding:6px 8px 4px 10px;
189
+ background:var(--note-bar);
190
+ border-top-left-radius:10px;border-top-right-radius:10px;
191
+ font-family:'PT Mono',monospace;
192
+ font-size:12px;letter-spacing:.3px
193
+ }
194
+ .badge{
195
+ background:rgba(0,0,0,.08);
196
+ padding:2px 6px;border-radius:999px;font-weight:600;
197
+ }
198
+ body.dark .badge{background:rgba(255,255,255,.12)}
199
+
200
+ .sticky img{
201
+ width:100%;height:auto;display:block;
202
+ border-bottom-left-radius:10px;border-bottom-right-radius:10px;
203
+ background:
204
+ linear-gradient(180deg,rgba(255,255,255,.25),rgba(255,255,255,0));
205
+ }
206
+
207
+ /* delete button */
208
+ .btn-del{
209
+ position:absolute;top:6px;right:6px;
210
+ width:26px;height:26px;border-radius:50%;
211
+ border:1px solid var(--note-border);
212
+ background:rgba(255,255,255,.35);
213
+ color:var(--btn-fg);font-weight:700;line-height:24px;text-align:center;
214
+ cursor:pointer;backdrop-filter:saturate(120%) blur(2px);
215
+ display:flex;align-items:center;justify-content:center;
216
+ opacity:.85;transition:background .12s, transform .12s, opacity .12s;
217
  }
218
+ .btn-del:hover{background:var(--btn-bg-hover);transform:scale(1.06);opacity:1}
219
+ .btn-del:active{transform:scale(.96)}
220
+
221
+ /* processing badge */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  .processing{
223
  position:fixed;top:20px;right:20px;background:#333;color:#fff;
224
  padding:10px 20px;border-radius:6px;font-family:'PT Mono',monospace;
 
226
  }
227
  .processing.show{opacity:.9}
228
 
229
+ /* instructions */
230
+ .instructions{ text-align:center;font-family:'PT Mono',monospace;font-size:14px;color:#666;font-style:italic;margin-top:16px }
231
+
232
  /* responsive & print */
233
  @media(max-width:768px){
234
+ .container{margin:20px 16px;padding:24px}
 
235
  h1{font-size:24px}
236
+ #board{grid-template-columns:repeat(auto-fill,minmax(180px,1fr))}
237
  }
238
  @media print{
239
  body{background:#fff}
 
249
  <button id="themeToggle" title="Toggle dark / light">🌙</button>
250
 
251
  <div class="header">
252
+ <h1>Question Sticky Board</h1>
253
+ <div class="subtitle">press ctrl+v anywhere to paste images of questions</div>
254
  </div>
255
 
256
+ <div id="board">
257
  <div class="placeholder">
258
+ Press <kbd>Ctrl</kbd>+<kbd>V</kbd> (or <kbd>⌘</kbd>+<kbd>V</kbd>) to paste screenshots/images.<br/>
259
+ You can also drag &amp; drop images here.
260
  </div>
261
  </div>
262
 
263
+ <div class="instructions">
264
+ Tip: each image becomes a sticky. Click the tiny × to delete.
265
  </div>
266
  </div>
267
 
268
  <div class="processing">Processing…</div>
269
 
270
  <script>
271
+ /* ======= helpers ======= */
272
+ const board = document.getElementById('board');
273
  const processingNode = document.querySelector('.processing');
274
+ const themeBtn = document.getElementById('themeToggle');
275
 
276
+ let noteCount = 0;
 
277
 
278
+ function showProcessing(){ processingNode.classList.add('show') }
279
+ function hideProcessing(){ setTimeout(()=>processingNode.classList.remove('show'),250) }
 
280
 
281
+ function removePlaceholder(){
282
+ const ph = board.querySelector('.placeholder');
283
+ if(ph) ph.remove();
284
+ }
285
 
286
+ function randTilt(){
287
+ // small friendly tilt between -2.2deg and 2.2deg
288
+ const d = (Math.random()*4.4 - 2.2).toFixed(2);
289
+ return d + 'deg';
290
+ }
291
+ function pickColorClass(){
292
+ const choices = ['note-yellow','note-mint','note-blush','note-blue'];
293
+ return choices[Math.floor(Math.random()*choices.length)];
294
+ }
295
 
296
+ /* ======= create sticky from an image URL ======= */
297
+ function createSticky(src, isBlobUrl=false){
298
+ removePlaceholder();
299
+ noteCount++;
300
+
301
+ const wrap = document.createElement('div');
302
+ const colorClass = pickColorClass();
303
+ wrap.className = `sticky ${colorClass}`;
304
+ wrap.style.setProperty('--tilt', randTilt());
305
+
306
+ // header bar
307
+ const bar = document.createElement('div');
308
+ bar.className = 'bar';
309
+ bar.innerHTML = `<span class="badge">Q${noteCount}</span><span style="opacity:.7">pasted</span>`;
310
+
311
+ // image
312
+ const img = document.createElement('img');
313
+ img.alt = `Question ${noteCount}`;
314
+ img.src = src;
315
+
316
+ // delete button
317
+ const btn = document.createElement('button');
318
+ btn.className = 'btn-del';
319
+ btn.setAttribute('aria-label','Delete sticky');
320
+ btn.textContent = '×';
321
+
322
+ btn.addEventListener('click',()=>{
323
+ wrap.style.opacity = '0';
324
+ wrap.style.transform = 'scale(.96)';
325
+ setTimeout(()=>{
326
+ // release blob URL to avoid memory leaks
327
+ if(isBlobUrl) URL.revokeObjectURL(src);
328
+ wrap.remove();
329
+ if(!board.querySelector('.sticky')){
330
+ // restore placeholder if empty
331
+ const ph = document.createElement('div');
332
+ ph.className = 'placeholder';
333
+ 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.`;
334
+ board.appendChild(ph);
335
+ }
336
+ },140);
337
+ });
338
+
339
+ wrap.appendChild(bar);
340
+ wrap.appendChild(img);
341
+ wrap.appendChild(btn);
342
+ board.prepend(wrap); // newest on top
343
+ }
344
 
345
+ /* ======= handle pasted images ======= */
346
+ function filesFromClipboard(dataTransfer){
347
+ const items = dataTransfer?.items || [];
348
+ const files = [];
349
+ for(const it of items){
350
+ if(it.kind === 'file' && it.type.startsWith('image/')){
351
+ const f = it.getAsFile();
352
+ if(f) files.push(f);
353
+ }
354
+ }
355
+ return files;
356
+ }
357
+
358
+ async function handleImages(files){
359
+ if(!files.length){
360
+ // no images, brief nudge
361
+ processingNode.textContent = 'No images found in clipboard';
362
+ showProcessing(); hideProcessing();
363
+ processingNode.textContent = 'Processing…';
364
+ return;
365
+ }
366
+ showProcessing();
367
+ for(const file of files){
368
+ const url = URL.createObjectURL(file);
369
+ createSticky(url, true);
370
+ }
371
+ hideProcessing();
372
  }
373
 
374
  /* ======= paste listener ======= */
375
+ document.addEventListener('paste', e=>{
376
+ const files = filesFromClipboard(e.clipboardData);
377
+ if(files.length){ e.preventDefault(); handleImages(files); }
 
378
  });
379
 
380
+ /* ======= drag & drop (optional bonus) ======= */
381
+ board.addEventListener('dragover', e=>{
382
+ e.preventDefault();
383
+ board.style.outline = '2px dashed rgba(0,0,0,.2)';
 
 
 
 
384
  });
385
+ board.addEventListener('dragleave', ()=>{
386
+ board.style.outline = 'none';
387
+ });
388
+ board.addEventListener('drop', e=>{
389
+ e.preventDefault();
390
+ board.style.outline = 'none';
391
+ const dtFiles = [...(e.dataTransfer?.files || [])].filter(f=>f.type.startsWith('image/'));
392
+ handleImages(dtFiles);
393
  });
394
 
395
  /* ======= theme toggler ======= */
 
396
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
397
  const savedTheme = localStorage.getItem('note-theme');
398
 
399
  initTheme();
400
+ themeBtn.addEventListener('click',()=>{
401
  document.body.classList.toggle('dark');
402
  updateIcon();
403
+ localStorage.setItem('note-theme', document.body.classList.contains('dark') ? 'dark' : 'light');
404
  });
405
  function initTheme(){
406
  if(savedTheme){
407
+ document.body.classList.toggle('dark', savedTheme==='dark');
408
  }else if(prefersDark.matches){
409
  document.body.classList.add('dark');
410
  }
411
  updateIcon();
412
  }
413
  function updateIcon(){
414
+ themeBtn.textContent = document.body.classList.contains('dark') ? '☀️' : '🌙';
415
  }
416
+
417
+ /* ======= smooth fade in ======= */
418
+ document.addEventListener('DOMContentLoaded',()=>{
419
+ const sheet=document.querySelector('.container');
420
+ sheet.style.opacity='0';
421
+ setTimeout(()=>{sheet.style.transition='opacity .6s ease';sheet.style.opacity='1'},80);
422
+ });
423
  </script>
424
  </body>
425
  </html>