-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhtmlTypist.html
More file actions
803 lines (745 loc) · 63.9 KB
/
Copy pathhtmlTypist.html
File metadata and controls
803 lines (745 loc) · 63.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>htmlTypist</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{display:flex;flex-direction:column;height:100vh;min-height:560px;background:#fafafa;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}
#toolbar{display:flex;align-items:center;gap:10px;padding:10px 16px;background:#fff;border-bottom:1px solid #efefef;flex-shrink:0}
#logo{font-size:15px;font-weight:600;letter-spacing:-.3px;margin-right:4px;background:linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
#toolbar-right{display:flex;align-items:center;gap:6px;margin-left:auto}
.tb-btn{display:flex;align-items:center;gap:5px;font-size:12px;padding:6px 12px;border-radius:20px;cursor:pointer;border:1px solid #dbdbdb;background:#fff;color:#262626;font-weight:500;transition:all .15s;white-space:nowrap}
.tb-btn:hover{background:#f5f5f5}
.tb-btn:disabled{opacity:.4;cursor:not-allowed}
#save-btn{border:none;background:linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888);color:#fff;font-size:12px;padding:6px 14px;border-radius:20px;cursor:pointer;font-weight:500;display:flex;align-items:center;gap:5px;transition:opacity .15s,background .3s;white-space:nowrap}
#save-btn:hover{opacity:.86}
#save-btn:disabled{opacity:.35;cursor:not-allowed}
#save-btn.flash-green{background:#22c55e!important;transition:background .15s}
#dot{width:7px;height:7px;border-radius:50%;background:linear-gradient(45deg,#f09433,#bc1888);display:none;flex-shrink:0}
#fname{font-size:12px;font-weight:500;color:#262626;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:none}
#status{font-size:12px;color:#8e8e8e;min-width:0}
/* ── Version A: 渐变文字 lang 按钮 ── */
#lang-wrap{position:relative}
#lang-btn{display:flex;align-items:center;gap:5px;font-size:12px;padding:6px 12px;border-radius:20px;cursor:pointer;border:1px solid #dbdbdb;background:#fff;font-weight:600;transition:all .15s;white-space:nowrap}
#lang-btn:hover{background:#f5f5f5}
#lang-btn-label{background:linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-size:12px;font-weight:700;letter-spacing:.04em}
#lang-menu{position:absolute;top:calc(100% + 6px);right:0;background:#fff;border:1px solid #efefef;border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,.1);overflow:hidden;z-index:100;display:none;min-width:110px}
#lang-menu.open{display:block}
.lang-opt{display:flex;align-items:center;gap:8px;padding:8px 14px;font-size:12px;cursor:pointer;color:#262626;transition:background .12s;white-space:nowrap}
.lang-opt:hover{background:#fafafa}
.lang-opt.active{font-weight:600;background:linear-gradient(90deg,rgba(240,148,51,.08),rgba(188,24,136,.08))}
.lang-opt-label{font-size:12px}
.lang-opt.active .lang-opt-label{background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
#main{display:flex;flex:1;overflow:hidden}
#preview-pane{flex:1;overflow:auto;background:#fafafa;position:relative;min-width:0}
#drop-overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px}
#overlay-svg-wrap{width:180px;height:180px;margin-bottom:4px}
#overlay-svg{width:100%;height:100%}
#overlay-title{font-size:36px;color:#1a1a1a;font-weight:600;letter-spacing:-.5px;margin:0}
#overlay-sub{font-size:18px;color:#8e8e8e;margin:0;font-weight:400}
.drop-btns{display:flex;gap:10px;margin-top:8px}
.grad-btn{border:none;background:linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888);color:#fff;font-size:14px;padding:10px 22px;border-radius:24px;cursor:pointer;font-weight:600;display:flex;align-items:center;gap:6px}
#rendered-wrap{padding:20px}
.et{outline:none;border-radius:3px;transition:background .12s;cursor:text;color:inherit!important;-webkit-text-fill-color:inherit!important}
.et:hover{background:rgba(220,39,67,.07)}
.et:focus{background:rgba(220,39,67,.1);box-shadow:0 0 0 1.5px rgba(220,39,67,.22)}
#divider{width:1px;background:#efefef;flex-shrink:0;position:relative}
#source-pane{width:300px;flex-shrink:0;display:flex;flex-direction:column;background:#fff;border-left:1px solid #efefef;transition:width .2s ease}
#source-pane.collapsed{width:0;overflow:hidden;border-left:none}
#source-header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid #efefef;flex-shrink:0}
#source-label{font-size:12px;font-weight:600;color:#262626;white-space:nowrap}
#char-count{font-size:11px;color:#8e8e8e;margin-left:6px;font-weight:400}
#toggle-source{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;border:1px solid #dbdbdb;background:#fff;flex-shrink:0}
#toggle-source:hover{background:#f5f5f5}
#source-code{flex:1;overflow:auto;padding:14px;font-family:'SF Mono',Monaco,monospace;font-size:11px;color:#262626;line-height:1.65;white-space:pre-wrap;word-break:break-all;background:#fafafa}
#source-toggle-bar{position:absolute;right:-14px;top:50%;transform:translateY(-50%);width:28px;height:28px;border-radius:50%;background:#fff;border:1px solid #dbdbdb;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:10;box-shadow:0 2px 6px rgba(0,0,0,.08)}
#source-toggle-bar:hover{background:#f5f5f5}
.title-bar{font-size:11px;color:#8e8e8e;font-family:'SF Mono',Monaco,monospace;padding:8px 12px;background:#f5f5f5;border-radius:8px;margin-bottom:14px;border:1px solid #efefef}
#statusbar{display:flex;align-items:center;justify-content:space-between;padding:5px 16px;background:#fff;border-top:1px solid #efefef;flex-shrink:0}
#save-hint{font-size:11px;color:#8e8e8e}
#byline{font-size:11px;color:#c7c7c7;letter-spacing:.02em;font-family:'SF Mono',Monaco,monospace}
#byline span{background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:600}
#byline-link{background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:600;text-decoration:none;cursor:pointer;transition:opacity .15s}
#byline-link:hover{opacity:.7}
#fs-notice{font-size:12px;color:#92400e;background:#fef3c7;border:1px solid #fde68a;border-radius:8px;padding:10px 12px;margin:12px;display:none;line-height:1.6}
</style>
</head>
<body>
<div id="toolbar">
<span id="logo">htmlTypist</span>
<span id="dot"></span>
<span id="fname"></span>
<span id="status"></span>
<div id="toolbar-right">
<button class="tb-btn" id="open-btn"></button>
<button class="tb-btn" id="try-btn"></button>
<button class="tb-btn" id="copy-btn" disabled></button>
<button id="save-btn" disabled></button>
<div id="lang-wrap">
<button id="lang-btn"><span id="lang-btn-label">EN</span></button>
<div id="lang-menu">
<div class="lang-opt active" data-lang="en"><span class="lang-opt-label">English</span></div>
<div class="lang-opt" data-lang="zh"><span class="lang-opt-label">中文</span></div>
<div class="lang-opt" data-lang="ja"><span class="lang-opt-label">日本語</span></div>
<div class="lang-opt" data-lang="ko"><span class="lang-opt-label">한국어</span></div>
</div>
</div>
</div>
</div>
<div id="main">
<div id="preview-pane">
<div id="drop-overlay">
<div id="overlay-svg-wrap">
<svg id="overlay-svg" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<defs>
<linearGradient id="ig" x1="0%" y1="100%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#f09433"/>
<stop offset="25%" stop-color="#e6683c"/>
<stop offset="50%" stop-color="#dc2743"/>
<stop offset="75%" stop-color="#cc2366"/>
<stop offset="100%" stop-color="#bc1888"/>
</linearGradient>
<linearGradient id="ig2" x1="0%" y1="100%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#f09433" stop-opacity="0.15"/>
<stop offset="100%" stop-color="#bc1888" stop-opacity="0.15"/>
</linearGradient>
</defs>
<!-- outer ring animated -->
<circle cx="100" cy="100" r="88" fill="none" stroke="url(#ig)" stroke-width="1.5" stroke-dasharray="553" stroke-dashoffset="553" opacity="0.5">
<animate attributeName="stroke-dashoffset" from="553" to="0" dur="1.8s" fill="freeze" calcMode="spline" keySplines="0.4 0 0.2 1"/>
</circle>
<!-- inner fill ring -->
<circle cx="100" cy="100" r="72" fill="url(#ig2)">
<animate attributeName="r" from="0" to="72" dur="0.8s" fill="freeze" calcMode="spline" keySplines="0.4 0 0.2 1"/>
<animate attributeName="opacity" from="0" to="1" dur="0.8s" fill="freeze"/>
</circle>
<!-- file body -->
<g opacity="0">
<animate attributeName="opacity" from="0" to="1" begin="0.5s" dur="0.5s" fill="freeze"/>
<rect x="74" y="64" width="46" height="58" rx="5" fill="none" stroke="url(#ig)" stroke-width="2"/>
<!-- fold -->
<polyline points="100,64 100,78 114,78" fill="none" stroke="url(#ig)" stroke-width="2" stroke-linejoin="round"/>
<!-- lines -->
<line x1="82" y1="90" x2="108" y2="90" stroke="url(#ig)" stroke-width="2" stroke-linecap="round" opacity="0.7"/>
<line x1="82" y1="100" x2="108" y2="100" stroke="url(#ig)" stroke-width="2" stroke-linecap="round" opacity="0.7"/>
<line x1="82" y1="110" x2="98" y2="110" stroke="url(#ig)" stroke-width="2" stroke-linecap="round" opacity="0.7"/>
</g>
<!-- floating dots -->
<circle cx="36" cy="60" r="4" fill="url(#ig)" opacity="0">
<animate attributeName="opacity" values="0;0.6;0" dur="3s" begin="1s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;4,-8;0,0" dur="3s" begin="1s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.2 1;0.4 0 0.2 1"/>
</circle>
<circle cx="164" cy="140" r="3" fill="url(#ig)" opacity="0">
<animate attributeName="opacity" values="0;0.5;0" dur="2.5s" begin="1.4s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;-6,-6;0,0" dur="2.5s" begin="1.4s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.2 1;0.4 0 0.2 1"/>
</circle>
<circle cx="50" cy="148" r="2.5" fill="url(#ig)" opacity="0">
<animate attributeName="opacity" values="0;0.4;0" dur="3.5s" begin="0.8s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;3,-10;0,0" dur="3.5s" begin="0.8s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.2 1;0.4 0 0.2 1"/>
</circle>
<circle cx="156" cy="52" r="3.5" fill="url(#ig)" opacity="0">
<animate attributeName="opacity" values="0;0.5;0" dur="4s" begin="2s" repeatCount="indefinite"/>
<animateTransform attributeName="transform" type="translate" values="0,0;-4,8;0,0" dur="4s" begin="2s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.2 1;0.4 0 0.2 1"/>
</circle>
</svg>
</div>
<p id="overlay-title"></p>
<small id="overlay-sub"></small>
<div class="drop-btns">
<button class="tb-btn" id="open-btn2"></button>
<button class="grad-btn" id="try-btn2"></button>
</div>
</div>
<div id="fs-notice"></div>
<div id="rendered-wrap" style="display:none"></div>
</div>
<div id="divider">
<div id="source-toggle-bar">
<svg id="toggle-icon" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#8e8e8e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:inline-block;vertical-align:middle;transition:transform .2s"><polyline points="9 18 15 12 9 6"/></svg>
</div>
</div>
<div id="source-pane">
<div id="source-header">
<span id="source-label"> <span id="char-count" style="font-size:11px;color:#8e8e8e;margin-left:4px;font-weight:400"></span></span>
<button id="toggle-source"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#8e8e8e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:inline-block;vertical-align:middle"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
</div>
<div id="source-code"></div>
</div>
</div>
<div id="statusbar">
<span id="save-hint"></span>
<span id="byline">Created by <a href="https://github.com/hxssworks/htmlTypist" target="_blank" rel="noopener" id="byline-link">hxssworks</a> · v1.0.0 · 2026</span>
</div>
<input type="file" id="file-input" accept=".html,.htm" style="display:none">
<script>
const $=id=>document.getElementById(id);
const I18N={
en:{status_idle:'Drop an HTML file to start editing',status_opened:'Opened — click any text to edit',status_saved:'Saved ✓',status_copied:'Copied ✓',status_copy_fail:'Copy failed',status_no_file:'Please open a file first',status_no_body:'No <body> found',btn_open:'Open',btn_sample:'Sample',btn_copy:'Copy',btn_save:'Save',overlay_title:'Drop an HTML file',overlay_sub:'Text nodes become instantly editable',btn_open2:'Choose file',btn_sample2:'Try sample',source_label:'Source',hint_save:'⌘S / Ctrl+S to save back to original',hint_fallback:'Edit then Copy or Download',fs_notice:'⚠️ Direct save requires Chrome / Edge.',btn_dl:'Download',downloaded:'Downloaded',lang_label:'EN'},
zh:{status_idle:'拖入 HTML 文件开始编辑',status_opened:'已打开,点击文字即可编辑',status_saved:'已保存 ✓',status_copied:'已复制 ✓',status_copy_fail:'复制失败',status_no_file:'请先加载文件',status_no_body:'未找到 <body>',btn_open:'打开',btn_sample:'示例',btn_copy:'复制',btn_save:'保存',overlay_title:'拖入 HTML 文件',overlay_sub:'文字区域自动变为可编辑',btn_open2:'选择文件',btn_sample2:'试用示例',source_label:'源码',hint_save:'Cmd+S / Ctrl+S 保存回原文件',hint_fallback:'编辑后点复制或下载',fs_notice:'⚠️ 直接保存需要 Chrome / Edge。',btn_dl:'下载',downloaded:'已下载',lang_label:'CN'},
ja:{status_idle:'HTMLファイルをドロップして編集開始',status_opened:'開きました。テキストをクリックして編集',status_saved:'保存しました ✓',status_copied:'コピーしました ✓',status_copy_fail:'コピー失敗',status_no_file:'ファイルを開いてください',status_no_body:'<body> が見つかりません',btn_open:'開く',btn_sample:'サンプル',btn_copy:'コピー',btn_save:'保存',overlay_title:'HTMLファイルをドロップ',overlay_sub:'テキストノードが即座に編集可能になります',btn_open2:'ファイルを選択',btn_sample2:'サンプルを試す',source_label:'ソース',hint_save:'⌘S / Ctrl+S で元ファイルに保存',hint_fallback:'コピーまたはダウンロードで保存',fs_notice:'⚠️ 直接保存は Chrome / Edge が必要です。',btn_dl:'ダウンロード',downloaded:'ダウンロードしました',lang_label:'JP'},
ko:{status_idle:'HTML 파일을 드롭해 편집 시작',status_opened:'열렸습니다. 텍스트를 클릭해 편집하세요',status_saved:'저장됨 ✓',status_copied:'복사됨 ✓',status_copy_fail:'복사 실패',status_no_file:'파일을 먼저 열어주세요',status_no_body:'<body> 를 찾을 수 없습니다',btn_open:'열기',btn_sample:'샘플',btn_copy:'복사',btn_save:'저장',overlay_title:'HTML 파일을 드롭하세요',overlay_sub:'텍스트 노드가 즉시 편집 가능 상태로 전환',btn_open2:'파일 선택',btn_sample2:'샘플 사용해보기',source_label:'소스',hint_save:'⌘S / Ctrl+S 로 원본 파일에 저장',hint_fallback:'복사 또는 다운로드로 저장',fs_notice:'⚠️ 직접 저장은 Chrome / Edge가 필요합니다.',btn_dl:'다운로드',downloaded:'다운로드됨',lang_label:'KR'}
};
const SAMPLES={
en:`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Editing HTML Has Never Felt This Natural — htmlTypist</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;padding:0;min-height:100%;background:#f7f4ef;overflow-x:hidden}
body{font-family:Georgia,'Times New Roman',serif;color:#1a1a1a;position:relative}
/* ── floating typographic glyphs ── */
.glyphs{position:fixed;inset:0;pointer-events:none;z-index:0;overflow:hidden}
.g{position:absolute;font-family:Georgia,'Times New Roman',serif;
background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
background-clip:text;
animation:floatG var(--dur,12s) ease-in-out infinite var(--delay,0s);
opacity:var(--op,0.13);line-height:1;user-select:none}
@keyframes floatG{
0%,100%{transform:translate(0,0) rotate(var(--r0,0deg))}
40%{transform:translate(var(--tx,6px),var(--ty,-14px)) rotate(var(--r1,3deg))}
70%{transform:translate(var(--tx2,-5px),var(--ty2,8px)) rotate(var(--r2,-2deg))}
}
.g1{top:1%;left:1%;font-size:120px;font-weight:700;--dur:14s;--delay:0s;--tx:10px;--ty:-20px;--r1:2deg;--tx2:-8px;--ty2:12px;--r2:-1deg;--op:0.07}
.g2{top:3%;left:36%;font-size:48px;font-style:italic;--dur:10s;--delay:1s;--tx:-8px;--ty:-12px;--r0:-20deg;--r1:-24deg;--tx2:6px;--ty2:8px;--r2:-17deg;--op:0.12}
.g3{top:2%;right:4%;font-size:80px;font-weight:700;--dur:13s;--delay:0.5s;--tx:6px;--ty:-16px;--r1:3deg;--tx2:-5px;--ty2:10px;--r2:-2deg;--op:0.08}
.g4{top:18%;left:1%;font-size:36px;font-style:italic;--dur:9s;--delay:2s;--tx:8px;--ty:-10px;--r1:-5deg;--tx2:-6px;--ty2:6px;--r2:4deg;--op:0.14}
.g5{top:28%;right:2%;font-size:64px;font-weight:700;--dur:16s;--delay:0.8s;--tx:-10px;--ty:-18px;--r1:4deg;--tx2:7px;--ty2:10px;--r2:-3deg;--op:0.07}
.g6{top:48%;left:2%;font-size:28px;font-family:'Courier New',monospace;--dur:11s;--delay:1.5s;--tx:6px;--ty:-10px;--r1:-3deg;--tx2:-4px;--ty2:8px;--r2:2deg;--op:0.16}
.g7{top:55%;right:3%;font-size:52px;font-style:italic;--dur:12s;--delay:3s;--tx:-8px;--ty:-14px;--r1:5deg;--tx2:6px;--ty2:8px;--r2:-4deg;--op:0.1}
.g8{bottom:22%;left:1%;font-size:96px;font-weight:700;--dur:15s;--delay:0.3s;--tx:12px;--ty:-16px;--r1:2deg;--tx2:-8px;--ty2:10px;--r2:-1deg;--op:0.06}
.g9{bottom:14%;right:2%;font-size:40px;font-family:'Courier New',monospace;--dur:10s;--delay:2.4s;--tx:-6px;--ty:-12px;--r1:-4deg;--tx2:5px;--ty2:8px;--r2:3deg;--op:0.13}
.g10{bottom:5%;left:30%;font-size:60px;font-style:italic;--dur:13s;--delay:1.8s;--tx:8px;--ty:-14px;--r1:3deg;--tx2:-6px;--ty2:10px;--r2:-2deg;--op:0.09}
.g11{bottom:3%;right:8%;font-size:32px;font-family:'Courier New',monospace;--dur:9s;--delay:4s;--tx:-6px;--ty:-8px;--r1:-3deg;--tx2:4px;--ty2:6px;--r2:2deg;--op:0.15}
.g12{top:38%;left:44%;font-size:22px;font-family:'Courier New',monospace;--dur:8s;--delay:2.8s;--tx:4px;--ty:-8px;--r1:2deg;--tx2:-3px;--ty2:6px;--r2:-1deg;--op:0.18}
/* ── newspaper layout ── */
.content{position:relative;z-index:10;max-width:740px;margin:0 auto;padding:52px 52px 72px}
/* masthead */
.masthead{text-align:center;border-bottom:3px double #1a1a1a;padding-bottom:16px;margin-bottom:6px}
.masthead-title{font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:#555;margin-bottom:10px;font-family:-apple-system,sans-serif}
.masthead-name{font-size:52px;font-weight:700;letter-spacing:-.5px;line-height:1;color:#1a1a1a}
.masthead-name span{background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.masthead-sub{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:#888;margin-top:8px;font-family:-apple-system,sans-serif}
.dateline{display:flex;justify-content:space-between;align-items:center;font-size:11px;color:#888;letter-spacing:.06em;font-family:-apple-system,sans-serif;padding:8px 0;border-bottom:1px solid #1a1a1a;margin-bottom:28px}
.dateline-grad{background:linear-gradient(45deg,#f09433,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:600}
/* headline hierarchy */
.kicker{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:#888;margin-bottom:8px;font-family:-apple-system,sans-serif}
h1{font-size:38px;font-weight:700;line-height:1.15;letter-spacing:-.3px;margin:0 0 10px;color:#1a1a1a}
h2{font-size:22px;font-weight:700;line-height:1.25;letter-spacing:-.1px;margin:32px 0 12px;color:#1a1a1a;border-top:1px solid #d0c9be;padding-top:18px}
h3{font-size:16px;font-weight:700;margin:24px 0 8px;color:#1a1a1a;font-style:italic}
.standfirst{font-size:17px;line-height:1.6;color:#333;font-style:italic;border-left:3px solid;border-image:linear-gradient(to bottom,#f09433,#bc1888) 1;padding-left:16px;margin:16px 0 24px}
.byline{font-size:11px;color:#888;letter-spacing:.08em;font-family:-apple-system,sans-serif;margin-bottom:24px;padding-bottom:14px;border-bottom:1px solid #d0c9be}
.byline strong{color:#1a1a1a}
p{font-size:16px;line-height:1.82;margin:0 0 18px;color:#222;text-align:justify}
blockquote{margin:28px 0;padding:18px 24px;border-left:4px solid;border-image:linear-gradient(to bottom,#f09433,#bc1888) 1;background:rgba(240,148,51,.04);font-style:italic;font-size:17px;color:#444;line-height:1.65}
.rule{border:none;border-top:1px solid #d0c9be;margin:28px 0}
ul,ol{margin:0 0 18px;padding-left:22px}
li{font-size:15px;line-height:1.75;margin-bottom:6px;color:#222}
.pull-quote{font-size:22px;font-weight:700;line-height:1.35;color:#1a1a1a;border-top:3px solid;border-bottom:1px solid;border-image:none;border-color:#1a1a1a;padding:16px 0;margin:32px 0;letter-spacing:-.2px}
.tag{display:inline-block;font-size:10px;letter-spacing:.14em;text-transform:uppercase;padding:3px 8px;border:1px solid;font-family:-apple-system,sans-serif;margin-bottom:18px;background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;border-image:linear-gradient(45deg,#f09433,#bc1888) 1}
.footer-rule{border:none;border-top:3px double #1a1a1a;margin:40px 0 16px}
footer{font-size:11px;color:#aaa;letter-spacing:.06em;font-family:-apple-system,sans-serif;display:flex;justify-content:space-between}
footer span{background:linear-gradient(45deg,#f09433,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:600}
</style>
</head>
<body>
<div class="glyphs">
<div class="g g1">T</div>
<div class="g g2">edit</div>
<div class="g g3">H</div>
<div class="g g4">“</div>
<div class="g g5">A</div>
<div class="g g6"></></div>
<div class="g g7">&</div>
<div class="g g8">P</div>
<div class="g g9">{}</div>
<div class="g g10"><em>f</em></div>
<div class="g g11">⌘S</div>
<div class="g g12">¶</div>
</div>
<div class="content">
<div class="masthead">
<div class="masthead-title">The Visual HTML Editor · Sample Document</div>
<div class="masthead-name">html<span>Typist</span></div>
<div class="masthead-sub">The Visual HTML Editor</div>
</div>
<div class="dateline">
<span>Vol. 1 · Issue 1</span>
<span class="dateline-grad">htmlTypist · hxssworks · 2026</span>
<span>Sample Edition</span>
</div>
<div class="tag">Feature</div>
<div class="kicker">Technology · Web Publishing</div>
<h1>Editing HTML Has Never Felt This Natural</h1>
<p class="standfirst">A new kind of editor lets you click directly on rendered text and type — no source code required, no learning curve, no friction.</p>
<div class="byline">By <strong>hxssworks</strong> · htmlTypist Editorial · 2026</div>
<p>This is a sample page loaded inside <strong>htmlTypist</strong>. Every piece of text you see here is editable — simply click any word and begin typing. The experience feels closer to editing a document than writing code.</p>
<p>The editor preserves the complete structure and styling of your original HTML file. Headings stay headings. Paragraphs stay paragraphs. Only the text content changes; nothing else is touched.</p>
<h2>How It Works</h2>
<h3>A dual-tree architecture for reliable editing</h3>
<p>htmlTypist parses your HTML file into two parallel DOM trees: one for rendering, one for serialisation. Every editable text node is mapped by a unique identifier, so edits flow directly to the output without path-tracking errors.</p>
<ul><li>Click any text to edit it in place</li><li>Press ⌘S or Ctrl+S to save back to the original file</li><li>Toggle the source panel on the right to inspect changes live</li><li>Copy or download the result when finished</li></ul>
<div class="pull-quote">"The best tool is the one that gets out of the way."</div>
<h2>Design Philosophy</h2>
<blockquote>Good design is as little design as possible. Less, but better — the same principle that guides htmlTypist: a single file, zero dependencies, instant load, and direct manipulation of what you see.</blockquote>
<hr class="rule">
<hr class="footer-rule">
<footer>
<span>htmlTypist</span>
<span style="-webkit-text-fill-color:#aaa;background:none">Created by hxssworks · v1.0.0 · 2026</span>
</footer>
</div>
</body>
</html>`,
zh:`<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>编辑 HTML,从未如此自然 — htmlTypist</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;padding:0;min-height:100%;background:#f7f4ef;overflow-x:hidden}
body{font-family:Georgia,'Times New Roman',serif;color:#1a1a1a;position:relative}
/* ── floating typographic glyphs ── */
.glyphs{position:fixed;inset:0;pointer-events:none;z-index:0;overflow:hidden}
.g{position:absolute;font-family:Georgia,'Times New Roman',serif;
background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
background-clip:text;
animation:floatG var(--dur,12s) ease-in-out infinite var(--delay,0s);
opacity:var(--op,0.13);line-height:1;user-select:none}
@keyframes floatG{
0%,100%{transform:translate(0,0) rotate(var(--r0,0deg))}
40%{transform:translate(var(--tx,6px),var(--ty,-14px)) rotate(var(--r1,3deg))}
70%{transform:translate(var(--tx2,-5px),var(--ty2,8px)) rotate(var(--r2,-2deg))}
}
.g1{top:1%;left:1%;font-size:120px;font-weight:700;--dur:14s;--delay:0s;--tx:10px;--ty:-20px;--r1:2deg;--tx2:-8px;--ty2:12px;--r2:-1deg;--op:0.07}
.g2{top:3%;left:36%;font-size:48px;font-style:italic;--dur:10s;--delay:1s;--tx:-8px;--ty:-12px;--r0:-20deg;--r1:-24deg;--tx2:6px;--ty2:8px;--r2:-17deg;--op:0.12}
.g3{top:2%;right:4%;font-size:80px;font-weight:700;--dur:13s;--delay:0.5s;--tx:6px;--ty:-16px;--r1:3deg;--tx2:-5px;--ty2:10px;--r2:-2deg;--op:0.08}
.g4{top:18%;left:1%;font-size:36px;font-style:italic;--dur:9s;--delay:2s;--tx:8px;--ty:-10px;--r1:-5deg;--tx2:-6px;--ty2:6px;--r2:4deg;--op:0.14}
.g5{top:28%;right:2%;font-size:64px;font-weight:700;--dur:16s;--delay:0.8s;--tx:-10px;--ty:-18px;--r1:4deg;--tx2:7px;--ty2:10px;--r2:-3deg;--op:0.07}
.g6{top:48%;left:2%;font-size:28px;font-family:'Courier New',monospace;--dur:11s;--delay:1.5s;--tx:6px;--ty:-10px;--r1:-3deg;--tx2:-4px;--ty2:8px;--r2:2deg;--op:0.16}
.g7{top:55%;right:3%;font-size:52px;font-style:italic;--dur:12s;--delay:3s;--tx:-8px;--ty:-14px;--r1:5deg;--tx2:6px;--ty2:8px;--r2:-4deg;--op:0.1}
.g8{bottom:22%;left:1%;font-size:96px;font-weight:700;--dur:15s;--delay:0.3s;--tx:12px;--ty:-16px;--r1:2deg;--tx2:-8px;--ty2:10px;--r2:-1deg;--op:0.06}
.g9{bottom:14%;right:2%;font-size:40px;font-family:'Courier New',monospace;--dur:10s;--delay:2.4s;--tx:-6px;--ty:-12px;--r1:-4deg;--tx2:5px;--ty2:8px;--r2:3deg;--op:0.13}
.g10{bottom:5%;left:30%;font-size:60px;font-style:italic;--dur:13s;--delay:1.8s;--tx:8px;--ty:-14px;--r1:3deg;--tx2:-6px;--ty2:10px;--r2:-2deg;--op:0.09}
.g11{bottom:3%;right:8%;font-size:32px;font-family:'Courier New',monospace;--dur:9s;--delay:4s;--tx:-6px;--ty:-8px;--r1:-3deg;--tx2:4px;--ty2:6px;--r2:2deg;--op:0.15}
.g12{top:38%;left:44%;font-size:22px;font-family:'Courier New',monospace;--dur:8s;--delay:2.8s;--tx:4px;--ty:-8px;--r1:2deg;--tx2:-3px;--ty2:6px;--r2:-1deg;--op:0.18}
/* ── newspaper layout ── */
.content{position:relative;z-index:10;max-width:740px;margin:0 auto;padding:52px 52px 72px}
/* masthead */
.masthead{text-align:center;border-bottom:3px double #1a1a1a;padding-bottom:16px;margin-bottom:6px}
.masthead-title{font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:#555;margin-bottom:10px;font-family:-apple-system,sans-serif}
.masthead-name{font-size:52px;font-weight:700;letter-spacing:-.5px;line-height:1;color:#1a1a1a}
.masthead-name span{background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.masthead-sub{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:#888;margin-top:8px;font-family:-apple-system,sans-serif}
.dateline{display:flex;justify-content:space-between;align-items:center;font-size:11px;color:#888;letter-spacing:.06em;font-family:-apple-system,sans-serif;padding:8px 0;border-bottom:1px solid #1a1a1a;margin-bottom:28px}
.dateline-grad{background:linear-gradient(45deg,#f09433,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:600}
/* headline hierarchy */
.kicker{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:#888;margin-bottom:8px;font-family:-apple-system,sans-serif}
h1{font-size:38px;font-weight:700;line-height:1.15;letter-spacing:-.3px;margin:0 0 10px;color:#1a1a1a}
h2{font-size:22px;font-weight:700;line-height:1.25;letter-spacing:-.1px;margin:32px 0 12px;color:#1a1a1a;border-top:1px solid #d0c9be;padding-top:18px}
h3{font-size:16px;font-weight:700;margin:24px 0 8px;color:#1a1a1a;font-style:italic}
.standfirst{font-size:17px;line-height:1.6;color:#333;font-style:italic;border-left:3px solid;border-image:linear-gradient(to bottom,#f09433,#bc1888) 1;padding-left:16px;margin:16px 0 24px}
.byline{font-size:11px;color:#888;letter-spacing:.08em;font-family:-apple-system,sans-serif;margin-bottom:24px;padding-bottom:14px;border-bottom:1px solid #d0c9be}
.byline strong{color:#1a1a1a}
p{font-size:16px;line-height:1.82;margin:0 0 18px;color:#222;text-align:justify}
blockquote{margin:28px 0;padding:18px 24px;border-left:4px solid;border-image:linear-gradient(to bottom,#f09433,#bc1888) 1;background:rgba(240,148,51,.04);font-style:italic;font-size:17px;color:#444;line-height:1.65}
.rule{border:none;border-top:1px solid #d0c9be;margin:28px 0}
ul,ol{margin:0 0 18px;padding-left:22px}
li{font-size:15px;line-height:1.75;margin-bottom:6px;color:#222}
.pull-quote{font-size:22px;font-weight:700;line-height:1.35;color:#1a1a1a;border-top:3px solid;border-bottom:1px solid;border-image:none;border-color:#1a1a1a;padding:16px 0;margin:32px 0;letter-spacing:-.2px}
.tag{display:inline-block;font-size:10px;letter-spacing:.14em;text-transform:uppercase;padding:3px 8px;border:1px solid;font-family:-apple-system,sans-serif;margin-bottom:18px;background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;border-image:linear-gradient(45deg,#f09433,#bc1888) 1}
.footer-rule{border:none;border-top:3px double #1a1a1a;margin:40px 0 16px}
footer{font-size:11px;color:#aaa;letter-spacing:.06em;font-family:-apple-system,sans-serif;display:flex;justify-content:space-between}
footer span{background:linear-gradient(45deg,#f09433,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:600}
</style>
</head>
<body>
<div class="glyphs">
<div class="g g1">文</div>
<div class="g g2">字</div>
<div class="g g3">编</div>
<div class="g g4">「</div>
<div class="g g5">辑</div>
<div class="g g6"></></div>
<div class="g g7">」</div>
<div class="g g8">排</div>
<div class="g g9">{}</div>
<div class="g g10">版</div>
<div class="g g11">⌘S</div>
<div class="g g12">¶</div>
</div>
<div class="content">
<div class="masthead">
<div class="masthead-title">可视化 HTML 编辑器 · 示例文档</div>
<div class="masthead-name">html<span>Typist</span></div>
<div class="masthead-sub">The Visual HTML Editor</div>
</div>
<div class="dateline">
<span>Vol. 1 · Issue 1</span>
<span class="dateline-grad">htmlTypist · hxssworks · 2026</span>
<span>Sample Edition</span>
</div>
<div class="tag">Feature</div>
<div class="kicker">技术 · 网页出版</div>
<h1>编辑 HTML,从未如此自然</h1>
<p class="standfirst">一种全新的编辑器让你直接点击渲染后的文字并开始输入——无需查看源码,无需学习曲线,零摩擦。</p>
<div class="byline">作者:<strong>hxssworks</strong> · htmlTypist 编辑部 · 2026</div>
<p>这是一份加载在 <strong>htmlTypist</strong> 中的示例页面。页面上所有文字都是可编辑的——点击任意词句,即可直接修改。这种体验更接近于编辑一份文档,而非书写代码。</p>
<p>编辑器会完整保留原始 HTML 文件的结构与样式。标题仍然是标题,段落仍然是段落。只有文本内容发生变化,其他一切保持原样。</p>
<h2>工作原理</h2>
<h3>双树架构,保障编辑的可靠性</h3>
<p>htmlTypist 将你的 HTML 文件解析为两棵并行的 DOM 树:一棵用于渲染展示,一棵用于序列化输出。每个可编辑的文本节点都通过唯一标识符映射,编辑结果直接写回输出,不存在路径追踪错误。</p>
<ul><li>点击任意文字,就地编辑</li><li>按 Cmd+S 或 Ctrl+S 保存回原文件</li><li>点击右侧分隔条,实时查看源码变化</li><li>编辑完成后复制或下载结果</li></ul>
<div class="pull-quote">「最好的工具,是那个不妨碍你的工具。」</div>
<h2>设计理念</h2>
<blockquote>少即是多。删去一切不必要之物,留下最本质的那一部分——这也是 htmlTypist 的设计原则:单一文件,零依赖,即开即用,所见即所编。</blockquote>
<hr class="rule">
<hr class="footer-rule">
<footer>
<span>htmlTypist</span>
<span style="-webkit-text-fill-color:#aaa;background:none">Created by hxssworks · v1.0.0 · 2026</span>
</footer>
</div>
</body>
</html>`,
ja:`<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>HTML 編集が、これほど自然に感じられたことはなかった — htmlTypist</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;padding:0;min-height:100%;background:#f7f4ef;overflow-x:hidden}
body{font-family:Georgia,'Times New Roman',serif;color:#1a1a1a;position:relative}
/* ── floating typographic glyphs ── */
.glyphs{position:fixed;inset:0;pointer-events:none;z-index:0;overflow:hidden}
.g{position:absolute;font-family:Georgia,'Times New Roman',serif;
background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
background-clip:text;
animation:floatG var(--dur,12s) ease-in-out infinite var(--delay,0s);
opacity:var(--op,0.13);line-height:1;user-select:none}
@keyframes floatG{
0%,100%{transform:translate(0,0) rotate(var(--r0,0deg))}
40%{transform:translate(var(--tx,6px),var(--ty,-14px)) rotate(var(--r1,3deg))}
70%{transform:translate(var(--tx2,-5px),var(--ty2,8px)) rotate(var(--r2,-2deg))}
}
.g1{top:1%;left:1%;font-size:120px;font-weight:700;--dur:14s;--delay:0s;--tx:10px;--ty:-20px;--r1:2deg;--tx2:-8px;--ty2:12px;--r2:-1deg;--op:0.07}
.g2{top:3%;left:36%;font-size:48px;font-style:italic;--dur:10s;--delay:1s;--tx:-8px;--ty:-12px;--r0:-20deg;--r1:-24deg;--tx2:6px;--ty2:8px;--r2:-17deg;--op:0.12}
.g3{top:2%;right:4%;font-size:80px;font-weight:700;--dur:13s;--delay:0.5s;--tx:6px;--ty:-16px;--r1:3deg;--tx2:-5px;--ty2:10px;--r2:-2deg;--op:0.08}
.g4{top:18%;left:1%;font-size:36px;font-style:italic;--dur:9s;--delay:2s;--tx:8px;--ty:-10px;--r1:-5deg;--tx2:-6px;--ty2:6px;--r2:4deg;--op:0.14}
.g5{top:28%;right:2%;font-size:64px;font-weight:700;--dur:16s;--delay:0.8s;--tx:-10px;--ty:-18px;--r1:4deg;--tx2:7px;--ty2:10px;--r2:-3deg;--op:0.07}
.g6{top:48%;left:2%;font-size:28px;font-family:'Courier New',monospace;--dur:11s;--delay:1.5s;--tx:6px;--ty:-10px;--r1:-3deg;--tx2:-4px;--ty2:8px;--r2:2deg;--op:0.16}
.g7{top:55%;right:3%;font-size:52px;font-style:italic;--dur:12s;--delay:3s;--tx:-8px;--ty:-14px;--r1:5deg;--tx2:6px;--ty2:8px;--r2:-4deg;--op:0.1}
.g8{bottom:22%;left:1%;font-size:96px;font-weight:700;--dur:15s;--delay:0.3s;--tx:12px;--ty:-16px;--r1:2deg;--tx2:-8px;--ty2:10px;--r2:-1deg;--op:0.06}
.g9{bottom:14%;right:2%;font-size:40px;font-family:'Courier New',monospace;--dur:10s;--delay:2.4s;--tx:-6px;--ty:-12px;--r1:-4deg;--tx2:5px;--ty2:8px;--r2:3deg;--op:0.13}
.g10{bottom:5%;left:30%;font-size:60px;font-style:italic;--dur:13s;--delay:1.8s;--tx:8px;--ty:-14px;--r1:3deg;--tx2:-6px;--ty2:10px;--r2:-2deg;--op:0.09}
.g11{bottom:3%;right:8%;font-size:32px;font-family:'Courier New',monospace;--dur:9s;--delay:4s;--tx:-6px;--ty:-8px;--r1:-3deg;--tx2:4px;--ty2:6px;--r2:2deg;--op:0.15}
.g12{top:38%;left:44%;font-size:22px;font-family:'Courier New',monospace;--dur:8s;--delay:2.8s;--tx:4px;--ty:-8px;--r1:2deg;--tx2:-3px;--ty2:6px;--r2:-1deg;--op:0.18}
/* ── newspaper layout ── */
.content{position:relative;z-index:10;max-width:740px;margin:0 auto;padding:52px 52px 72px}
/* masthead */
.masthead{text-align:center;border-bottom:3px double #1a1a1a;padding-bottom:16px;margin-bottom:6px}
.masthead-title{font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:#555;margin-bottom:10px;font-family:-apple-system,sans-serif}
.masthead-name{font-size:52px;font-weight:700;letter-spacing:-.5px;line-height:1;color:#1a1a1a}
.masthead-name span{background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.masthead-sub{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:#888;margin-top:8px;font-family:-apple-system,sans-serif}
.dateline{display:flex;justify-content:space-between;align-items:center;font-size:11px;color:#888;letter-spacing:.06em;font-family:-apple-system,sans-serif;padding:8px 0;border-bottom:1px solid #1a1a1a;margin-bottom:28px}
.dateline-grad{background:linear-gradient(45deg,#f09433,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:600}
/* headline hierarchy */
.kicker{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:#888;margin-bottom:8px;font-family:-apple-system,sans-serif}
h1{font-size:38px;font-weight:700;line-height:1.15;letter-spacing:-.3px;margin:0 0 10px;color:#1a1a1a}
h2{font-size:22px;font-weight:700;line-height:1.25;letter-spacing:-.1px;margin:32px 0 12px;color:#1a1a1a;border-top:1px solid #d0c9be;padding-top:18px}
h3{font-size:16px;font-weight:700;margin:24px 0 8px;color:#1a1a1a;font-style:italic}
.standfirst{font-size:17px;line-height:1.6;color:#333;font-style:italic;border-left:3px solid;border-image:linear-gradient(to bottom,#f09433,#bc1888) 1;padding-left:16px;margin:16px 0 24px}
.byline{font-size:11px;color:#888;letter-spacing:.08em;font-family:-apple-system,sans-serif;margin-bottom:24px;padding-bottom:14px;border-bottom:1px solid #d0c9be}
.byline strong{color:#1a1a1a}
p{font-size:16px;line-height:1.82;margin:0 0 18px;color:#222;text-align:justify}
blockquote{margin:28px 0;padding:18px 24px;border-left:4px solid;border-image:linear-gradient(to bottom,#f09433,#bc1888) 1;background:rgba(240,148,51,.04);font-style:italic;font-size:17px;color:#444;line-height:1.65}
.rule{border:none;border-top:1px solid #d0c9be;margin:28px 0}
ul,ol{margin:0 0 18px;padding-left:22px}
li{font-size:15px;line-height:1.75;margin-bottom:6px;color:#222}
.pull-quote{font-size:22px;font-weight:700;line-height:1.35;color:#1a1a1a;border-top:3px solid;border-bottom:1px solid;border-image:none;border-color:#1a1a1a;padding:16px 0;margin:32px 0;letter-spacing:-.2px}
.tag{display:inline-block;font-size:10px;letter-spacing:.14em;text-transform:uppercase;padding:3px 8px;border:1px solid;font-family:-apple-system,sans-serif;margin-bottom:18px;background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;border-image:linear-gradient(45deg,#f09433,#bc1888) 1}
.footer-rule{border:none;border-top:3px double #1a1a1a;margin:40px 0 16px}
footer{font-size:11px;color:#aaa;letter-spacing:.06em;font-family:-apple-system,sans-serif;display:flex;justify-content:space-between}
footer span{background:linear-gradient(45deg,#f09433,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:600}
</style>
</head>
<body>
<div class="glyphs">
<div class="g g1">文</div>
<div class="g g2">字</div>
<div class="g g3">編</div>
<div class="g g4">「</div>
<div class="g g5">集</div>
<div class="g g6"></></div>
<div class="g g7">」</div>
<div class="g g8">組</div>
<div class="g g9">{}</div>
<div class="g g10">版</div>
<div class="g g11">⌘S</div>
<div class="g g12">¶</div>
</div>
<div class="content">
<div class="masthead">
<div class="masthead-title">ビジュアル HTML エディタ · サンプル</div>
<div class="masthead-name">html<span>Typist</span></div>
<div class="masthead-sub">The Visual HTML Editor</div>
</div>
<div class="dateline">
<span>Vol. 1 · Issue 1</span>
<span class="dateline-grad">htmlTypist · hxssworks · 2026</span>
<span>Sample Edition</span>
</div>
<div class="tag">Feature</div>
<div class="kicker">テクノロジー · ウェブ出版</div>
<h1>HTML 編集が、これほど自然に感じられたことはなかった</h1>
<p class="standfirst">新しいエディタは、レンダリングされたテキストを直接クリックして入力できる——ソースコード不要、学習コストなし、ストレスゼロ。</p>
<div class="byline">著者:<strong>hxssworks</strong> · htmlTypist 編集部 · 2026</div>
<p>これは <strong>htmlTypist</strong> に読み込まれたサンプルページです。ページ上のすべてのテキストは編集可能です——任意の文字をクリックして、そのまま入力を始めてください。コードを書くより、文書を編集する感覚に近い体験です。</p>
<p>エディタは元の HTML ファイルの構造とスタイルを完全に保持します。見出しは見出しのまま、段落は段落のまま。変更されるのはテキストの内容のみで、他はすべてそのままです。</p>
<h2>仕組み</h2>
<h3>デュアルツリー構造による信頼性の高い編集</h3>
<p>htmlTypist は HTML ファイルを並列する 2 つの DOM ツリーに解析します。一方はレンダリング用、もう一方はシリアライズ用です。各テキストノードは一意の識別子でマッピングされ、編集結果はパス追跡エラーなしに直接出力に反映されます。</p>
<ul><li>任意のテキストをクリックして就地編集</li><li>⌘S または Ctrl+S で元ファイルに保存</li><li>右側の仕切りをクリックしてソースをリアルタイム確認</li><li>編集後はコピーまたはダウンロード</li></ul>
<div class="pull-quote">「最良のツールとは、邪魔をしないツールだ。」</div>
<h2>デザイン哲学</h2>
<blockquote>少ないことは、豊かなことだ。不要なものをすべて取り除き、本質だけを残す——それが htmlTypist の設計原則です:単一ファイル、ゼロ依存、即時起動、直接操作。</blockquote>
<hr class="rule">
<hr class="footer-rule">
<footer>
<span>htmlTypist</span>
<span style="-webkit-text-fill-color:#aaa;background:none">Created by hxssworks · v1.0.0 · 2026</span>
</footer>
</div>
</body>
</html>`,
ko:`<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>HTML 편집이 이토록 자연스러웠던 적은 없었다 — htmlTypist</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;padding:0;min-height:100%;background:#f7f4ef;overflow-x:hidden}
body{font-family:Georgia,'Times New Roman',serif;color:#1a1a1a;position:relative}
/* ── floating typographic glyphs ── */
.glyphs{position:fixed;inset:0;pointer-events:none;z-index:0;overflow:hidden}
.g{position:absolute;font-family:Georgia,'Times New Roman',serif;
background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
background-clip:text;
animation:floatG var(--dur,12s) ease-in-out infinite var(--delay,0s);
opacity:var(--op,0.13);line-height:1;user-select:none}
@keyframes floatG{
0%,100%{transform:translate(0,0) rotate(var(--r0,0deg))}
40%{transform:translate(var(--tx,6px),var(--ty,-14px)) rotate(var(--r1,3deg))}
70%{transform:translate(var(--tx2,-5px),var(--ty2,8px)) rotate(var(--r2,-2deg))}
}
.g1{top:1%;left:1%;font-size:120px;font-weight:700;--dur:14s;--delay:0s;--tx:10px;--ty:-20px;--r1:2deg;--tx2:-8px;--ty2:12px;--r2:-1deg;--op:0.07}
.g2{top:3%;left:36%;font-size:48px;font-style:italic;--dur:10s;--delay:1s;--tx:-8px;--ty:-12px;--r0:-20deg;--r1:-24deg;--tx2:6px;--ty2:8px;--r2:-17deg;--op:0.12}
.g3{top:2%;right:4%;font-size:80px;font-weight:700;--dur:13s;--delay:0.5s;--tx:6px;--ty:-16px;--r1:3deg;--tx2:-5px;--ty2:10px;--r2:-2deg;--op:0.08}
.g4{top:18%;left:1%;font-size:36px;font-style:italic;--dur:9s;--delay:2s;--tx:8px;--ty:-10px;--r1:-5deg;--tx2:-6px;--ty2:6px;--r2:4deg;--op:0.14}
.g5{top:28%;right:2%;font-size:64px;font-weight:700;--dur:16s;--delay:0.8s;--tx:-10px;--ty:-18px;--r1:4deg;--tx2:7px;--ty2:10px;--r2:-3deg;--op:0.07}
.g6{top:48%;left:2%;font-size:28px;font-family:'Courier New',monospace;--dur:11s;--delay:1.5s;--tx:6px;--ty:-10px;--r1:-3deg;--tx2:-4px;--ty2:8px;--r2:2deg;--op:0.16}
.g7{top:55%;right:3%;font-size:52px;font-style:italic;--dur:12s;--delay:3s;--tx:-8px;--ty:-14px;--r1:5deg;--tx2:6px;--ty2:8px;--r2:-4deg;--op:0.1}
.g8{bottom:22%;left:1%;font-size:96px;font-weight:700;--dur:15s;--delay:0.3s;--tx:12px;--ty:-16px;--r1:2deg;--tx2:-8px;--ty2:10px;--r2:-1deg;--op:0.06}
.g9{bottom:14%;right:2%;font-size:40px;font-family:'Courier New',monospace;--dur:10s;--delay:2.4s;--tx:-6px;--ty:-12px;--r1:-4deg;--tx2:5px;--ty2:8px;--r2:3deg;--op:0.13}
.g10{bottom:5%;left:30%;font-size:60px;font-style:italic;--dur:13s;--delay:1.8s;--tx:8px;--ty:-14px;--r1:3deg;--tx2:-6px;--ty2:10px;--r2:-2deg;--op:0.09}
.g11{bottom:3%;right:8%;font-size:32px;font-family:'Courier New',monospace;--dur:9s;--delay:4s;--tx:-6px;--ty:-8px;--r1:-3deg;--tx2:4px;--ty2:6px;--r2:2deg;--op:0.15}
.g12{top:38%;left:44%;font-size:22px;font-family:'Courier New',monospace;--dur:8s;--delay:2.8s;--tx:4px;--ty:-8px;--r1:2deg;--tx2:-3px;--ty2:6px;--r2:-1deg;--op:0.18}
/* ── newspaper layout ── */
.content{position:relative;z-index:10;max-width:740px;margin:0 auto;padding:52px 52px 72px}
/* masthead */
.masthead{text-align:center;border-bottom:3px double #1a1a1a;padding-bottom:16px;margin-bottom:6px}
.masthead-title{font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:#555;margin-bottom:10px;font-family:-apple-system,sans-serif}
.masthead-name{font-size:52px;font-weight:700;letter-spacing:-.5px;line-height:1;color:#1a1a1a}
.masthead-name span{background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.masthead-sub{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:#888;margin-top:8px;font-family:-apple-system,sans-serif}
.dateline{display:flex;justify-content:space-between;align-items:center;font-size:11px;color:#888;letter-spacing:.06em;font-family:-apple-system,sans-serif;padding:8px 0;border-bottom:1px solid #1a1a1a;margin-bottom:28px}
.dateline-grad{background:linear-gradient(45deg,#f09433,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:600}
/* headline hierarchy */
.kicker{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:#888;margin-bottom:8px;font-family:-apple-system,sans-serif}
h1{font-size:38px;font-weight:700;line-height:1.15;letter-spacing:-.3px;margin:0 0 10px;color:#1a1a1a}
h2{font-size:22px;font-weight:700;line-height:1.25;letter-spacing:-.1px;margin:32px 0 12px;color:#1a1a1a;border-top:1px solid #d0c9be;padding-top:18px}
h3{font-size:16px;font-weight:700;margin:24px 0 8px;color:#1a1a1a;font-style:italic}
.standfirst{font-size:17px;line-height:1.6;color:#333;font-style:italic;border-left:3px solid;border-image:linear-gradient(to bottom,#f09433,#bc1888) 1;padding-left:16px;margin:16px 0 24px}
.byline{font-size:11px;color:#888;letter-spacing:.08em;font-family:-apple-system,sans-serif;margin-bottom:24px;padding-bottom:14px;border-bottom:1px solid #d0c9be}
.byline strong{color:#1a1a1a}
p{font-size:16px;line-height:1.82;margin:0 0 18px;color:#222;text-align:justify}
blockquote{margin:28px 0;padding:18px 24px;border-left:4px solid;border-image:linear-gradient(to bottom,#f09433,#bc1888) 1;background:rgba(240,148,51,.04);font-style:italic;font-size:17px;color:#444;line-height:1.65}
.rule{border:none;border-top:1px solid #d0c9be;margin:28px 0}
ul,ol{margin:0 0 18px;padding-left:22px}
li{font-size:15px;line-height:1.75;margin-bottom:6px;color:#222}
.pull-quote{font-size:22px;font-weight:700;line-height:1.35;color:#1a1a1a;border-top:3px solid;border-bottom:1px solid;border-image:none;border-color:#1a1a1a;padding:16px 0;margin:32px 0;letter-spacing:-.2px}
.tag{display:inline-block;font-size:10px;letter-spacing:.14em;text-transform:uppercase;padding:3px 8px;border:1px solid;font-family:-apple-system,sans-serif;margin-bottom:18px;background:linear-gradient(45deg,#f09433,#dc2743,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;border-image:linear-gradient(45deg,#f09433,#bc1888) 1}
.footer-rule{border:none;border-top:3px double #1a1a1a;margin:40px 0 16px}
footer{font-size:11px;color:#aaa;letter-spacing:.06em;font-family:-apple-system,sans-serif;display:flex;justify-content:space-between}
footer span{background:linear-gradient(45deg,#f09433,#bc1888);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:600}
</style>
</head>
<body>
<div class="glyphs">
<div class="g g1">문</div>
<div class="g g2">자</div>
<div class="g g3">편</div>
<div class="g g4">「</div>
<div class="g g5">집</div>
<div class="g g6"></></div>
<div class="g g7">」</div>
<div class="g g8">타</div>
<div class="g g9">{}</div>
<div class="g g10">이</div>
<div class="g g11">⌘S</div>
<div class="g g12">¶</div>
</div>
<div class="content">
<div class="masthead">
<div class="masthead-title">비주얼 HTML 에디터 · 샘플 문서</div>
<div class="masthead-name">html<span>Typist</span></div>
<div class="masthead-sub">The Visual HTML Editor</div>
</div>
<div class="dateline">
<span>Vol. 1 · Issue 1</span>
<span class="dateline-grad">htmlTypist · hxssworks · 2026</span>
<span>Sample Edition</span>
</div>
<div class="tag">Feature</div>
<div class="kicker">테크놀로지 · 웹 퍼블리싱</div>
<h1>HTML 편집이 이토록 자연스러웠던 적은 없었다</h1>
<p class="standfirst">새로운 에디터는 렌더링된 텍스트를 직접 클릭해 입력할 수 있게 해준다——소스 코드 불필요, 학습 곡선 없음, 마찰 제로.</p>
<div class="byline">저자: <strong>hxssworks</strong> · htmlTypist 편집부 · 2026</div>
<p>이것은 <strong>htmlTypist</strong>에 불러온 샘플 페이지입니다. 이 페이지의 모든 텍스트는 편집 가능합니다——원하는 글자를 클릭하고 바로 입력을 시작하세요. 코드를 작성하는 것보다 문서를 편집하는 느낌에 가깝습니다.</p>
<p>편집기는 원본 HTML 파일의 구조와 스타일을 완전히 보존합니다. 제목은 제목으로, 단락은 단락으로 유지됩니다. 텍스트 내용만 변경되며 나머지는 그대로 유지됩니다.</p>
<h2>작동 원리</h2>
<h3>이중 트리 구조로 신뢰할 수 있는 편집</h3>
<p>htmlTypist는 HTML 파일을 두 개의 병렬 DOM 트리로 파싱합니다. 하나는 렌더링용, 다른 하나는 직렬화용입니다. 각 텍스트 노드는 고유 식별자로 매핑되어, 경로 추적 오류 없이 편집 결과가 직접 출력에 반영됩니다.</p>
<ul><li>텍스트를 클릭해 바로 편집</li><li>⌘S 또는 Ctrl+S로 원본 파일에 저장</li><li>오른쪽 구분선을 클릭해 소스를 실시간 확인</li><li>편집 후 복사 또는 다운로드</li></ul>
<div class="pull-quote">「최고의 도구는 방해하지 않는 도구다.」</div>
<h2>디자인 철학</h2>
<blockquote>적을수록 풍요롭다. 불필요한 것을 모두 제거하고 본질만을 남긴다——이것이 htmlTypist의 설계 원칙입니다: 단일 파일, 의존성 없음, 즉시 로드, 직접 조작.</blockquote>
<hr class="rule">
<hr class="footer-rule">
<footer>
<span>htmlTypist</span>
<span style="-webkit-text-fill-color:#aaa;background:none">Created by hxssworks · v1.0.0 · 2026</span>
</footer>
</div>
</body>
</html>`
};
let currentLang='en',originalDoc=null,fileHandle=null,isDirty=false,sourcePaneVisible=true;
let nodeMap=new Map(),nidCounter=0,flashTimer=null,fsNoticeVisible=false;
const hasFS=!!window.showOpenFilePicker;
const t=k=>I18N[currentLang][k]||I18N.en[k]||k;
function applyLang(){
const l=I18N[currentLang];
$('open-btn').innerHTML=`<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:inline-block;vertical-align:middle"><path d="M5 19a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v1"/><path d="M21 19H7a2 2 0 0 1-2-2l1-7h15l-1 7a2 2 0 0 1-2 2z"/></svg>${l.btn_open}`;
$('try-btn').textContent=l.btn_sample;
$('copy-btn').innerHTML=`<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:inline-block;vertical-align:middle"><rect x="8" y="8" width="13" height="13" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/></svg>${l.btn_copy}`;
$('save-btn').innerHTML=`<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:inline-block;vertical-align:middle"><path d="M6 4h10l4 4v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"/><path d="M10 14a2 2 0 1 0 4 0a2 2 0 0 0-4 0"/><path d="M14 4v4H8V4"/></svg>${l.btn_save}`;
$('lang-btn-label').textContent=l.lang_label;
$('overlay-title').textContent=l.overlay_title;
$('overlay-sub').textContent=l.overlay_sub;
$('open-btn2').innerHTML=`<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:inline-block;vertical-align:middle"><path d="M5 19a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v1"/><path d="M21 19H7a2 2 0 0 1-2-2l1-7h15l-1 7a2 2 0 0 1-2 2z"/></svg>${l.btn_open2}`;
$('try-btn2').textContent=l.btn_sample2;
// source-label: only update the text, keep #char-count in place
$('source-label').childNodes[0].textContent=l.source_label;
if(!originalDoc) $('status').textContent=l.status_idle;
if($('save-hint').textContent) $('save-hint').textContent=hasFS?l.hint_save:l.hint_fallback;
if(fsNoticeVisible){
$('fs-notice').innerHTML=l.fs_notice+`<br><button class="tb-btn" id="dl-btn" style="margin-top:8px;display:inline-flex"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:inline-block;vertical-align:middle"><path d="M12 5v14m-7-7 7 7 7-7"/><line x1="5" y1="19" x2="19" y2="19"/></svg>${l.btn_dl}</button>`;
const d=$('dl-btn');if(d)d.addEventListener('click',downloadFile);
}
document.querySelectorAll('.lang-opt').forEach(el=>el.classList.toggle('active',el.dataset.lang===currentLang));
}
function setStatus(msg,dur=2500){$('status').textContent=msg;if(dur)setTimeout(()=>{if($('status').textContent===msg)$('status').textContent='';},dur);}
function markDirty(){if(isDirty)return;isDirty=true;$('dot').style.display='flex';}
function markClean(){isDirty=false;$('dot').style.display='none';if(flashTimer)clearTimeout(flashTimer);$('save-btn').classList.add('flash-green');flashTimer=setTimeout(()=>$('save-btn').classList.remove('flash-green'),1600);}
function getHTML(){return originalDoc?'<!DOCTYPE html>\n'+originalDoc.documentElement.outerHTML:'';}
function updateSource(){if(!originalDoc)return;const html=getHTML();$('source-code').textContent=html;$('char-count').textContent=' · '+html.length;}
function makeEditable(ob,cb){
nodeMap.clear();nidCounter=0;
function walk(o,c){
const oc=Array.from(o.childNodes),cc=Array.from(c.childNodes);
oc.forEach((oChild,i)=>{
const cChild=cc[i];if(!cChild)return;
if(oChild.nodeType===Node.TEXT_NODE){
if(oChild.textContent.trim()==='')return;
const tag=((oChild.parentElement||{}).tagName||'').toUpperCase();
if(['SCRIPT','STYLE','NOSCRIPT'].includes(tag))return;
const nid=String(++nidCounter);nodeMap.set(nid,oChild);
const span=document.createElement('span');
span.className='et';span.contentEditable='true';span.setAttribute('spellcheck','false');
span.dataset.nid=nid;span.textContent=oChild.textContent;
span.addEventListener('input',()=>{const tn=nodeMap.get(span.dataset.nid);if(tn)tn.textContent=span.textContent;updateSource();markDirty();});
span.addEventListener('keydown',e=>{if(e.key==='Enter')e.preventDefault();});
cChild.parentNode.replaceChild(span,cChild);
} else if(oChild.nodeType===Node.ELEMENT_NODE){
const tag=(oChild.tagName||'').toUpperCase();
if(['SCRIPT','STYLE','NOSCRIPT'].includes(tag))return;
walk(oChild,cChild);
}
});
}
walk(ob,cb);
}
function loadHTML(html,name,handle){
const parser=new DOMParser();originalDoc=parser.parseFromString(html,'text/html');
fileHandle=handle||null;isDirty=false;$('dot').style.display='none';
if(flashTimer)clearTimeout(flashTimer);$('save-btn').classList.remove('flash-green');
const body=originalDoc.querySelector('body');if(!body){setStatus(t('status_no_body'),3000);return;}
$('rendered-wrap').innerHTML='';$('fs-notice').style.display='none';fsNoticeVisible=false;
if(name){$('fname').textContent=name;$('fname').style.display='block';$('status').style.display='none';}
const titleEl=originalDoc.querySelector('title');
if(titleEl){const bar=document.createElement('div');bar.className='title-bar';bar.textContent='<title> '+titleEl.textContent+' </title>';$('rendered-wrap').appendChild(bar);}
originalDoc.querySelectorAll('head style').forEach(s=>$('rendered-wrap').appendChild(s.cloneNode(true)));
const cloneBody=body.cloneNode(true);const wrap=document.createElement('div');
wrap.appendChild(cloneBody);$('rendered-wrap').appendChild(wrap);makeEditable(body,cloneBody);
$('drop-overlay').style.display='none';$('rendered-wrap').style.display='block';
updateSource();$('copy-btn').disabled=false;$('save-btn').disabled=false;
if(!hasFS){
fsNoticeVisible=true;
$('fs-notice').innerHTML=t('fs_notice')+`<br><button class="tb-btn" id="dl-btn" style="margin-top:8px;display:inline-flex"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:inline-block;vertical-align:middle"><path d="M12 5v14m-7-7 7 7 7-7"/><line x1="5" y1="19" x2="19" y2="19"/></svg>${t('btn_dl')}</button>`;
$('fs-notice').style.display='block';const d=$('dl-btn');if(d)d.addEventListener('click',downloadFile);
$('save-hint').textContent=t('hint_fallback');
} else {$('save-hint').textContent=t('hint_save');}
setStatus(t('status_opened'),3000);
}
function downloadFile(){if(!originalDoc)return;const blob=new Blob([getHTML()],{type:'text/html'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='edited.html';a.click();markClean();setStatus(t('downloaded'));}
async function saveFile(){
if(!originalDoc){setStatus(t('status_no_file'));return;}const html=getHTML();
if(fileHandle){try{const w=await fileHandle.createWritable();await w.write(html);await w.close();markClean();setStatus(t('status_saved'));}catch(e){if(e.name!=='AbortError')setStatus('Error: '+e.message,4000);}return;}
if(hasFS){try{const handle=await window.showSaveFilePicker({suggestedName:'edited.html',types:[{description:'HTML',accept:{'text/html':['.html','.htm']}}]});fileHandle=handle;const w=await handle.createWritable();await w.write(html);await w.close();$('fname').textContent=handle.name;markClean();setStatus(t('status_saved'));}catch(e){if(e.name!=='AbortError')setStatus('Error: '+e.message,4000);}return;}
downloadFile();
}
async function openFile(){
if(hasFS){try{const[handle]=await window.showOpenFilePicker({types:[{description:'HTML',accept:{'text/html':['.html','.htm']}}]});const file=await handle.getFile();loadHTML(await file.text(),file.name,handle);}catch(e){if(e.name!=='AbortError')$('file-input').click();}}
else $('file-input').click();
}
$('lang-btn').addEventListener('click',e=>{e.stopPropagation();$('lang-menu').classList.toggle('open');});
document.addEventListener('click',()=>$('lang-menu').classList.remove('open'));
document.querySelectorAll('.lang-opt').forEach(el=>el.addEventListener('click',e=>{e.stopPropagation();currentLang=el.dataset.lang;localStorage.setItem('htmlTypist_lang',currentLang);$('lang-menu').classList.remove('open');applyLang();}));
['open-btn','open-btn2'].forEach(id=>$(id).addEventListener('click',openFile));
['try-btn','try-btn2'].forEach(id=>$(id).addEventListener('click',()=>loadHTML(SAMPLES[currentLang],'sample.html',null)));
$('file-input').addEventListener('change',e=>{const f=e.target.files[0];if(!f)return;const r=new FileReader();r.onload=ev=>loadHTML(ev.target.result,f.name,null);r.readAsText(f,'utf-8');$('file-input').value='';});
$('save-btn').addEventListener('click',saveFile);
$('copy-btn').addEventListener('click',()=>{if(!originalDoc){setStatus(t('status_no_file'));return;}navigator.clipboard.writeText(getHTML()).then(()=>setStatus(t('status_copied'))).catch(()=>setStatus(t('status_copy_fail')));});
document.addEventListener('keydown',e=>{if((e.metaKey||e.ctrlKey)&&e.key==='s'){e.preventDefault();saveFile();}});
$('preview-pane').addEventListener('dragover',e=>{e.preventDefault();$('preview-pane').style.outline='2px dashed #dc2743';$('preview-pane').style.outlineOffset='-4px';});
$('preview-pane').addEventListener('dragleave',()=>{$('preview-pane').style.outline='';});
$('preview-pane').addEventListener('drop',e=>{e.preventDefault();$('preview-pane').style.outline='';const f=e.dataTransfer.files[0];if(!f)return;const r=new FileReader();r.onload=ev=>loadHTML(ev.target.result,f.name,null);r.readAsText(f,'utf-8');});
$('toggle-source').addEventListener('click',()=>{sourcePaneVisible=false;$('source-pane').classList.add('collapsed');$('toggle-icon').style.transform='rotate(180deg)';});
$('source-toggle-bar').addEventListener('click',()=>{sourcePaneVisible=!sourcePaneVisible;$('source-pane').classList.toggle('collapsed',!sourcePaneVisible);$('toggle-icon').style.transform=sourcePaneVisible?'rotate(0deg)':'rotate(180deg)';});
const _sl=localStorage.getItem('htmlTypist_lang');if(_sl&&I18N[_sl])currentLang=_sl;
applyLang();$('status').style.display='block';$('status').textContent=t('status_idle');
</script>
</body>
</html>