Repository navigation
Expand file tree
/
Copy pathelectron-control.html
More file actions
1098 lines (1065 loc) · 101 KB
/
Copy pathelectron-control.html
File metadata and controls
1098 lines (1065 loc) · 101 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
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; media-src 'self' data:; font-src 'self' data:; connect-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-src 'none'; worker-src 'none'">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Управление Таймером</title>
<link rel="stylesheet" href="fonts.css">
<link rel="stylesheet" href="design-tokens.css">
<link rel="stylesheet" href="control.css">
<!-- Тема интерфейса ставится ДО первого кадра, поэтому скрипт в <head>, а не
в конце <body> вместе с остальными: иначе при включённом высоком контрасте
окно успевало бы отрисоваться в тёмной теме и мигнуть. -->
<script src="ui-theme.js"></script>
<!-- Замок «Закрепить положение» — рядом с темой и по той же причине: обе
величины общие для всех окон и обе обязаны быть применены ДО первого
кадра, иначе окно успевает показать чужое состояние. -->
<script src="ui-lock.js"></script>
<script src="theme-init.js"></script>
</head>
<body class="control-window state-idle">
<div class="app-shell">
<div class="control-panel">
<!-- Кастомная панель заголовка (macOS style) -->
<div class="custom-titlebar">
<div class="window-controls">
<button class="win-btn close-btn" id="closeAppBtn" data-glyph="×" title="Закрыть" aria-label="Закрыть"></button>
<button class="win-btn minimize-btn" id="minimizeBtn" data-glyph="−" title="Свернуть" aria-label="Свернуть"></button>
</div>
<span class="title-text"><svg class="title-glyph" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="9" r="5.2"/><path d="M8 6.4V9l1.8 1.1"/><path d="M6.2 2h3.6"/></svg>ТАЙМЕР</span>
<div class="titlebar-right">
<!-- Переключатель темы живёт в титлбаре, а не во вкладках настроек:
вкладки настраивают КОНКРЕТНОЕ окно (виджет, часы, дисплей), а
тема общая для всего приложения. -->
<button class="titlebar-help" id="miniBarToggle" type="button" aria-expanded="true" title="Свернуть в полосу (M)" aria-label="Свернуть в полосу">▾</button>
<button class="titlebar-help" id="contrastToggle" type="button" aria-pressed="false" title="Высокий контраст" aria-label="Высокий контраст">◐</button>
<!-- Замок — рядом с темой: величина общая для ВСЕХ окон. -->
<button class="titlebar-help" id="lockToggle" type="button" aria-pressed="false" title="Закрепить положение" aria-label="Закрепить положение" data-glyph="unlocked"><svg class="lock-glyph" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="7.25" width="9" height="6.5" rx="1.5"/><path class="lock-shackle-closed" d="M5.5 7.25V5a2.5 2.5 0 0 1 5 0v2.25"/><path class="lock-shackle-open" d="M5.5 7.25V5a2.5 2.5 0 0 1 4.85-.85"/></svg></button>
<button class="titlebar-help" id="faqBtn" title="Справка (F1)" aria-label="Справка">?</button>
</div>
</div>
<!-- Полоса: ВТОРОЕ СОСТОЯНИЕ этого окна, а не отдельное окно и не
второй таймер. Время и точку сюда пишет тот же updateDisplay(),
что и в герой-блок; своего источника времени у полосы нет. -->
<div class="mini-bar" id="miniBar">
<span class="mini-dot" id="miniBarDot" aria-hidden="true"></span>
<span class="mini-time" id="miniBarTime">00:00</span>
<span class="mini-spacer"></span>
<!-- Пресеты вида и замок — В ПОЛОСЕ, потому что полоса и есть режим
«всё настроено, идёт мероприятие»: именно там нужно быстро
переключить вид и запретить случайные сдвиги, не разворачивая
окно. Кнопки те же самые (те же id-префиксы и та же привязка),
второго владельца состояния нет — см. panel-presets.js и
panel-titlebar.js. -->
<span class="mini-presets" role="group" aria-label="Пресеты вида">
<button class="preset-slot mini-preset" id="miniPresetSlot1" type="button">1</button>
<button class="preset-slot mini-preset" id="miniPresetSlot2" type="button">2</button>
<button class="preset-slot mini-preset" id="miniPresetSlot3" type="button">3</button>
<button class="preset-slot mini-preset" id="miniPresetSlot4" type="button">4</button>
</span>
<button class="mini-btn" id="miniBarLock" type="button" aria-pressed="false" title="Закрепить положение" aria-label="Закрепить положение" data-glyph="unlocked"><svg class="lock-glyph" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="7.25" width="9" height="6.5" rx="1.5"/><path class="lock-shackle-closed" d="M5.5 7.25V5a2.5 2.5 0 0 1 5 0v2.25"/><path class="lock-shackle-open" d="M5.5 7.25V5a2.5 2.5 0 0 1 4.85-.85"/></svg></button>
<span class="mini-divider"></span>
<!-- Действие названо СЛОВОМ, а не глифом. Кнопок по-прежнему две
(за ними висят разные обработчики), на экране всегда одна:
выбирает то же состояние, что и в развёрнутой панели. -->
<button class="mini-action" id="miniBarPause" title="Пауза (Space)" aria-label="Приостановить">Пауза</button>
<button class="mini-action" id="miniBarStart" title="Старт (Space)" aria-label="Запустить таймер">Старт</button>
<button class="mini-btn" id="miniBarReset" title="Сброс (R)" aria-label="Сбросить">↺</button>
<button class="mini-btn" id="miniBarExpand" title="Развернуть (M)" aria-label="Развернуть панель">▴</button>
</div>
<!-- ГЕРОЙ. Порядок строк — из макета редизайна: подпись состояния СВЕРХУ
(в ней же точка), под ней цифры, под цифрами подсказка. Раньше точка
и подпись стояли ПОД цифрами; «Осталось» над отрицательным числом
читается неверно, и состояние теперь объявляется до числа. -->
<div class="hero" id="hero">
<div class="hero-label" id="controlHeroLabel">
<span class="status-dot" id="statusDot"></span>
<span id="statusText">Длительность</span>
</div>
<div class="timer-display-main" id="controlTime" title="Нажмите, чтобы ввести своё время"><span class="tm-sign" id="controlTimeSign"></span><span class="tm-digits" id="controlTimeDigits">00:00</span></div>
<!-- ТРИ поля, а не одна слепленная строка. «1:30:00» требует
помнить формат и попадать курсором в нужную позицию; поля сами
говорят, что куда, и каждое принимает просто число.
Часы появились сразу за минутами и секундами: без них длинный
таймер набирался как «90 минут», то есть в уме. -->
<div class="time-entry" id="timeEntry" role="group" aria-label="Ввод времени">
<label class="time-field">
<input type="text" id="manualHours" inputmode="numeric" maxlength="2"
autocomplete="off" spellcheck="false" aria-label="Часы" placeholder="0">
<span class="time-field-label">час</span>
</label>
<span class="time-entry-sep" aria-hidden="true">:</span>
<label class="time-field">
<input type="text" id="manualMinutes" inputmode="numeric" maxlength="2"
autocomplete="off" spellcheck="false" aria-label="Минуты" placeholder="00">
<span class="time-field-label">мин</span>
</label>
<span class="time-entry-sep" aria-hidden="true">:</span>
<label class="time-field">
<input type="text" id="manualSeconds" inputmode="numeric" maxlength="2"
autocomplete="off" spellcheck="false" aria-label="Секунды" placeholder="00">
<span class="time-field-label">сек</span>
</label>
</div>
<div class="hero-hint" id="heroHint">нажмите на время, чтобы ввести своё</div>
</div>
<!-- Прогресс полосой, а не кольцом. Во всю ширину панели, без радиуса. -->
<div class="panel-progress" id="panelProgress" role="presentation">
<span class="panel-progress-fill" id="panelProgressFill"></span>
</div>
<!-- ПРЕСЕТЫ: пять ячеек в ряд. Пятая — «мин», она не длительность, а
вход в ручной ввод. Видны только в покое. -->
<div class="presets" id="presetsRow">
<button class="preset" data-minutes="5">5</button>
<button class="preset" data-minutes="15">15</button>
<button class="preset" data-minutes="25">25</button>
<button class="preset" data-minutes="45">45</button>
<button class="preset preset-custom" id="presetCustom" title="Своё время">мин</button>
</div>
<!-- ТРАНСПОРТ. Действие названо СЛОВОМ с подсказкой клавиши. Кнопок в
разметке две, на экране всегда одна: состояние выбирает, какая. -->
<div class="transport">
<button class="transport-main start" id="startBtn" title="Запустить таймер (Space)" aria-label="Запустить таймер">Старт<span class="transport-key" aria-hidden="true">Space</span></button>
<button class="transport-main pause" id="pauseBtn" title="Приостановить (Space)" aria-label="Приостановить">Пауза<span class="transport-key" aria-hidden="true">Space</span></button>
<button class="transport-reset" id="resetBtn" title="Сбросить к начальному значению (R)" aria-label="Сбросить">↺</button>
<button class="transport-cancel" id="manualCancel" title="Отменить ввод (Esc)" aria-label="Отменить ввод">Отмена</button>
</div>
<!-- РЯД ±: четыре кнопки, залитые и БЕЗ рамки. Появляется только там,
где нужен, — в отсчёте и при ручном вводе. -->
<div class="adjust" id="adjustRow">
<button class="adjust-btn" data-adjust="-300">−5</button>
<button class="adjust-btn" data-adjust="-60">−1</button>
<button class="adjust-btn" data-adjust="60">+1</button>
<button class="adjust-btn" data-adjust="300">+5</button>
</div>
<!-- Две настройки отсчёта живут при ВВОДЕ: они про то, каким будет
таймер, а не про то, что показывать на экране. -->
<div class="input-extras" id="inputExtras">
<div class="srow">
<span class="srow-text">
<span class="srow-name">Считать ниже нуля</span>
<span class="srow-sub">после нуля таймер уходит в минус</span>
</span>
<!-- aria-label обязателен: видимая подпись лежит СОСЕДОМ, а не
внутри <label>, поэтому имя чекбоксу больше ничем не даётся.
tests/faq-and-hidden-controls.test.js проверяет это по всей
панели и поймал ровно этот случай. -->
<label class="switch" title="Таймер продолжит отсчёт в минус после окончания">
<input type="checkbox" id="allowNegative" aria-label="Считать ниже нуля">
<span class="switch-track"></span>
</label>
</div>
<div class="srow" id="overrunRow">
<span class="srow-text">
<span class="srow-name">Остановить на</span>
<span class="srow-sub" id="overrunLimitHint">без лимита</span>
</span>
<input type="text" id="overrunLimit" class="srow-input" value="00:00"
autocomplete="off" spellcheck="false" aria-label="Лимит перерасхода"
title="Остановить таймер, уйдя в минус на столько: 90 = 90 сек, 5:00 = 5 мин. 00:00 — без лимита">
</div>
</div>
<!-- ПОКАЗАТЬ НА ЭКРАНЕ. Строка окна заменила и плитку «открыть», и
вкладку настроек: тумблер открывает окно, шеврон — его настройки.
Поэтому id кнопок и data-tab сохранены — за ними висят обработчики
открытия окон и ящика настроек. -->
<div class="group-title">Показать на экране<span class="group-note">Esc окон не гасит</span></div>
<div class="wrows">
<div class="wrow">
<span class="wrow-icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="9" r="5.2"/><path d="M8 6.4V9l1.8 1.1"/><path d="M6.2 2h3.6"/></svg></span>
<span class="wrow-text"><span class="wrow-head"><span class="wrow-name">Виджет</span><span class="wrow-key"><kbd>W</kbd> — открыть/закрыть</span></span><span class="wrow-sub" id="subWidget">маленький таймер поверх окон</span></span>
<button class="wrow-chevron tab-btn" data-tab="timer" aria-controls="settingsDrawer" aria-expanded="false" title="Настройки виджета" aria-label="Настройки виджета">›</button>
<button class="wrow-toggle quick-window-btn widget" id="openWidgetBtn" role="switch" aria-checked="false" title="Компактный таймер поверх окон (W)" aria-label="Виджет"><span class="wrow-knob" aria-hidden="true"></span></button>
</div>
<div class="wrow">
<span class="wrow-icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M8 4.4V8l2.6 1.6"/></svg></span>
<span class="wrow-text"><span class="wrow-head"><span class="wrow-name">Часы</span><span class="wrow-key"><kbd>C</kbd> — открыть/закрыть</span></span><span class="wrow-sub" id="subClock">текущее время и дата</span></span>
<button class="wrow-chevron tab-btn" data-tab="clock" aria-controls="settingsDrawer" aria-expanded="false" title="Настройки часов" aria-label="Настройки часов">›</button>
<button class="wrow-toggle quick-window-btn clock" id="openClockBtn" role="switch" aria-checked="false" title="Часы в реальном времени (C)" aria-label="Часы"><span class="wrow-knob" aria-hidden="true"></span></button>
</div>
<div class="wrow">
<span class="wrow-icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1.8" y="2.6" width="12.4" height="8.4" rx="1.4"/><path d="M5.6 13.4h4.8"/><path d="M8 11v2.4"/></svg></span>
<span class="wrow-text"><span class="wrow-head"><span class="wrow-name">Дисплей</span><span class="wrow-key"><kbd>D</kbd> — открыть/закрыть</span></span><span class="wrow-sub" id="subDisplay">во весь экран, для проектора</span></span>
<button class="wrow-chevron tab-btn" data-tab="display" aria-controls="settingsDrawer" aria-expanded="false" title="Настройки дисплея" aria-label="Настройки дисплея">›</button>
<button class="wrow-toggle quick-window-btn display" id="openDisplayBtn" role="switch" aria-checked="false" title="Полноэкранный режим для презентаций (D)" aria-label="Дисплей"><span class="wrow-knob" aria-hidden="true"></span></button>
</div>
<!-- Звуки — не окно, но раздел тех же настроек, и вход к ним обязан
быть виден. Тумблер здесь означает звук вкл/выкл. -->
<div class="wrow">
<span class="wrow-icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8.4 3.2 5.4 5.8H2.8v4.4h2.6l3 2.6z"/><path d="M11 6.2a2.6 2.6 0 0 1 0 3.6"/></svg></span>
<span class="wrow-text"><span class="wrow-head"><span class="wrow-name">Звуки</span><span class="wrow-key"><kbd>Z</kbd> — вкл/выкл</span></span><span class="wrow-sub" id="subSound">окончание</span></span>
<button class="wrow-chevron tab-btn" data-tab="sound" aria-controls="settingsDrawer" aria-expanded="false" title="Настройки звука" aria-label="Настройки звука">›</button>
<button class="wrow-toggle" id="soundMasterToggle" role="switch" aria-checked="true" title="Звук включён" aria-label="Звук"><span class="wrow-knob" aria-hidden="true"></span></button>
</div>
</div>
<!-- ВИД ЦЕЛИКОМ в четырёх ячейках. Клик применяет, Shift+клик пишет; почему одна кнопка на два действия — в panel-presets.js. -->
<div class="preset-row" role="group" aria-label="Пресеты вида"><span class="preset-caption" id="presetCaption" title="В ячейке — весь вид: стили окон, блоки дисплея и их места, раскладка, цвета, шрифты и масштабы">Вид</span><button class="preset-slot" id="presetSlot1" type="button">1</button><button class="preset-slot" id="presetSlot2" type="button">2</button><button class="preset-slot" id="presetSlot3" type="button">3</button><button class="preset-slot" id="presetSlot4" type="button">4</button><span class="preset-hint">Клик по пустой ячейке — запомнить вид, по заполненной — применить. Shift+клик — заменить, Ctrl+1–4 — то же с клавиатуры</span></div>
<!-- Подсказка клавиш моноширинным, по центру — последняя строка панели. -->
<div class="panel-footer" id="panelFooter">Space — старт · R — сброс · 1–4 — пресеты</div>
<!-- Настройки (всегда видны) -->
<div class="advanced-settings open" id="advancedSettings">
<div class="content-section">
<!-- Таб: Виджет -->
<div class="tab-content active" id="tab-timer">
<div class="settings-hint" role="note">
<span class="hint-icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5.6 10.2a4.2 4.2 0 1 1 4.8 0v1.3H5.6z"/><path d="M6.4 13.4h3.2"/></svg></span>
<span class="hint-text">На виджете: <span class="kbd-combo"><kbd>Ctrl</kbd>+колесо</span> — масштаб (30–600%) · Перетаскивание мышью — перемещение окна · <span class="kbd-combo"><kbd>Space</kbd>/<kbd>R</kbd>/<kbd>1</kbd>–<kbd>4</kbd></span> работают и здесь · <kbd>W</kbd> — открыть/закрыть виджет <span class="kbd-combo">(<kbd>Esc</kbd></span> окон не гасит)</span>
</div>
<div class="settings-group">
<div class="settings-group-title">
<span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="4"/><rect x="7.5" y="7.5" width="6.5" height="6.5" rx="1.5"/></svg></span>
Стиль
</div>
<!-- .setting-block, а не .toggle-row: пять сегментов не
помещаются в правую половину flex-строки ни при какой
ширине ящика. Замер до правки: подпись 30px при
интерлиньяже 15px, то есть ровно две строки. Тот же
приём уже применён к «Шрифту цифр» ниже. -->
<div class="setting-block">
<span class="toggle-label">Стиль таймера</span>
<div class="segmented" id="timerStyle" role="radiogroup" aria-label="Стиль виджета" data-value="circle">
<button type="button" data-val="circle" class="active">Круг</button>
<button type="button" data-val="flip">Флип</button>
<button type="button" data-val="analog">Аналог</button>
<button type="button" data-val="digits">Цифры</button>
</div>
</div>
<!-- Видна только при стиле «Цифры» (начертание у остальных
четырёх — часть стиля). Блок, не .toggle-row: список
раскрыт постоянно (6 строк с образцами), в правую
половину flex-строки не помещается; раскрытый список
вместо попапа — ещё один слой поверх и так слоёного Esc
в этом окне это лишний способ его сломать. id — на
РЕАЛЬНОЙ обёртке контрола (см. урок #clockStyleRow). -->
<div class="setting-block" id="widgetDigitsFontRow">
<span class="toggle-label">Шрифт цифр</span>
<div class="font-select" id="widgetDigitsFont" role="listbox"
aria-label="Шрифт цифр виджета" data-value="inter"></div>
</div>
<div class="toggle-row">
<span class="toggle-label">Масштаб</span>
<div class="scale-control">
<input type="range" id="timerScale" class="scale-slider" min="30" max="600" value="100" aria-label="Масштаб таймера">
<span id="timerScaleValue" class="scale-value-text">100%</span>
</div>
</div>
<!-- По умолчанию ВЫКЛЮЧЕНА: состояние уже несёт цвет дуги
(зелёный идёт, оранжевый последние минуты, красный
перерасход), а на маленьком виджете слово занимает
место, которого нет. Тумблер для тех, кому слово
нужнее места. -->
<div class="toggle-row" id="widgetStatusRow">
<label class="toggle-label" for="widgetStatusLabel">Подпись состояния</label>
<label class="toggle-switch" title="Показывать «идёт», «пауза», «перерасход» под цифрами виджета">
<input type="checkbox" id="widgetStatusLabel">
<span class="toggle-slider"></span>
</label>
</div>
<!-- Уровень окна раньше был прибит в главном процессе и
пользователю не подчинялся. Выключение опускает виджет
в обычный слой — он перестаёт закрывать собой
полноэкранные приложения. -->
<div class="toggle-row">
<label class="toggle-label" for="widgetAlwaysOnTop">Поверх всех окон</label>
<label class="toggle-switch" title="Виджет не прячется за другие приложения">
<input type="checkbox" id="widgetAlwaysOnTop" checked>
<span class="toggle-slider"></span>
</label>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">
<span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2.2s4 4.3 4 6.9a4 4 0 0 1-8 0c0-2.6 4-6.9 4-6.9z"/></svg></span>
Цвета
</div>
<div class="themes-grid" id="themesGrid"></div>
<div class="color-picker-panel" id="widgetColorPickerPanel">
<canvas class="cp-sv-canvas" id="widgetCpSv"></canvas>
<canvas class="cp-hue-slider" id="widgetCpHue"></canvas>
<div class="cp-hex-row">
<div class="cp-preview" id="widgetCpPreview"></div>
<input type="text" class="cp-hex-input" id="widgetCpHex" placeholder="#ffffff" maxlength="7" aria-label="HEX-код цвета виджета">
</div>
</div>
<!-- Точка монтирования: сам ряд «Фон» строит panel-colors.js. -->
<div id="widgetSurfaceRow"></div>
<div id="widgetResetRow"></div>
</div>
</div>
<!-- Таб: Звуки -->
<div class="tab-content" id="tab-sound">
<div class="settings-column">
<div class="settings-group" id="soundSettings">
<div class="settings-group-title">
<span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 6.2h2.3L8 3.5v9L4.8 9.8H2.5z"/><path d="M10.8 6a2.8 2.8 0 0 1 0 4"/></svg></span>
Звуки
<label class="toggle-switch">
<input type="checkbox" id="soundMasterEnabled" checked aria-label="Все звуки">
<span class="toggle-slider"></span>
</label>
</div>
<div class="sound-list">
<div class="sound-item" data-kind="start">
<!-- Переключатель, а не чекбокс: разбор в control.css у .sound-item .sound-toggle -->
<label class="toggle-switch sound-toggle" title="Включить звук старта">
<input type="checkbox" id="soundStartEnabled" aria-label="Звук старта включён">
<span class="toggle-slider"></span>
</label>
<span class="sound-name">Старт</span>
<div class="select-wrap">
<select class="sound-select" id="soundStartPreset" aria-label="Звук старта">
<!-- Список строит sound-presets.js: набор один на четыре события -->
</select>
</div>
<button class="sound-preview" id="soundStartPreview" title="Прослушать" aria-label="Прослушать">▶</button>
</div>
<div class="sound-item" data-kind="end">
<label class="toggle-switch sound-toggle" title="Включить звук окончания">
<input type="checkbox" id="soundEndEnabled" checked aria-label="Звук окончания включён">
<span class="toggle-slider"></span>
</label>
<span class="sound-name">Окончание</span>
<div class="select-wrap">
<select class="sound-select" id="soundEndPreset" aria-label="Звук окончания">
<!-- Список строит sound-presets.js: набор один на четыре события -->
</select>
</div>
<button class="sound-preview" id="soundEndPreview" title="Прослушать" aria-label="Прослушать">▶</button>
</div>
<div class="sound-item" data-kind="minute">
<label class="toggle-switch sound-toggle" title="Звук за минуту до конца">
<input type="checkbox" id="soundMinuteEnabled" aria-label="Звук за минуту до конца включён">
<span class="toggle-slider"></span>
</label>
<span class="sound-name">Минута</span>
<div class="select-wrap">
<select class="sound-select" id="soundMinutePreset" aria-label="Звук за минуту до конца">
<!-- Список строит sound-presets.js: набор один на четыре события -->
</select>
</div>
<button class="sound-preview" id="soundMinutePreview" title="Прослушать" aria-label="Прослушать">▶</button>
</div>
<div class="sound-item" data-kind="overrun">
<label class="toggle-switch sound-toggle" title="Звук перерасхода">
<input type="checkbox" id="soundOverrunEnabled" aria-label="Звук перерасхода включён">
<span class="toggle-slider"></span>
</label>
<span class="sound-name">Перерасход</span>
<div class="select-wrap">
<select class="sound-select" id="soundOverrunPreset" aria-label="Звук овертайма">
<!-- Список строит sound-presets.js: набор один на четыре события -->
</select>
</div>
<button class="sound-preview" id="soundOverrunPreview" title="Прослушать" aria-label="Прослушать">▶</button>
</div>
<div class="sound-item sound-sub-item">
<span class="sound-sub-name">повторять каждые</span>
<div class="select-wrap">
<select class="sound-select" id="overrunIntervalMinutes" aria-label="Интервал сигналов овертайма в минутах">
<option value="1">1 мин</option>
<option value="2">2 мин</option>
<option value="3">3 мин</option>
<option value="5">5 мин</option>
<option value="10">10 мин</option>
</select>
</div>
<span class="sound-preview-placeholder" aria-hidden="true"></span>
</div>
</div>
</div>
</div>
<div class="settings-column">
<div class="settings-group">
<div class="settings-group-title">
<span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 12V4l7-1.4v8"/><circle cx="4.3" cy="12" r="1.8"/><circle cx="11.3" cy="10.6" r="1.8"/></svg></span>
Ваши звуки
<span class="sound-formats">mp3, wav, ogg, aac, flac · до 5 МБ</span>
</div>
<div class="custom-sound-list" id="customSoundList"></div>
<div class="sound-err" id="soundUploadError">
<div class="err-icon">!</div>
<div class="err-body">
<div class="err-title" id="soundErrorTitle">Ошибка загрузки</div>
<div class="err-msg" id="soundErrorMsg"></div>
</div>
</div>
<div class="add-sound-row">
<button class="add-sound-btn" id="addSoundBtn" title="Загрузить аудиофайл с компьютера">
<span class="drop-icon" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
</span>
<span class="drop-title">Перетащите файл сюда</span>
<span class="drop-sub">или нажмите для выбора</span>
</button>
<input type="file" class="hidden-file-input" id="soundFileInput"
accept="audio/mp3,audio/wav,audio/ogg,audio/mpeg,audio/webm,audio/aac,audio/flac,.mp3,.wav,.ogg,.webm,.aac,.flac">
</div>
</div>
</div>
</div>
<!-- Таб: Часы -->
<div class="tab-content" id="tab-clock">
<div class="settings-column">
<div class="settings-hint" role="note">
<span class="hint-icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5.6 10.2a4.2 4.2 0 1 1 4.8 0v1.3H5.6z"/><path d="M6.4 13.4h3.2"/></svg></span>
<span class="hint-text">На часах: <span class="kbd-combo"><kbd>Ctrl</kbd>+колесо</span> — масштаб (30–600%) · Перетаскивание мышью — перемещение окна · <span class="kbd-combo"><kbd>Space</kbd>/<kbd>R</kbd>/<kbd>1</kbd>–<kbd>4</kbd></span> работают и здесь · <kbd>C</kbd> — открыть/закрыть часы <span class="kbd-combo">(<kbd>Esc</kbd></span> окон не гасит)</span>
</div>
<div class="settings-group">
<div class="settings-group-title">
<span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="4"/><rect x="7.5" y="7.5" width="6.5" height="6.5" rx="1.5"/></svg></span>
Стиль
</div>
<!-- Чекбокс синхронизации ОБЯЗАН быть видимым. Логику
«assignment .value не порождает change» починили в
проходе 4 (см. CLAUDE.md), но сам контрол остался
в блоке display:none — то есть фича продолжала быть
недостижимой мышью, а e2e ставил .checked кодом и
поэтому был зелёным. Тест на логику не доказывает,
что до функции можно дотянуться. -->
<div class="toggle-row">
<label class="toggle-label" for="syncClockStyle">Как у виджета</label>
<label class="toggle-switch" title="Часы следуют за стилем виджета таймера">
<input type="checkbox" id="syncClockStyle">
<span class="toggle-slider"></span>
</label>
</div>
<!-- Ряд виден ВСЕГДА. При включённой синхронизации он
показывает действующий стиль (стиль виджета), а клик
по нему означает «хочу свой» и снимает синхронизацию.
Прежде ряд прятался — и вместе с ним исчезал ответ на
вопрос, почему часы поехали за виджетом. -->
<div class="setting-block" id="clockStyleRow">
<span class="toggle-label">Стиль</span>
<div class="segmented" id="clockStyle" role="radiogroup" aria-label="Стиль часов" data-value="circle">
<button type="button" data-val="circle" class="active">Круг</button>
<button type="button" data-val="flip">Флип</button>
<button type="button" data-val="analog">Аналог</button>
<button type="button" data-val="digits">Цифры</button>
</div>
</div>
<!-- Видна только при «Цифрах» — см. #widgetDigitsFontRow выше. -->
<div class="setting-block" id="clockDigitsFontRow">
<span class="toggle-label">Шрифт цифр</span>
<div class="font-select" id="clockDigitsFont" role="listbox"
aria-label="Шрифт цифр часов" data-value="inter"></div>
</div>
<div class="toggle-row">
<span class="toggle-label">Масштаб</span>
<div class="scale-control">
<input type="range" id="clockScale" class="scale-slider" min="30" max="600" value="100" aria-label="Масштаб часов">
<span id="clockScaleValue" class="scale-value-text">100%</span>
</div>
</div>
<!-- Деления круглого циферблата ЧАСОВ. У виджета таймера их
больше нет: там это кольцо обратного отсчёта, и засечки
на нём только шумели. -->
<div class="toggle-row" id="clockTicksRow">
<label class="toggle-label" for="clockShowTicks">Деления на циферблате</label>
<label class="toggle-switch" title="Показывать деления на круглом циферблате часов">
<input type="checkbox" id="clockShowTicks">
<span class="toggle-slider"></span>
</label>
</div>
<div class="toggle-row" id="clockAnalogNumbersRow">
<label class="toggle-label" for="clockShowAnalogNumbers">Цифры на циферблате</label>
<label class="toggle-switch">
<input type="checkbox" id="clockShowAnalogNumbers">
<span class="toggle-slider"></span>
</label>
</div>
</div>
</div>
<div class="settings-column">
<div class="settings-group">
<div class="settings-group-title">
<span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2.2s4 4.3 4 6.9a4 4 0 0 1-8 0c0-2.6 4-6.9 4-6.9z"/></svg></span>
Цвета
</div>
<div class="themes-grid" id="clockThemesGrid"></div>
<div class="color-picker-panel" id="clockColorPickerPanel">
<canvas class="cp-sv-canvas" id="clockCpSv"></canvas>
<canvas class="cp-hue-slider" id="clockCpHue"></canvas>
<div class="cp-hex-row">
<div class="cp-preview" id="clockCpPreview"></div>
<input type="text" class="cp-hex-input" id="clockCpHex" placeholder="#ffffff" maxlength="7" aria-label="HEX-код цвета часов">
</div>
</div>
<!-- Фон и сбросы строит panel-colors.js: разметка (свотч,
вторая пипетка, ползунок прозрачности) собирается в
модуле, здесь — только точка монтирования. -->
<div id="clockSurfaceRow"></div>
<div id="clockResetRow"></div>
</div>
<div class="settings-group">
<div class="settings-group-title">
<span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h7"/></svg></span>
Отображение
</div>
<!-- Все четыре настройки окно часов реально исполняет:
_onClockSettings применяет showSeconds/format24h/
showTimezone, бейджи есть во всех четырёх стилях, и
раскладка под них подстраивается. Контролов не было —
инпуты лежали в блоке display:none «kept for JS
compatibility». Обвязка при этом полная: listener →
pushClockSettings() → localStorage → окно. -->
<div class="toggle-row">
<label class="toggle-label" for="clockShowSeconds">Секунды</label>
<label class="toggle-switch" title="Показывать секунды">
<input type="checkbox" id="clockShowSeconds" checked>
<span class="toggle-slider"></span>
</label>
</div>
<div class="toggle-row">
<label class="toggle-label" for="clockFormat24h">24-часовой формат</label>
<label class="toggle-switch" title="Выключено — 12 часов с AM/PM">
<input type="checkbox" id="clockFormat24h" checked>
<span class="toggle-slider"></span>
</label>
</div>
<div class="toggle-row">
<label class="toggle-label" for="clockShowDate">Дата</label>
<label class="toggle-switch" title="Показывать дату под временем">
<input type="checkbox" id="clockShowDate">
<span class="toggle-slider"></span>
</label>
</div>
<div class="toggle-row">
<label class="toggle-label" for="clockShowTimezone">Часовой пояс</label>
<label class="toggle-switch" title="Показывать часовой пояс">
<input type="checkbox" id="clockShowTimezone">
<span class="toggle-slider"></span>
</label>
</div>
</div>
</div>
</div>
<!-- Таб: Полноэкранный -->
<div class="tab-content" id="tab-display">
<div class="settings-column">
<div class="settings-group">
<div class="settings-group-title">
<span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="4"/><rect x="7.5" y="7.5" width="6.5" height="6.5" rx="1.5"/></svg></span>
Стиль таймера
</div>
<!-- Подсказка стоит ПЕРВОЙ в настройках дисплея, а не в конце
группы «Блоки времени»: она описывает жесты, которыми
пользуются В САМОМ ОКНЕ, и прочесть её надо до того, как
полез крутить колесо, а не после. -->
<div class="settings-hint" role="note">
<span class="hint-icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5.6 10.2a4.2 4.2 0 1 1 4.8 0v1.3H5.6z"/><path d="M6.4 13.4h3.2"/></svg></span>
<span class="hint-text">На дисплее: <span class="kbd-combo"><kbd>Ctrl</kbd>+колесо</span> над элементом — масштаб ЭТОГО элемента · <span class="kbd-combo"><kbd>Ctrl</kbd>+колесо</span> мимо всех — масштаб таймера · <span class="kbd-combo"><kbd>Shift</kbd>+колесо</span> — все блоки сразу · <kbd>Alt</kbd> — перетащить блок ИЛИ САМ ТАЙМЕР, крестиком — убрать блок · <span class="kbd-combo"><kbd>1</kbd>–<kbd>4</kbd></span> — пресеты времени, <span class="kbd-combo"><kbd>Ctrl</kbd>+<kbd>1</kbd>–<kbd>4</kbd></span> — пресеты вида, <kbd>Space</kbd> — старт/пауза, <kbd>R</kbd> — сброс · <kbd>D</kbd> — открыть/закрыть дисплей <span class="kbd-combo">(<kbd>Esc</kbd></span> окон не гасит)</span>
</div>
<!-- .setting-block по той же причине, что и у виджета. -->
<div class="setting-block">
<span class="toggle-label">Стиль</span>
<div class="segmented" id="displayTimerStyle" role="radiogroup" aria-label="Стиль таймера дисплея" data-value="circle">
<button type="button" data-val="circle" class="active">Круг</button>
<button type="button" data-val="flip">Флип</button>
<button type="button" data-val="analog">Аналог</button>
<button type="button" data-val="digits">Цифры</button>
</div>
</div>
<!-- Видна только при «Цифрах» — см. #widgetDigitsFontRow выше. -->
<div class="setting-block" id="displayDigitsFontRow">
<span class="toggle-label">Шрифт цифр</span>
<div class="font-select" id="displayDigitsFont" role="listbox"
aria-label="Шрифт цифр дисплея" data-value="inter"></div>
</div>
<div class="toggle-row">
<span class="toggle-label">Масштаб</span>
<div class="scale-control">
<input type="range" id="displayTimerScale" class="scale-slider" min="30" max="300" value="100" aria-label="Масштаб таймера дисплея">
<span id="displayTimerScaleValue" class="scale-value-text">100%</span>
</div>
</div>
<!-- Кнопки раскладок строит panel-display.js по реестру
display-layouts.js: перечень раскладок обязан жить в
ОДНОМ месте, здесь только точка монтирования. -->
<div class="setting-block">
<span class="toggle-label">Раскладка</span>
<div class="layout-grid" id="displayLayoutGrid"></div>
</div>
<div class="settings-divider"></div>
<div class="toggle-row">
<span class="toggle-label">Монитор</span>
<select id="displaySelect" class="settings-select" aria-label="Выбор монитора">
<option value="auto">Авто</option>
</select>
</div>
<div class="toggle-row">
<label class="toggle-label" for="showHeroLabel">Подпись над таймером</label>
<label class="toggle-switch">
<input type="checkbox" id="showHeroLabel" checked>
<span class="toggle-slider"></span>
</label>
</div>
<div class="toggle-row">
<label class="toggle-label" for="showStatusPill">Плашка состояния</label>
<label class="toggle-switch">
<input type="checkbox" id="showStatusPill" checked>
<span class="toggle-slider"></span>
</label>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">
<span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h7"/></svg></span>
Блоки времени
</div>
<div class="toggle-row">
<label class="toggle-label" for="showCurrentTime">Текущее время</label>
<label class="toggle-switch">
<input type="checkbox" id="showCurrentTime">
<span class="toggle-slider"></span>
</label>
</div>
<div class="toggle-row">
<label class="toggle-label" for="showEventTime">Начало</label>
<label class="toggle-switch">
<input type="checkbox" id="showEventTime">
<span class="toggle-slider"></span>
</label>
</div>
<div class="toggle-row">
<label class="toggle-label" for="showEndTime">Окончание</label>
<label class="toggle-switch">
<input type="checkbox" id="showEndTime">
<span class="toggle-slider"></span>
</label>
</div>
<div class="toggle-row">
<label class="toggle-label" for="showTimeLeft">До завершения</label>
<label class="toggle-switch">
<input type="checkbox" id="showTimeLeft">
<span class="toggle-slider"></span>
</label>
</div>
<div class="toggle-row">
<label class="toggle-label" for="showEventTitle">Название мероприятия</label>
<label class="toggle-switch">
<input type="checkbox" id="showEventTitle">
<span class="toggle-slider"></span>
</label>
</div>
<div id="timeBlocksSettings">
<div class="toggle-row">
<span class="toggle-label">Размер блоков</span>
<div class="scale-control">
<input type="range" id="timeBlocksScale" class="scale-slider" min="50" max="600" value="150" aria-label="Масштаб блоков времени">
<span id="timeBlocksScaleValue" class="scale-value-text">100%</span>
</div>
</div>
<div class="toggle-row">
<span class="toggle-label">Начало</span>
<input type="time" id="eventTimeInput" class="settings-time-input" value="10:00" aria-label="Время начала события">
</div>
<div class="toggle-row">
<span class="toggle-label">Конец</span>
<input type="time" id="endTimeInput" class="settings-time-input" value="12:00" aria-label="Время окончания события">
</div>
<div class="toggle-row">
<label class="toggle-label" for="eventTitleInput">Название</label>
<input type="text" id="eventTitleInput" class="srow-input" maxlength="60"
placeholder="Оперативное совещание" aria-label="Название мероприятия">
</div>
<!-- Ряды строит panel-display.js по реестру display-layouts.js:
вторая копия списка блоков в вёрстке разъехалась бы с ним. -->
<div class="settings-subtitle">Названия плашек</div>
<div id="blockLabelRows"></div>
<!-- Скрытый режим «47-й этаж»: секцию и модалку строит panel-display.js по образцу рядов подписей выше. -->
<div id="floor47Mount"></div>
</div>
</div>
</div>
<div class="settings-column">
<!-- Режим центрального времени (08.09.2026). Ряд кнопок ведёт
себя как выбор фона ниже: владелец значения — heroMode в
панели, кнопки лишь его вид. Поле заголовка одно на четыре
режима — показывается вход того режима, который выбран.
Сырая разметка совпадает с УМОЛЧАНИЕМ (режим «Таймер»):
ряд заголовка скрыт, пояснение про отчёт о состоянии
видно. Обратный порядок давал вспышку не того состояния
в кадре до того, как setHeroMode('timer') отработает. -->
<div class="settings-group" id="heroModeSection">
<div class="settings-subtitle">Центральное время</div>
<div class="hero-mode-tabs" role="radiogroup" aria-label="Что показывает крупное число">
<button class="hero-mode-btn active" type="button" role="radio" aria-checked="true" data-mode="timer">Таймер</button>
<button class="hero-mode-btn" type="button" role="radio" aria-checked="false" data-mode="current">Текущее время</button>
<button class="hero-mode-btn" type="button" role="radio" aria-checked="false" data-mode="to-start">До начала</button>
<button class="hero-mode-btn" type="button" role="radio" aria-checked="false" data-mode="to-end">До конца</button>
</div>
<div class="toggle-row" id="heroLabelRow" hidden>
<div class="toggle-text">
<label class="toggle-label" for="labelHeroCurrent">Заголовок</label>
<div class="toggle-hint" id="heroLabelHint">Пустое поле вернёт стандартный.</div>
</div>
<input type="text" class="srow-input" id="labelHeroCurrent" maxlength="40" hidden>
<input type="text" class="srow-input" id="labelHeroToStart" maxlength="40" hidden>
<input type="text" class="srow-input" id="labelHeroToEnd" maxlength="40" hidden>
</div>
<div class="toggle-hint" id="heroTimerLabelNote">В режиме «Таймер» подпись показывает состояние таймера — «Осталось», «Пауза», «Завершено».</div>
</div>
<div class="settings-group">
<div class="settings-group-title">
<span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="4"/><rect x="7.5" y="7.5" width="6.5" height="6.5" rx="1.5"/></svg></span>
Оформление дисплея
</div>
<div class="settings-subtitle">Цвета таймера</div>
<div class="themes-grid" id="displayThemesGrid"></div>
<div class="color-picker-panel" id="displayColorPickerPanel">
<canvas class="cp-sv-canvas" id="displayCpSv"></canvas>
<canvas class="cp-hue-slider" id="displayCpHue"></canvas>
<div class="cp-hex-row">
<div class="cp-preview" id="displayCpPreview"></div>
<input type="text" class="cp-hex-input" id="displayCpHex" placeholder="#ffffff" maxlength="7" aria-label="HEX-код цвета дисплея">
</div>
</div>
<div id="displayResetRow"></div>
<div class="settings-subtitle">Фон</div>
<div class="bg-mode-tabs">
<button class="bg-mode-btn active" data-mode="theme">По теме</button>
<button class="bg-mode-btn" data-mode="solid">Заливка</button>
<button class="bg-mode-btn" data-mode="gradient">Градиент</button>
<button class="bg-mode-btn" data-mode="local">Файл</button>
</div>
<!-- У режима «По теме» контролов нет и быть не должно:
значения выбирает тема. Но контейнер обязан
существовать — переключение режимов ищет его по
id (`bg{Режим}Controls`), и на отсутствующем
элементе обработчик падал бы молча. -->
<div class="bg-controls active" id="bgThemeControls">
<div class="settings-hint">Светлый холст в светлой теме, тёмный — в тёмной.</div>
</div>
<div class="bg-controls" id="bgSolidControls">
<input type="color" class="color-picker" id="bgSolidColor" value="#0f0c29" aria-label="Цвет сплошной заливки фона">
</div>
<div class="bg-controls" id="bgGradientControls">
<div class="gradient-row">
<input type="color" class="color-picker" id="bgGrad1" value="#0f0c29" aria-label="Первый цвет градиента">
<input type="color" class="color-picker" id="bgGrad2" value="#302b63" aria-label="Второй цвет градиента">
</div>
</div>
<div class="bg-controls" id="bgLocalControls">
<div class="local-bg-section">
<div class="local-bg-preview" id="localBgPreview">
<div class="local-bg-placeholder" id="localBgPlaceholder">
<span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 10.5V2.8"/><path d="M5.2 5.6 8 2.8l2.8 2.8"/><path d="M2.5 10.5v2a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1v-2"/></svg></span>
<span class="text">Нажмите для выбора изображения</span>
</div>
<img id="localBgImage">
<div class="local-bg-actions">
<button class="local-bg-action-btn change" id="changeBgBtn" title="Изменить" aria-label="Изменить"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.2 7A5.2 5.2 0 0 0 4 4.4"/><path d="M2.8 9A5.2 5.2 0 0 0 12 11.6"/><path d="M3.6 1.8v2.8h2.8"/><path d="M12.4 14.2v-2.8H9.6"/></svg></button>
<button class="local-bg-action-btn delete" id="deleteBgBtn" title="Удалить" aria-label="Удалить">✕</button>
</div>
</div>
<input type="file" class="hidden-file-input" id="bgFileInput"
accept="image/jpeg,image/png,image/gif,image/webp,image/bmp,.jpg,.jpeg,.png,.gif,.webp,.bmp">
<div class="bg-fit-options" id="bgFitOptions">
<button class="bg-fit-btn active" data-fit="cover">Заполнить</button>
<button class="bg-fit-btn" data-fit="contain">Вписать</button>
<button class="bg-fit-btn" data-fit="tile">Плитка</button>
</div>
<div class="bg-overlay-row" id="bgOverlayRow">
<label>Затемнение:</label>
<input type="range" id="bgOverlaySlider" min="0" max="80" value="30" aria-label="Прозрачность тёмного слоя фона">
<span class="bg-overlay-value" id="bgOverlayValue">30%</span>
</div>
</div>
</div>
<button class="reset-btn" id="resetStyleBtn">Сбросить фон по умолчанию</button>
</div>
<!-- eventTimeInput and endTimeInput moved to Блоки времени section -->
</div>
</div>
</div>
</div>
</div>
<!-- role="region", а не "dialog": это раскрывающаяся панель, а не модальное
окно. Модальность подразумевает ловушку фокуса и блокировку остального
интерфейса — ящик ничего этого не делает и не должен: панель за ним
остаётся рабочей. Кнопки-табы связаны с ящиком через aria-controls и
сообщают своё состояние через aria-expanded (стандартный паттерн
disclosure). Содержимое закрытого ящика из обхода Tab исключает
`visibility: hidden` в control.css — проверено замером: 90 контролов
внутри, ни один не получает фокус, пока ящик закрыт. -->
<aside class="settings-drawer" id="settingsDrawer" aria-hidden="true" role="region" aria-label="Настройки">
<!-- Шапка макета: «‹ Имя ×». Шеврон назад и крестик делают одно и то же
(закрывают ящик) и это НЕ дубль: «‹» отвечает на «как вернуться к
списку», «×» — на «как это убрать». Обе роли в макете нарисованы. -->
<div class="drawer-head">
<button class="drawer-back" id="drawerBack" type="button" title="Назад к списку (Esc)" aria-label="Назад к списку">‹</button>
<span class="drawer-title" id="drawerTitle">Настройки</span>
<button class="drawer-close" id="drawerClose" type="button" title="Закрыть (Esc)" aria-label="Закрыть">×</button>
</div>
<div class="drawer-body" id="drawerBody"></div>
</aside>
</div>
<!-- FAQ Modal -->
<!-- Диалог подтверждения выхода -->
<div class="exit-modal" id="exitModal" role="dialog" aria-modal="true" aria-labelledby="exitModalTitle">
<div class="exit-dialog">
<h3 id="exitModalTitle">Закрыть приложение?</h3>
<p>Все окна (виджет, часы, дисплей) будут закрыты</p>
<div class="exit-dialog-buttons">
<button class="exit-btn-cancel" id="exitCancel">Отмена</button>
<button class="exit-btn-confirm" id="exitConfirm">Закрыть</button>
</div>
</div>
</div>
<div class="faq-modal" id="faqModal" role="dialog" aria-modal="true" aria-labelledby="faqModalTitle">
<div class="faq-content">
<div class="faq-header">
<h2 id="faqModalTitle">Справка по Timer App</h2>
<button class="faq-close" id="faqClose" aria-label="Закрыть">×</button>
</div>
<div class="faq-body">
<!-- Содержимое сверено с реальным UI 30.07.2026 и с тех пор
сверяется тестом (faq-and-hidden-controls): в аккордеоне все
ответы закрыты, и устаревший текст годами никому не попадался
на глаза. Полный разбор — docs/lessons.md. -->
<div class="faq-section">
<div class="faq-section-title"><span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="9" r="5.2"/><path d="M8 6.4V9l1.8 1.1"/><path d="M6.2 2h3.6"/></svg></span> Основное управление</div>
<div class="faq-item">
<button class="faq-question" type="button" aria-expanded="false">Как установить время?<span class="arrow" aria-hidden="true">⌄</span></button>
<div class="faq-answer">
<ul>
<li><strong>Клавиши 1–4</strong> — четыре пресета времени (5, 15, 25, 45 мин): те же, что кнопки быстрого выбора, и работают из любого окна; <strong>5</strong> — своё время</li>
<li><strong>Пресеты вида</strong> — ряд «Вид» внизу панели: в ячейке весь вид (стили трёх окон, блоки дисплея и их места, раскладка, цвета, шрифты, масштабы, фон). Клик по <strong>пустой</strong> ячейке записывает текущий вид, по <strong>заполненной</strong> — применяет; <strong>Shift+клик</strong> перезаписывает; <strong>Ctrl+1–4</strong> — то же с клавиатуры и из окна дисплея; голые <strong>1–4</strong> остаются пресетами ВРЕМЕНИ. Картинка фона, положение окон и длительность в пресет не входят</li>
<li><strong>Замок «Закрепить положение»</strong> (значок замка в титлбаре) отменяет случайные жесты: перетаскивание блоков и окон, Ctrl/Shift+колесо, крестик на блоке. Настройки из панели работают по-прежнему</li>
<li><strong>Точное время</strong> — одно поле с умным разбором: <code>90</code> — это 90 секунд, <code>5:30</code> — 5 мин 30 с, <code>1:30:00</code> — 1 ч 30 мин. Максимум 99:59:59. Применить — <strong>Enter</strong> или <strong>✓</strong></li>
<li><strong>± на ходу</strong> — кнопки −1 ч / −5 м / −1 м / +1 м / +5 м / +1 ч меняют время даже во время отсчёта</li>
</ul>
</div>
</div>
<div class="faq-item">
<button class="faq-question" type="button" aria-expanded="false">Управление таймером<span class="arrow" aria-hidden="true">⌄</span></button>
<div class="faq-answer">
<ul>
<li><strong>▶ Старт</strong> — запустить (клавиша <strong>Space</strong>)</li>
<li><strong>⏸ Пауза</strong> — приостановить, время сохраняется (<strong>Space</strong> или <strong>S</strong>)</li>
<li><strong>↺ Сброс</strong> — вернуть к исходному значению (<strong>R</strong>)</li>
</ul>
<p>Сброс возвращает к <strong>пресету</strong>, а не к тому, что осталось после правок на ходу: если вы поставили 10 мин, добавили 5 и нажали сброс — станет снова 10 мин.</p>
</div>
</div>
<div class="faq-item">
<button class="faq-question" type="button" aria-expanded="false">Считать ниже нуля<span class="arrow" aria-hidden="true">⌄</span></button>
<div class="faq-answer">
<p>Тумблер в разделе <strong>Точная настройка</strong>. Таймер не остановится на нуле, а продолжит отсчёт в минус (например, −00:15).</p>
<ul>
<li>Звук окончания срабатывает ровно на нуле, независимо от этого режима</li>
<li>Когда режим включён, появляется поле <strong>Стоп на</strong> — жёсткий лимит перерасхода. <code>00:00</code> означает «без лимита»</li>
<li>Перерасход во всех окнах красный и пульсирует — так он отличается от просто завершённого таймера</li>
</ul>
</div>
</div>
</div>
<div class="faq-section">
<div class="faq-section-title"><span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1.8" y="2.6" width="12.4" height="8.4" rx="1.4"/><path d="M5.6 13.4h4.8"/><path d="M8 11v2.4"/></svg></span> Окна приложения</div>
<div class="faq-item">
<button class="faq-question" type="button" aria-expanded="false">Виджет таймера<span class="arrow" aria-hidden="true">⌄</span></button>
<div class="faq-answer">
<p>Компактный прозрачный виджет поверх любого окна. Настраивается на вкладке <strong>Виджет</strong>.</p>
<ul>
<li>Перетаскивается мышью за любое место</li>
<li><strong>Ctrl</strong> + колесо мыши — масштаб 30–600 %</li>
<li>Размер и положение сохраняются до следующего запуска</li>
<li>Горячая клавиша: <strong>W</strong> (открыть / закрыть)</li>
</ul>
</div>
</div>
<div class="faq-item">
<button class="faq-question" type="button" aria-expanded="false">Виджет часов<span class="arrow" aria-hidden="true">⌄</span></button>
<div class="faq-answer">
<p>Показывает текущее время. Настраивается на вкладке <strong>Часы</strong>.</p>
<ul>
<li>Четыре стиля: круговой, LED, перекидной, аналоговый</li>
<li>Переключатели: секунды, 24-часовой формат, дата, часовой пояс</li>
<li><strong>Как у виджета</strong> — часы следуют за стилем виджета таймера. Выбор стиля часов вручную снова разъединяет их</li>
<li><strong>Ctrl</strong> + колесо — масштаб 30–600 %, перетаскивание мышью — перемещение</li>
<li>Горячая клавиша: <strong>C</strong></li>
</ul>
</div>
</div>
<div class="faq-item">
<button class="faq-question" type="button" aria-expanded="false">Полноэкранный дисплей<span class="arrow" aria-hidden="true">⌄</span></button>
<div class="faq-answer">
<p>Таймер на весь экран — для презентаций, стримов и мероприятий. Настраивается на вкладке <strong>Дисплей</strong></p>
<ul>
<li>Выбор монитора, если их несколько</li>
<li><strong>Ctrl</strong> + колесо над элементом — масштаб ИМЕННО ЭТОГО элемента 50–600 %; мимо всех — масштаб таймера 30–300 %</li>
<li><strong>Shift</strong> + колесо — масштаб всех блоков времени сразу</li>
<li><strong>Alt</strong> + перетаскивание — переместить элемент куда угодно; позиции сохраняются. <strong>Alt</strong> + крестик — убрать его с экрана</li>
<li><strong>Раскладка</strong> — пять готовых видов: «Классика», «Совещание», «Сцена», «Сводка», «Минимум». Кнопка расставляет элементы и подгоняет масштабы так, чтобы всё поместилось</li>
<li>Элементы: текущее время, начало и окончание мероприятия, «до завершения», название, подпись над таймером, плашка состояния и САМ ТАЙМЕР — его тоже можно перетащить куда угодно</li>
<li>Названия плашек можно заменить своими: поля «Названия плашек» ниже. Пустое поле — стандартная подпись</li>
<li>Горячие клавиши: <strong>D</strong> — открыть / закрыть. <strong>Esc</strong> окна не гасит: на совещании его жмут рефлекторно</li>
</ul>
</div>
</div>
<div class="faq-item">
<button class="faq-question" type="button" aria-expanded="false">Где работают горячие клавиши<span class="arrow" aria-hidden="true">⌄</span></button>
<div class="faq-answer">
<p>Во <strong>всех</strong> окнах приложения, а не только в панели управления: нажатие <strong>Space</strong> на виджете или в полноэкранном режиме запускает и останавливает таймер так же, как кнопка в панели.</p>
</div>
</div>
</div>
<div class="faq-section">
<div class="faq-section-title"><span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="4"/><rect x="7.5" y="7.5" width="6.5" height="6.5" rx="1.5"/></svg></span> Стили и оформление</div>
<div class="faq-item">
<button class="faq-question" type="button" aria-expanded="false">Стили таймера<span class="arrow" aria-hidden="true">⌄</span></button>
<div class="faq-answer">
<ul>
<li><strong>Круг</strong> — кольцевой индикатор прогресса</li>
<li><strong>LED</strong> — цифровой дисплей с неоновым свечением</li>
<li><strong>Флип</strong> — механические перекидные часы</li>
<li><strong>Аналог</strong> — циферблат со стрелками</li>
</ul>
<p>Стиль выбирается отдельно для каждого окна: на вкладках <strong>Виджет</strong>, <strong>Часы</strong> и <strong>Дисплей</strong></p>
</div>
</div>
<div class="faq-item">
<button class="faq-question" type="button" aria-expanded="false">Цвета<span class="arrow" aria-hidden="true">⌄</span></button>
<div class="faq-answer">
<p>У виджета, часов и полноэкранного дисплея <strong>независимые</strong> цвета — на каждой вкладке своя палитра.</p>
<ul>
<li>Готовые темы — кружки в палитре</li>
<li><strong>Свой цвет</strong> — радужный кружок в конце палитры открывает выбор оттенка и ввод HEX</li>
</ul>
<p>Когда времени остаётся мало, цвет переключается сам: жёлтый, затем красный. На нуле и в перерасходе — красный.</p>
</div>
</div>
<div class="faq-item">
<button class="faq-question" type="button" aria-expanded="false">Фон полноэкранного дисплея<span class="arrow" aria-hidden="true">⌄</span></button>
<div class="faq-answer">
<p>Три режима на вкладке <strong>Дисплей</strong>:</p>
<ul>
<li><strong>Заливка</strong> — один цвет</li>
<li><strong>Градиент</strong> — два цвета с плавным переходом</li>
<li><strong>Файл</strong> — изображение с компьютера: JPG, PNG, GIF, WebP, BMP</li>
</ul>
<p>Для файла настраиваются вписывание (<strong>Заполнить</strong>, <strong>Вписать</strong>, <strong>Плитка</strong>) и затемнение поверх картинки, чтобы цифры читались.</p>
</div>
</div>
</div>
<div class="faq-section">
<div class="faq-section-title"><span class="icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4.4 11.2V7.4a3.6 3.6 0 0 1 7.2 0v3.8"/><path d="M2.8 11.2h10.4"/><path d="M6.6 13.4h2.8"/></svg></span> Звуковые уведомления</div>
<div class="faq-item">
<button class="faq-question" type="button" aria-expanded="false">Четыре события<span class="arrow" aria-hidden="true">⌄</span></button>
<div class="faq-answer">
<p>Вкладка <strong>Звуки</strong>, у каждого события свой переключатель и свой звук:</p>
<ul>
<li><strong>Старт</strong> — при запуске таймера</li>
<li><strong>Окончание</strong> — при достижении нуля</li>
<li><strong>Минута</strong> — когда остаётся одна минута</li>
<li><strong>Перерасход</strong> — в режиме отсчёта ниже нуля, с выбором интервала повтора: 1, 2, 3, 5 или 10 минут</li>
</ul>
<p>Тумблер в заголовке раздела глушит всё сразу. Пресет <strong>— без звука —</strong> отключает одно конкретное событие. <strong>▶</strong> — прослушать.</p>
</div>
</div>
<div class="faq-item">
<button class="faq-question" type="button" aria-expanded="false">Свои звуки<span class="arrow" aria-hidden="true">⌄</span></button>
<div class="faq-answer">
<ul>
<li>Перетащите файл в пунктирную рамку внизу вкладки <strong>Звуки</strong> или нажмите на неё</li>
<li>Форматы: MP3, WAV, OGG, AAC, FLAC — до 5 МБ</li>
<li>Загруженные появятся во всех четырёх списках в группе <strong>Ваши звуки</strong></li>
</ul>
</div>
</div>
</div>