SolarumAsteridion commited on
Commit
4c5f136
·
verified ·
1 Parent(s): 77be291

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +213 -286
index.html CHANGED
@@ -5,7 +5,7 @@
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: {
@@ -14,9 +14,7 @@ window.MathJax = {
14
  processEscapes: true,
15
  processEnvironments: true,
16
  },
17
- options: {
18
- skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
19
- },
20
  };
21
  </script>
22
  <script
@@ -25,259 +23,193 @@ window.MathJax = {
25
  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"
26
  ></script>
27
 
28
- <!-- marked.js -->
29
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
30
 
31
- <!-- Google-fonts -->
32
  <link
33
  href="https://fonts.googleapis.com/css2?family=Crimson+Text:wght@400;600&family=Libre+Baskerville:wght@400;700&family=PT+Mono&display=swap"
34
  rel="stylesheet"
35
  />
36
 
37
  <style>
38
- /* ----------- GLOBAL “DESK” BACKGROUND ---------- */
39
- html,
40
- body {
41
- height: 100%;
42
- }
43
- body {
44
- margin: 0;
45
- background: #fbf9f5;
46
- background-image: radial-gradient(#e2dccd 1px, transparent 1px);
47
- background-size: 14px 14px;
48
- font-family: 'Crimson Text', 'Times New Roman', serif;
49
- color: #222;
50
- line-height: 1.8;
51
- -webkit-font-smoothing: antialiased;
 
 
 
 
 
 
52
  }
53
 
54
- /* ------------- PAPER SHEET -------------------- */
55
- .container {
56
- max-width: 840px;
57
- margin: 40px auto;
58
- padding: 40px 60px 60px;
59
- background: #fffefa; /* warm paper colour */
60
- border-radius: 12px 12px 10px 10px;
61
- position: relative;
62
- border: 1px solid #ede7db;
63
-
64
- /* subtle inset shadow (feels like a notebook page) */
65
- box-shadow:
66
- 0 18px 40px -22px rgba(0, 0, 0, 0.35),
67
- inset 0 2px 6px rgba(0, 0, 0, 0.06);
68
-
69
- /* tiny surface grain */
70
- background-image:
71
- url('data:image/svg+xml;utf8,\
72
- <svg xmlns="http://www.w3.org/2000/svg" width=\"160\" height=\"160\" viewBox=\"0 0 40 40\" fill=\"%23dcd2c3\" opacity=\"0.35\">\
73
- <circle cx=\"1\" cy=\"1\" r=\"0.5\"/><circle cx=\"13\" cy=\"7\" r=\"0.4\"/>\
74
- <circle cx=\"27\" cy=\"4\" r=\"0.6\"/><circle cx=\"37\" cy=\"14\" r=\"0.5\"/>\
75
- <circle cx=\"20\" cy=\"20\" r=\"0.7\"/><circle cx=\"6\" cy=\"28\" r=\"0.4\"/>\
76
- <circle cx=\"32\" cy=\"32\" r=\"0.5\"/><circle cx=\"11\" cy=\"38\" r=\"0.7\"/>\
77
- </svg>');
78
- background-size: 160px 160px, 100% 100%;
79
- }
80
 
81
- /* perforation holes (left edge) */
82
- .container::before {
83
- content: '';
84
- position: absolute;
85
- top: 26px;
86
- bottom: 26px;
87
- left: 30px;
88
- width: 9px;
89
- background-image: radial-gradient(
90
- circle at center,
91
- #d0c6b7 0px,
92
- #d0c6b7 2px,
93
- transparent 3px
94
- );
95
- background-size: 9px 28px;
96
- background-repeat: repeat-y;
97
- pointer-events: none;
98
- }
99
- /* curled top-right corner */
100
- .container::after {
101
- content: '';
102
- position: absolute;
103
- top: 0;
104
- right: 0;
105
- width: 110px;
106
- height: 110px;
107
- background: linear-gradient(
108
- 135deg,
109
- rgba(0, 0, 0, 0.08) 0%,
110
- rgba(0, 0, 0, 0) 42%
111
- ),
112
- linear-gradient(
113
- 135deg,
114
- #fffefa 0%,
115
- #fffefa 50%,
116
- rgba(255, 255, 255, 0) 51%
117
- );
118
- background-size: 100% 100%;
119
- border-bottom-left-radius: 12px;
120
- transform: translate(1px, -1px);
121
- pointer-events: none;
122
- }
123
 
124
- .header {
125
- text-align: center;
126
- margin-bottom: 34px;
127
- padding-bottom: 18px;
128
- border-bottom: 1px solid #f2ede3;
129
- }
130
- h1 {
131
- font-family: 'Libre Baskerville', serif;
132
- margin: 0;
133
- font-size: 30px;
134
- letter-spacing: 0.5px;
135
- }
136
- .subtitle {
137
- font-family: 'PT Mono', monospace;
138
- font-size: 14px;
139
- letter-spacing: 1px;
140
- color: #666;
141
- margin-top: 6px;
142
- }
143
 
144
- #content {
145
- min-height: 520px;
146
- font-size: 18px;
147
- position: relative;
148
- padding: 10px 0 10px 26px; /* leave room for “margin” lines */
149
- overflow-wrap: break-word;
150
- hyphens: auto;
151
  }
152
 
153
- /* faint horizontal ruled lines (lined paper) */
154
- #content::before {
155
- content: '';
156
- position: absolute;
157
- inset: 0;
158
- background: repeating-linear-gradient(
159
- 0deg,
160
- transparent,
161
- transparent 2.65em,
162
- rgba(201, 190, 170, 0.18) 2.65em,
163
- rgba(201, 190, 170, 0.18) 2.7em
164
- );
165
- pointer-events: none;
166
- z-index: 1;
167
- }
168
- /* bring actual text above the ruled lines */
169
- #content * {
170
- position: relative;
171
- z-index: 2;
172
  }
