jebin2 commited on
Commit
207153e
·
1 Parent(s): c022bd8
Files changed (1) hide show
  1. index.html +114 -155
index.html CHANGED
@@ -1,6 +1,7 @@
1
  <!DOCTYPE html>
2
 
3
  <html lang="en">
 
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -27,16 +28,18 @@
27
  --paper-texture: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23f0ead6' fill-opacity='0.3'%3E%3Cpath d='M30 30c0-6.627-5.373-12-12-12s-12 5.373-12 12 5.373 12 12 12 12-5.373 12-12zm12 0c0-6.627-5.373-12-12-12s-12 5.373-12 12 5.373 12 12 12 12-5.373 12-12z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
28
  }
29
 
30
- * {
31
- margin: 0;
32
- padding: 0;
33
- box-sizing: border-box;
 
 
 
 
 
34
  }
35
- html, body {
36
- height: 100%;
37
- }
38
 
39
- body {
40
  font-family: 'Georgia', 'Times New Roman', serif;
41
  background: linear-gradient(135deg, #f9f6ef 0%, #f1ebe0 100%);
42
  background-attachment: fixed;
@@ -59,35 +62,24 @@
59
  }
60
 
61
  /* Login Screen */
62
- .login-screen {
63
- display: flex;
64
- justify-content: center;
65
- align-items: center;
66
- height: 100%;
67
  padding: 20px;
68
  position: relative;
69
  z-index: 2;
70
- background: var(--bg-secondary);
71
- }
72
-
73
- .login-screen::before {
74
- content: '';
75
- position: absolute;
76
- top: 0;
77
- left: 0;
78
- right: 0;
79
- bottom: 0;
80
-
81
- pointer-events: none;
82
  }
83
 
84
- .login-box {
85
- text-align: center;
86
- padding: 40px 35px;
87
- border-radius: var(--radius);
88
  background: var(--bg-primary);
89
  border: 2px solid var(--border);
90
- box-shadow:
91
  0 8px 32px var(--paper-shadow),
92
  0 2px 8px var(--paper-shadow),
93
  inset 0 1px 0 rgba(255, 255, 255, 0.8);
@@ -95,6 +87,8 @@
95
  z-index: 1;
96
  width: 100%;
97
  max-width: 380px;
 
 
98
  }
99
 
100
  .login-box::before {
@@ -109,11 +103,11 @@
109
  pointer-events: none;
110
  }
111
 
112
- .login-box h1 {
113
- margin-bottom: 8px;
114
- color: var(--text-primary);
115
- font-weight: 400;
116
- font-size: 36px;
117
  letter-spacing: -0.5px;
118
  font-family: 'Georgia', serif;
119
  position: relative;
@@ -133,25 +127,25 @@
133
  margin-bottom: 28px;
134
  }
135
 
136
- .password-input {
137
  width: 100%;
138
- padding: 18px 22px;
139
- font-size: 16px;
140
- border: 2px solid var(--border);
141
- border-radius: var(--radius);
142
  background: var(--bg-secondary);
143
  color: var(--text-primary);
144
- text-align: center;
145
- letter-spacing: 1px;
146
- outline: none;
147
  transition: var(--transition);
148
  font-family: 'Courier New', monospace;
149
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.03);
150
  }
151
 
152
- .password-input:focus {
153
- border-color: var(--accent);
154
- box-shadow:
155
  inset 0 2px 4px rgba(0, 0, 0, 0.03),
156
  0 0 0 3px rgba(212, 175, 55, 0.15);
157
  background: var(--bg-primary);
@@ -164,30 +158,30 @@
164
  font-style: italic;
165
  }
166
 