173
 
174
- /* placeholder */
175
- .placeholder {
176
- color: #888;
177
- font-style: italic;
178
- text-align: center;
179
- padding: 110px 20px;
180
- user-select: none;
 
 
 
 
 
 
 
 
181
  }
182
 
183
- /* markdown tweaks */
184
- blockquote {
185
- border-left: 4px solid #ccbfae;
186
- margin: 20px 0;
187
- padding: 15px 26px;
188
- background: #fbf8f1;
189
- font-style: italic;
 
190
  }
191
- code {
192
- font-family: 'PT Mono', monospace;
193
- background: #f2efe8;
194
- padding: 2px 6px;
195
- border-radius: 3px;
196
- font-size: 0.9em;
 
 
 
 
197
  }
198
- pre {
199
- background: #f4f2ec;
200
- padding: 16px 20px;
201
- border: 1px solid #e6e0d2;
202
- border-radius: 6px;
203
- overflow-x: auto;
204
- font-family: 'PT Mono', monospace;
 
 
205
  }
 
 
 
 
 
 
206
 
207
- /* ordered-list “a) / b)” style */
208
- ol {
209
- counter-reset: item;
210
- padding-left: 0;
211
- list-style: none;
212
  }
213
- ol > li {
214
- counter-increment: item;
215
- margin: 0.5em 0 0.5em 2em;
 
 
 
 
216
  }
217
- ol > li::before {
218
- content: counter(item) ')';
219
- display: inline-block;
220
- width: 1.5em;
221
- margin-left: -2em;
222
- text-align: right;
223
- font-weight: 600;
 
224
  }
225
- ol ol > li::before {
226
- content: counter(item, lower-alpha) ')';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  }
 
228
 
229
- /* processing badge */
230
- .processing {
231
- position: fixed;
232
- top: 20px;
233
- right: 20px;
234
- background: #333;
235
- color: #fff;
236
- padding: 10px 20px;
237
- border-radius: 6px;
238
- font-family: 'PT Mono', monospace;
239
- font-size: 14px;
240
- opacity: 0;
241
- transition: opacity 0.25s;
242
- z-index: 2000;
243
- }
244
- .processing.show {
245
- opacity: 0.9;
246
  }
247
-
248
- /* small screens */
249
- @media (max-width: 768px) {
250
- .container {
251
- margin: 20px 16px;
252
- padding: 28px;
253
- }
254
- #content {
255
- font-size: 16px;
256
- }
257
- h1 {
258
- font-size: 24px;
259
- }
260
- }
261
-
262
- /* print */
263
- @media print {
264
- body {
265
- background: #fff;
266
- }
267
- .container {
268
- box-shadow: none !important;
269
- border: none;
270
- }
271
- .header,
272
- .processing,
273
- .instructions {
274
- display: none;
275
- }
276
  }
277
  </style>
278
  </head>
 
279
  <body>
280
  <div class="container">
 
 
 
281
  <div class="header">
282
  <h1>LaTeX Notepad</h1>
283
  <div class="subtitle">press ctrl+v anywhere to render</div>
@@ -285,8 +217,7 @@ ol ol > li::before {
285
 
286
  <div id="content">
287
  <div class="placeholder">
288
- Press <kbd>Ctrl</kbd>+<kbd>V</kbd> (or <kbd>⌘</kbd>+<kbd>V</kbd>) to paste
289
- and render Markdown/LaTeX
290
  </div>
291
  </div>
292
 
@@ -298,86 +229,82 @@ ol ol > li::before {
298
  <div class="processing">Processing…</div>
299
 
300
  <script>
301
- const content = document.getElementById('content');
302
- const processingIndicator = document.querySelector('.processing');
 
303
 
304
- /* ------------- helper: processing badge ---------------- */
305
- function showProcessing() {
306
- processingIndicator.classList.add('show');
307
- }
308
- function hideProcessing() {
309
- setTimeout(() => processingIndicator.classList.remove('show'), 300);
310
- }
311
 
312
- /* ------------- main “render” pipeline ------------------ */
313
- function processContent(text) {
314
  showProcessing();
315
 
316
- /* protect LaTeX so marked.js doesn’t mangle it */
317
- const store = [];
318
- const PLACE = (i) => `%%LATEX_${i}%%`;
319
- let idx = 0;
320
-
321
- function keep(m) {
322
- store.push(m);
323
- return PLACE(idx++);
324
- }
325
 
326
- /* Display: \[...\], $$...$$ then Inline: \(...\), $...$ */
327
  text = text
328
- .replace(/\\\[[\s\S]*?\\\]/g, keep)
329
- .replace(/\$\$[\s\S]*?\$\$/g, keep)
330
- .replace(/\\\([\s\S]*?\\\)/g, keep)
331
- /* single $…$ but NOT $$…$$ */
332
- .replace(/\$([^\$\n]+?)\$/g, keep);
333
 
334
- /* markdown → HTML */
335
  let html = marked.parse(text);
336
-
337
- /* restore LaTeX */
338
- store.forEach((latex, i) => {
339
- html = html.replaceAll(PLACE(i), latex);
340
- });
341
-
342
  content.innerHTML = html;
343
 
344
- /* trigger MathJax */
345
- if (window.MathJax?.typesetPromise) {
346
- MathJax.typesetPromise([content]).then(hideProcessing).catch(e => {
347
- console.error('MathJax error:', e);
348
- hideProcessing();
349
- });
350
- } else {
351
- hideProcessing();
352
- }
353
  }
354
 
355
- /* ------------- paste listeners ------------------------- */
356
- document.addEventListener('paste', (e) => {
357
  e.preventDefault();
358
- const text = e.clipboardData.getData('text/plain');
359
- if (text.trim()) processContent(text);
360
  });
361
 
362
- /* subtlerubber-bandhint if user clicks before any content */
363
- content.addEventListener('click', () => {
364
- const ph = content.querySelector('.placeholder');
365
- if (ph) {
366
- ph.style.transform = 'scale(0.97)';
367
- ph.style.transition = 'transform 0.12s';
368
- setTimeout(() => (ph.style.transform = 'scale(1)'), 120);
369
  }
370
  });
371
 
372
- /* smooth initial fade-in */
373
- document.addEventListener('DOMContentLoaded', () => {
374
- const sheet = document.querySelector('.container');
375
- sheet.style.opacity = '0';
376
- setTimeout(() => {
377
- sheet.style.transition = 'opacity 0.6s ease';
378
- sheet.style.opacity = '1';
379
- }, 80);
380
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  </script>
382
  </body>
383
  </html>
 
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: {
 
14
  processEscapes: true,
15
  processEnvironments: true,
16
  },
17
+ options: { skipHtmlTags: ['script','noscript','style','textarea','pre'] }
 
 
18
  };
19
  </script>
20
  <script
 
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
31
  href="https://fonts.googleapis.com/css2?family=Crimson+Text:wght@400;600&family=Libre+Baskerville:wght@400;700&family=PT+Mono&display=swap"
32
  rel="stylesheet"
33
  />
34
 
35
  <style>
36
+ /* ─────────────────────────────────────────
37
+ COLOR SYSTEM (light / dark via variables)
38
+ ───────────────────────────────────────── */
39
+ :root {
40
+ --desk-bg: #fbf9f5;
41
+ --desk-dot: #e2dccd;
42
+
43
+ --paper-bg: #fffefa;
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
 
62
+ --paper-bg: #302e2b;
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
+ /* ─────────────────────────────────────────
78
+ GLOBAL “DESK” BACKGROUND
79
+ ───────────────────────────────────────── */
80
+ html,body{height:100%}
81
+ body{
82
+ margin:0;
83
+ background: var(--desk-bg);
84
+ background-image: radial-gradient(var(--desk-dot) 1px,transparent 1px);
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);
102
+ border-radius:12px 12px 10px 10px;
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 */
110
+ .container::before{
111
+ content:'';
112
+ position:absolute;top:26px;bottom:26px;left:30px;width:9px;
113
+ background-image:radial-gradient(circle var(--perforation) 0%,var(--perforation) 2px,transparent 3px);
114
+ background-size:9px 28px;
115
+ background-repeat:repeat-y;
116
+ pointer-events:none;
117
  }
118
+ /* curled corner */
119
+ .container::after{
120
+ content:'';position:absolute;top:0;right:0;width:110px;height:110px;
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
+ #themeToggle:hover{transform:rotate(20deg)scale(1.15)}
140
+
141
+ /* ───────── header ───────── */
142
+ .header{text-align:center;margin-bottom:34px;padding-bottom:18px;border-bottom:1px solid rgba(0,0,0,.05)}
143
+ h1{font-family:'Libre Baskerville',serif;margin:0;font-size:30px;letter-spacing:.5px}
144
+ .subtitle{font-family:'PT Mono',monospace;font-size:14px;color:#666;margin-top:6px;letter-spacing:1px}
145
 
146
+ /* ───────── content area ───────── */
147
+ #content{
148
+ min-height:520px;font-size:18px;position:relative;
149
+ padding:10px 0 10px 26px;overflow-wrap:break-word;hyphens:auto;
 
150
  }
151
+ #content::before{
152
+ content:'';position:absolute;inset:0;
153
+ background:repeating-linear-gradient(
154
+ 0deg,
155
+ transparent,transparent 2.65em,
156
+ var(--line) 2.65em,var(--line) 2.7em);
157
+ pointer-events:none;z-index:1;
158
  }
159
+ #content *{position:relative;z-index:2}
160
+ .placeholder{color:#888;font-style:italic;text-align:center;padding:110px 20px;user-select:none}
161
+
162
+ /* ───────── markdown tweaks ───────── */
163
+ blockquote{
164
+ border-left:4px solid var(--blockquote-bar);
165
+ margin:20px 0;padding:15px 26px;
166
+ background:var(--blockquote-bg);font-style:italic
167
  }
168
+ code{font-family:'PT Mono',monospace;background:var(--inline-code-bg);
169
+ padding:2px 6px;border-radius:3px;font-size:.9em}
170
+ pre{background:var(--code-bg);padding:16px 20px;border:1px solid var(--code-border);
171
+ border-radius:6px;overflow-x:auto;font-family:'PT Mono',monospace}
172
+
173
+ /* lists */
174
+ ol{counter-reset:item;padding-left:0;list-style:none}
175
+ ol>li{counter-increment:item;margin:.5em 0 .5em 2em}
176
+ ol>li::before{content:counter(item)')';display:inline-block;width:1.5em;margin-left:-2em;text-align:right;font-weight:600}
177
+ ol ol>li::before{content:counter(item,lower-alpha)')'}
178
+
179
+ /* ───────── TABLES ───────── */
180
+ table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums;margin:1.2em 0}
181
+ thead tr{border-bottom:1px solid var(--tbl-border)}
182
+ tbody tr:not(:last-child){border-bottom:1px solid var(--tbl-border)}
183
+ th,td{padding:.55em .8em;text-align:right}
184
+ th{font-weight:600}
185
+
186
+ /* ───────── processing badge ───────── */
187
+ .processing{
188
+ position:fixed;top:20px;right:20px;background:#333;color:#fff;
189
+ padding:10px 20px;border-radius:6px;font-family:'PT Mono',monospace;
190
+ font-size:14px;opacity:0;transition:opacity .25s;z-index:2000
191
  }
192
+ .processing.show{opacity:.9}
193
 
194
+ /* responsive & print */
195
+ @media(max-width:768px){
196
+ .container{margin:20px 16px;padding:28px}
197
+ #content{font-size:16px}
198
+ h1{font-size:24px}
 
 
 
 
 
 
 
 
 
 
 
 
199
  }
200
+ @media print{
201
+ body{background:#fff}
202
+ .container{box-shadow:none;border:none}
203
+ .header,.processing,.instructions,#themeToggle{display:none}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  }
205
  </style>
206
  </head>
207
+
208
  <body>
209
  <div class="container">
210
+ <!-- theme icon -->
211
+ <button id="themeToggle" title="Toggle dark / light">🌙</button>
212
+
213
  <div class="header">
214
  <h1>LaTeX Notepad</h1>
215
  <div class="subtitle">press ctrl+v anywhere to render</div>
 
217
 
218
  <div id="content">
219
  <div class="placeholder">
220
+ Press <kbd>Ctrl</kbd>+<kbd>V</kbd> (or <kbd>⌘</kbd>+<kbd>V</kbd>) to paste and render Markdown / LaTeX
 
221
  </div>
222
  </div>
223
 
 
229
  <div class="processing">Processing…</div>
230
 
231
  <script>
232
+ /* ======= processing badge helpers ======= */
233
+ const content = document.getElementById('content');
234
+ const processingNode = document.querySelector('.processing');
235
 
236
+ function showProcessing(){processingNode.classList.add('show')}
237
+ function hideProcessing(){setTimeout(()=>processingNode.classList.remove('show'),300)}
 
 
 
 
 
238
 
239
+ /* ======= markdown + latex pipeline ======= */
240
+ function processContent(text){
241
  showProcessing();
242
 
243
+ const store=[], PL=i=>`%%LATEX_${i}%%`; let idx=0;
244
+ const keep=m=>(store.push(m),PL(idx++));
 
 
 
 
 
 
 
245
 
 
246
  text = text
247
+ .replace(/\\\[[\s\S]*?\\\]/g, keep) // \[ ... \]
248
+ .replace(/\$\$[\s\S]*?\$\$/g, keep) // $$ ... $$
249
+ .replace(/\\\([\s\S]*?\\\)/g, keep) // \( ... \)
250
+ .replace(/\$([^\$\n]+?)\$/g, keep); // $ ... $
 
251
 
 
252
  let html = marked.parse(text);
253
+ store.forEach((latex,i)=>{html=html.replaceAll(PL(i),latex)});
 
 
 
 
 
254
  content.innerHTML = html;
255
 
256
+ if(window.MathJax?.typesetPromise){
257
+ MathJax.typesetPromise([content]).then(hideProcessing)
258
+ .catch(e=>{console.error('MathJax error:',e);hideProcessing()});
259
+ }else{hideProcessing()}
 
 
 
 
 
260
  }
261
 
262
+ /* ======= paste listener ======= */
263
+ document.addEventListener('paste',e=>{
264
  e.preventDefault();
265
+ const txt=e.clipboardData.getData('text/plain');
266
+ if(txt.trim())processContent(txt);
267
  });
268
 
269
+ /* smallbounceon placeholder click */
270
+ content.addEventListener('click',()=>{
271
+ const ph=content.querySelector('.placeholder');
272
+ if(ph){
273
+ ph.style.transform='scale(.97)';
274
+ ph.style.transition='transform .12s';
275
+ setTimeout(()=>ph.style.transform='scale(1)',120);
276
  }
277
  });
278
 
279
+ /* smooth fade in */
280
+ document.addEventListener('DOMContentLoaded',()=>{
281
+ const sheet=document.querySelector('.container');
282
+ sheet.style.opacity='0';
283
+ setTimeout(()=>{sheet.style.transition='opacity .6s ease';sheet.style.opacity='1'},80);
 
 
 
284
  });
285
+
286
+ /* ======= theme toggler ======= */
287
+ const btn = document.getElementById('themeToggle');
288
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
289
+ const savedTheme = localStorage.getItem('note-theme');
290
+
291
+ initTheme();
292
+ btn.addEventListener('click',()=>{
293
+ document.body.classList.toggle('dark');
294
+ updateIcon();
295
+ localStorage.setItem('note-theme',document.body.classList.contains('dark')?'dark':'light');
296
+ });
297
+ function initTheme(){
298
+ if(savedTheme){
299
+ document.body.classList.toggle('dark',savedTheme==='dark');
300
+ }else if(prefersDark.matches){
301
+ document.body.classList.add('dark');
302
+ }
303
+ updateIcon();
304
+ }
305
+ function updateIcon(){
306
+ btn.textContent=document.body.classList.contains('dark')?'☀️':'🌙';
307
+ }
308
  </script>
309
  </body>
310
  </html>