167
- .enter-btn {
168
  width: 100%;
169
- padding: 18px 32px;
170
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
171
- color: var(--bg-primary);
172
- border: none;
173
- border-radius: var(--radius);
174
- font-size: 16px;
175
  font-weight: 500;
176
- cursor: pointer;
177
  transition: var(--transition);
178
  position: relative;
179
  overflow: hidden;
180
  text-transform: uppercase;
181
  letter-spacing: 0.5px;
182
  font-family: 'Georgia', serif;
183
- box-shadow:
184
  0 4px 15px rgba(212, 175, 55, 0.3),
185
  inset 0 1px 0 rgba(255, 255, 255, 0.2);
186
  }
187
 
188
- .enter-btn:hover:not(:disabled) {
189
  transform: translateY(-2px);
190
- box-shadow:
191
  0 6px 20px rgba(212, 175, 55, 0.4),
192
  inset 0 1px 0 rgba(255, 255, 255, 0.2);
193
  }
@@ -202,17 +196,17 @@
202
  transform: none;
203
  }
204
 
205
- .error {
206
- color: var(--error);
207
- margin-top: 16px;
208
- font-size: 14px;
209
  min-height: 20px;
210
  font-weight: 400;
211
  font-style: italic;
212
  }
213
 
214
  /* Editor Screen */
215
- .editor-screen {
216
  display: none;
217
  height: 100%;
218
  background: var(--bg-primary);
@@ -221,8 +215,8 @@
221
  z-index: 2;
222
  }
223
 
224
- .header {
225
- padding: 15px;
226
  background: var(--bg-secondary);
227
  border-bottom: 3px solid var(--border);
228
  display: flex;
@@ -265,11 +259,11 @@
265
  flex-wrap: wrap;
266
  }
267
 
268
- .save-status {
269
- font-size: 13px;
270
  font-weight: 500;
271
- padding: 8px 14px;
272
- border-radius: 20px;
273
  background: var(--bg-tertiary);
274
  color: var(--text-secondary);
275
  border: 1px solid var(--border);
@@ -278,13 +272,13 @@
278
  font-family: 'Georgia', serif;
279
  }
280
 
281
- .save-status.saving {
282
  background: linear-gradient(135deg, var(--warning) 0%, #d4850f 100%);
283
  color: white;
284
  border-color: var(--warning);
285
  }
286
-
287
- .save-status.saved {
288
  background: linear-gradient(135deg, var(--success) 0%, #5a8a69 100%);
289
  color: white;
290
  border-color: var(--success);
@@ -305,38 +299,22 @@
305
  border-radius: var(--radius);
306
  background: var(--bg-primary);
307
  border: 2px solid var(--border);
308
- box-shadow:
309
  inset 0 2px 8px rgba(0, 0, 0, 0.03),
310
  0 4px 20px var(--paper-shadow);
 
311
  }
312
 
313
- .editor-container::before {
314
- content: '';
315
- position: absolute;
316
- top: 0;
317
- left: 0;
318
- right: 0;
319
- bottom: 0;
320
- background:
321
- repeating-linear-gradient(
322
- transparent,
323
- transparent 29px
324
- );
325
- pointer-events: none;
326
- z-index: 1;
327
- border-radius: var(--radius);
328
- }
329
-
330
- .editor {
331
- width: 100%;
332
- height:100%;
333
- padding: 25px 30px;
334
- border: none;
335
- outline: none;
336
- font-family: 'Georgia', 'Times New Roman', serif;
337
- font-size: 16px;
338
- line-height: 1.8;
339
- resize: none;
340
  background: transparent;
341
  color: var(--text-primary);
342
  position: relative;
@@ -376,60 +354,41 @@
376
  }
377
 
378
  @keyframes fadeIn {
379
- from {
380
- opacity: 0;
381
  transform: translateY(20px) scale(0.98);
382
  }
383
- to {
384
- opacity: 1;
 
385
  transform: translateY(0) scale(1);
386
  }
387
  }
388
 
389
- /* Mobile optimizations without @media */
390
- .login-box {
391
- min-height: auto;
392
- }
393
-
394
- .header-right {
395
- justify-content: flex-end;
396
- }
397
-
398
- .word-count {
399
- white-space: nowrap;
400
- }
401
-
402
  /* Focus improvements */
403
  .password-input:focus,
404
  .editor:focus {
405
  outline: none;
406
  }
407
 
408
- /* Subtle animations */
409
- .login-box {
410
- animation: slideUp 0.6s ease-out;
411
- }
412
-
413
  @keyframes slideUp {
414
  from {
415
  opacity: 0;
416
  transform: translateY(30px);
417
  }
 
418
  to {
419
  opacity: 1;
420
  transform: translateY(0);
421
  }
422
  }
423
 
424
- .editor-container {
425
- animation: paperUnfold 0.5s ease-out;
426
- }
427
-
428
  @keyframes paperUnfold {
429
  from {
430
  opacity: 0;
431
  transform: scale(0.95) rotateX(5deg);
432
  }
 
433
  to {
434
  opacity: 1;
435
  transform: scale(1) rotateX(0deg);
@@ -452,6 +411,7 @@
452
  }
453
  </style>
454
  </head>
 
455
  <body>
456
  <div id="loginScreen" class="login-screen">
457
  <div class="login-box">
@@ -459,9 +419,10 @@
459
  <p class="login-subtitle">Perfect for temporary notes and secure sharing. Deleted after two days.</p>
460
 
461
  <div class="input-group">
462
- <input type="password" id="passwordInput" class="password-input" placeholder="min-8-char password" minlength="8" maxlength="100">
 
463
  </div>
464
-
465
  <button onclick="login()" class="enter-btn">Enter</button>
466
  <div id="loginError" class="error"></div>
467
  </div>
@@ -535,17 +496,17 @@
535
  const encoder = new TextEncoder();
536
  const data = encoder.encode(text);
537
  const iv = crypto.getRandomValues(new Uint8Array(12));
538
-
539
  const encryptedContent = await crypto.subtle.encrypt(
540
  { name: 'AES-GCM', iv: iv },
541
  key,
542
  data
543
  );
544
-
545
  const combined = new Uint8Array(iv.length + encryptedContent.byteLength);
546
  combined.set(iv);
547
  combined.set(new Uint8Array(encryptedContent), iv.length);
548
-
549
  return btoa(String.fromCharCode.apply(null, combined));
550
  }
551
 
@@ -560,14 +521,14 @@
560
  key,
561
  encryptedContent
562
  );
563
-
564
  return new TextDecoder().decode(decrypted);
565
  } catch (error) {
566
  console.error('Decryption failed:', error);
567
  throw new Error('Decryption failed. Check password.');
568
  }
569
  }
570
-
571
  function base64ToUint8Array(base64) {
572
  const binaryString = atob(base64);
573
  const len = binaryString.length;
@@ -590,7 +551,7 @@
590
  if (isIOS) {
591
  const vh = window.innerHeight * 0.01;
592
  document.documentElement.style.setProperty('--vh', `${vh}px`);
593
-
594
  window.addEventListener('resize', () => {
595
  const vh = window.innerHeight * 0.01;
596
  document.documentElement.style.setProperty('--vh', `${vh}px`);
@@ -604,51 +565,48 @@
604
  async function login() {
605
  if (isWorking) return;
606
  isWorking = true;
607
-
608
  const password = document.getElementById('passwordInput').value;
609
  const errorDiv = document.getElementById('loginError');
610
  const enterBtn = document.querySelector('.enter-btn');
611
-
612
  errorDiv.textContent = '';
613
  if (password.length < 8) {
614
  errorDiv.textContent = 'Password must be at least 8 characters';
615
  isWorking = false;
616
  return;
617
  }
618
-
619
  enterBtn.textContent = 'Loading...';
620
  enterBtn.disabled = true;
621
 
622
  currentPassword = password;
623
  fileHash = await generateFilenameHash(password);
624
-
625
  try {
626
  const response = await fetch('/api/load', {
627
  method: 'POST',
628
  headers: { 'Content-Type': 'application/json' },
629
  body: JSON.stringify({ hash: fileHash })
630
  });
631
-
632
  const data = await response.json();
633
-
634
  if (!response.ok) {
635
  throw new Error(data.error || 'Login failed');
636
  }
637
-
638
  currentSalt = base64ToUint8Array(data.salt);
639
-
640
  let content = '';
641
  if (data.content) {
642
  const key = await deriveKey(currentPassword, currentSalt);
643
  content = await decrypt(data.content, key);
644
  }
645
-
646
  document.getElementById('editor').value = content;
647
  document.getElementById('loginScreen').style.display = 'none';
648
  document.getElementById('editorScreen').style.display = 'flex';
649
- //let editor = document.getElementById('editor')
650
- //editor.focus();
651
- //editor.setSelectionRange(0, 0);
652
  setupAutoSave();
653
  updateSaveStatus('Ready');
654
  updateWordCount();
@@ -664,12 +622,12 @@
664
 
665
  function setupAutoSave() {
666
  const editor = document.getElementById('editor');
667
-
668
  editor.addEventListener('input', () => {
669
  clearTimeout(saveTimeout);
670
  updateSaveStatus('Typing...');
671
  updateWordCount();
672
-
673
  saveTimeout = setTimeout(saveContent, 1500);
674
  });
675
  }
@@ -677,24 +635,24 @@
677
  async function saveContent() {
678
  if (isWorking) return;
679
  isWorking = true;
680
-
681
  updateSaveStatus('Saving...');
682
-
683
  const content = document.getElementById('editor').value;
684
-
685
  try {
686
  const key = await deriveKey(currentPassword, currentSalt);
687
  const encryptedContent = await encrypt(content, key);
688
-
689
  const response = await fetch('/api/save', {
690
  method: 'POST',
691
  headers: { 'Content-Type': 'application/json' },
692
- body: JSON.stringify({
693
  hash: fileHash,
694
- content: encryptedContent
695
  })
696
  });
697
-
698
  if (response.ok) {
699
  updateSaveStatus('Saved');
700
  } else {
@@ -712,7 +670,7 @@
712
  function updateSaveStatus(status) {
713
  const statusDiv = document.getElementById('saveStatus');
714
  statusDiv.textContent = status;
715
-
716
  statusDiv.className = 'save-status';
717
  if (status.includes('Saving') || status.includes('Typing')) {
718
  statusDiv.className += ' saving';
@@ -722,7 +680,7 @@
722
  }
723
 
724
  // Prevent accidental page close
725
- window.addEventListener('beforeunload', function(e) {
726
  const statusDiv = document.getElementById('saveStatus');
727
  if (statusDiv.className.includes(" saving")) {
728
  e.preventDefault();
@@ -731,4 +689,5 @@
731
  });
732
  </script>
733
  </body>
 
734
  </html>
 
1
  <!DOCTYPE html>
2
 
3
  <html lang="en">
4
+
5
  <head>
6
  <meta charset="UTF-8">
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
28
  --paper-texture: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23f0ead6' fill-opacity='0.3'%3E%3Cpath d='M30 30c0-6.627-5.373-12-12-12s-12 5.373-12 12 5.373 12 12 12 12-5.373 12-12zm12 0c0-6.627-5.373-12-12-12s-12 5.373-12 12 5.373 12 12 12 12-5.373 12-12z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
29
  }
30
 
31
+ * {
32
+ margin: 0;
33
+ padding: 0;
34
+ box-sizing: border-box;
35
+ }
36
+
37
+ html,
38
+ body {
39
+ height: 100%;
40
  }
 
 
 
41
 
42
+ body {
43
  font-family: 'Georgia', 'Times New Roman', serif;
44
  background: linear-gradient(135deg, #f9f6ef 0%, #f1ebe0 100%);
45
  background-attachment: fixed;
 
62
  }
63
 
64
  /* Login Screen */
65
+ .login-screen {
66
+ display: flex;
67
+ justify-content: center;
68
+ align-items: center;
69
+ height: 100%;
70
  padding: 20px;
71
  position: relative;
72
  z-index: 2;
73
+ background: var(--bg-secondary);
 
 
 
 
 
 
 
 
 
 
 
74
  }
75
 
76
+ .login-box {
77
+ text-align: center;
78
+ padding: 40px 35px;
79
+ border-radius: var(--radius);
80
  background: var(--bg-primary);
81
  border: 2px solid var(--border);
82
+ box-shadow:
83
  0 8px 32px var(--paper-shadow),
84
  0 2px 8px var(--paper-shadow),
85
  inset 0 1px 0 rgba(255, 255, 255, 0.8);
 
87
  z-index: 1;
88
  width: 100%;
89
  max-width: 380px;
90
+ min-height: auto;
91
+ animation: slideUp 0.6s ease-out;
92
  }
93
 
94
  .login-box::before {
 
103
  pointer-events: none;
104
  }
105
 
106
+ .login-box h1 {
107
+ margin-bottom: 8px;
108
+ color: var(--text-primary);
109
+ font-weight: 400;
110
+ font-size: 36px;
111
  letter-spacing: -0.5px;
112
  font-family: 'Georgia', serif;
113
  position: relative;
 
127
  margin-bottom: 28px;
128
  }
129
 
130
+ .password-input {
131
  width: 100%;
132
+ padding: 18px 22px;
133
+ font-size: 16px;
134
+ border: 2px solid var(--border);
135
+ border-radius: var(--radius);
136
  background: var(--bg-secondary);
137
  color: var(--text-primary);
138
+ text-align: center;
139
+ letter-spacing: 1px;
140
+ outline: none;
141
  transition: var(--transition);
142
  font-family: 'Courier New', monospace;
143
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.03);
144
  }
145
 
146
+ .password-input:focus {
147
+ border-color: var(--accent);
148
+ box-shadow:
149
  inset 0 2px 4px rgba(0, 0, 0, 0.03),
150
  0 0 0 3px rgba(212, 175, 55, 0.15);
151
  background: var(--bg-primary);
 
158
  font-style: italic;
159
  }
160
 
161
+ .enter-btn {
162
  width: 100%;
163
+ padding: 18px 32px;
164
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
165
+ color: var(--bg-primary);
166
+ border: none;
167
+ border-radius: var(--radius);
168
+ font-size: 16px;
169
  font-weight: 500;
170
+ cursor: pointer;
171
  transition: var(--transition);
172
  position: relative;
173
  overflow: hidden;
174
  text-transform: uppercase;
175
  letter-spacing: 0.5px;
176
  font-family: 'Georgia', serif;
177
+ box-shadow:
178
  0 4px 15px rgba(212, 175, 55, 0.3),
179
  inset 0 1px 0 rgba(255, 255, 255, 0.2);
180
  }
181
 
182
+ .enter-btn:hover:not(:disabled) {
183
  transform: translateY(-2px);
184
+ box-shadow:
185
  0 6px 20px rgba(212, 175, 55, 0.4),
186
  inset 0 1px 0 rgba(255, 255, 255, 0.2);
187
  }
 
196
  transform: none;
197
  }
198
 
199
+ .error {
200
+ color: var(--error);
201
+ margin-top: 16px;
202
+ font-size: 14px;
203
  min-height: 20px;
204
  font-weight: 400;
205
  font-style: italic;
206
  }
207
 
208
  /* Editor Screen */
209
+ .editor-screen {
210
  display: none;
211
  height: 100%;
212
  background: var(--bg-primary);
 
215
  z-index: 2;
216
  }
217
 
218
+ .header {
219
+ padding: 15px;
220
  background: var(--bg-secondary);
221
  border-bottom: 3px solid var(--border);
222
  display: flex;
 
259
  flex-wrap: wrap;
260
  }
261
 
262
+ .save-status {
263
+ font-size: 13px;
264
  font-weight: 500;
265
+ padding: 8px 14px;
266
+ border-radius: 20px;
267
  background: var(--bg-tertiary);
268
  color: var(--text-secondary);
269
  border: 1px solid var(--border);
 
272
  font-family: 'Georgia', serif;
273
  }
274
 
275
+ .save-status.saving {
276
  background: linear-gradient(135deg, var(--warning) 0%, #d4850f 100%);
277
  color: white;
278
  border-color: var(--warning);
279
  }
280
+
281
+ .save-status.saved {
282
  background: linear-gradient(135deg, var(--success) 0%, #5a8a69 100%);
283
  color: white;
284
  border-color: var(--success);
 
299
  border-radius: var(--radius);
300
  background: var(--bg-primary);
301
  border: 2px solid var(--border);
302
+ box-shadow:
303
  inset 0 2px 8px rgba(0, 0, 0, 0.03),
304
  0 4px 20px var(--paper-shadow);
305
+ animation: paperUnfold 0.5s ease-out;
306
  }
307
 
308
+ .editor {
309
+ width: 100%;
310
+ height: 100%;
311
+ padding: 25px 30px;
312
+ border: none;
313
+ outline: none;
314
+ font-family: 'Georgia', 'Times New Roman', serif;
315
+ font-size: 16px;
316
+ line-height: 1.8;
317
+ resize: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  background: transparent;
319
  color: var(--text-primary);
320
  position: relative;
 
354
  }
355
 
356
  @keyframes fadeIn {
357
+ from {
358
+ opacity: 0;
359
  transform: translateY(20px) scale(0.98);
360
  }
361
+
362
+ to {
363
+ opacity: 1;
364
  transform: translateY(0) scale(1);
365
  }
366
  }
367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  /* Focus improvements */
369
  .password-input:focus,
370
  .editor:focus {
371
  outline: none;
372
  }
373
 
 
 
 
 
 
374
  @keyframes slideUp {
375
  from {
376
  opacity: 0;
377
  transform: translateY(30px);
378
  }
379
+
380
  to {
381
  opacity: 1;
382
  transform: translateY(0);
383
  }
384
  }
385
 
 
 
 
 
386
  @keyframes paperUnfold {
387
  from {
388
  opacity: 0;
389
  transform: scale(0.95) rotateX(5deg);
390
  }
391
+
392
  to {
393
  opacity: 1;
394
  transform: scale(1) rotateX(0deg);
 
411
  }
412
  </style>
413
  </head>
414
+
415
  <body>
416
  <div id="loginScreen" class="login-screen">
417
  <div class="login-box">
 
419
  <p class="login-subtitle">Perfect for temporary notes and secure sharing. Deleted after two days.</p>
420
 
421
  <div class="input-group">
422
+ <input type="password" id="passwordInput" class="password-input" placeholder="min-8-char password"
423
+ minlength="8" maxlength="100">
424
  </div>
425
+
426
  <button onclick="login()" class="enter-btn">Enter</button>
427
  <div id="loginError" class="error"></div>
428
  </div>
 
496
  const encoder = new TextEncoder();
497
  const data = encoder.encode(text);
498
  const iv = crypto.getRandomValues(new Uint8Array(12));
499
+
500
  const encryptedContent = await crypto.subtle.encrypt(
501
  { name: 'AES-GCM', iv: iv },
502
  key,
503
  data
504
  );
505
+
506
  const combined = new Uint8Array(iv.length + encryptedContent.byteLength);
507
  combined.set(iv);
508
  combined.set(new Uint8Array(encryptedContent), iv.length);
509
+
510
  return btoa(String.fromCharCode.apply(null, combined));
511
  }
512
 
 
521
  key,
522
  encryptedContent
523
  );
524
+
525
  return new TextDecoder().decode(decrypted);
526
  } catch (error) {
527
  console.error('Decryption failed:', error);
528
  throw new Error('Decryption failed. Check password.');
529
  }
530
  }
531
+
532
  function base64ToUint8Array(base64) {
533
  const binaryString = atob(base64);
534
  const len = binaryString.length;
 
551
  if (isIOS) {
552
  const vh = window.innerHeight * 0.01;
553
  document.documentElement.style.setProperty('--vh', `${vh}px`);
554
+
555
  window.addEventListener('resize', () => {
556
  const vh = window.innerHeight * 0.01;
557
  document.documentElement.style.setProperty('--vh', `${vh}px`);
 
565
  async function login() {
566
  if (isWorking) return;
567
  isWorking = true;
568
+
569
  const password = document.getElementById('passwordInput').value;
570
  const errorDiv = document.getElementById('loginError');
571
  const enterBtn = document.querySelector('.enter-btn');
572
+
573
  errorDiv.textContent = '';
574
  if (password.length < 8) {
575
  errorDiv.textContent = 'Password must be at least 8 characters';
576
  isWorking = false;
577
  return;
578
  }
579
+
580
  enterBtn.textContent = 'Loading...';
581
  enterBtn.disabled = true;
582
 
583
  currentPassword = password;
584
  fileHash = await generateFilenameHash(password);
585
+
586
  try {
587
  const response = await fetch('/api/load', {
588
  method: 'POST',
589
  headers: { 'Content-Type': 'application/json' },
590
  body: JSON.stringify({ hash: fileHash })
591
  });
592
+
593
  const data = await response.json();
594
+
595
  if (!response.ok) {
596
  throw new Error(data.error || 'Login failed');
597
  }
598
+
599
  currentSalt = base64ToUint8Array(data.salt);
600
+
601
  let content = '';
602
  if (data.content) {
603
  const key = await deriveKey(currentPassword, currentSalt);
604
  content = await decrypt(data.content, key);
605
  }
606
+
607
  document.getElementById('editor').value = content;
608
  document.getElementById('loginScreen').style.display = 'none';
609
  document.getElementById('editorScreen').style.display = 'flex';
 
 
 
610
  setupAutoSave();
611
  updateSaveStatus('Ready');
612
  updateWordCount();
 
622
 
623
  function setupAutoSave() {
624
  const editor = document.getElementById('editor');
625
+
626
  editor.addEventListener('input', () => {
627
  clearTimeout(saveTimeout);
628
  updateSaveStatus('Typing...');
629
  updateWordCount();
630
+
631
  saveTimeout = setTimeout(saveContent, 1500);
632
  });
633
  }
 
635
  async function saveContent() {
636
  if (isWorking) return;
637
  isWorking = true;
638
+
639
  updateSaveStatus('Saving...');
640
+
641
  const content = document.getElementById('editor').value;
642
+
643
  try {
644
  const key = await deriveKey(currentPassword, currentSalt);
645
  const encryptedContent = await encrypt(content, key);
646
+
647
  const response = await fetch('/api/save', {
648
  method: 'POST',
649
  headers: { 'Content-Type': 'application/json' },
650
+ body: JSON.stringify({
651
  hash: fileHash,
652
+ content: encryptedContent
653
  })
654
  });
655
+
656
  if (response.ok) {
657
  updateSaveStatus('Saved');
658
  } else {
 
670
  function updateSaveStatus(status) {
671
  const statusDiv = document.getElementById('saveStatus');
672
  statusDiv.textContent = status;
673
+
674
  statusDiv.className = 'save-status';
675
  if (status.includes('Saving') || status.includes('Typing')) {
676
  statusDiv.className += ' saving';
 
680
  }
681
 
682
  // Prevent accidental page close
683
+ window.addEventListener('beforeunload', function (e) {
684
  const statusDiv = document.getElementById('saveStatus');
685
  if (statusDiv.className.includes(" saving")) {
686
  e.preventDefault();
 
689
  });
690
  </script>
691
  </body>
692
+
693
  </html>