-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
980 lines (959 loc) · 180 KB
/
index.html
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
<!DOCTYPE html><html lang="en-US"><head><title>Unpoly 3</title><meta property="og:title" content="Unpoly 3"><meta charset="UTF-8"><meta name="viewport" content="width=device-width,height=device-height,initial-scale=1.0"><meta name="apple-mobile-web-app-capable" content="yes"><meta http-equiv="X-UA-Compatible" content="ie=edge"><meta property="og:type" content="website"><meta name="twitter:card" content="summary"><style>@media screen{body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container button{-webkit-tap-highlight-color:transparent;-webkit-appearance:none;appearance:none;background-color:transparent;border:0;color:inherit;cursor:pointer;font-size:inherit;opacity:.8;outline:none;padding:0;transition:opacity .2s linear}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button:disabled,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button:disabled,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button:disabled,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container button:disabled{cursor:not-allowed;opacity:.15!important}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button:hover,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button:hover,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button:hover,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container button:hover{opacity:1}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button:hover:active,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button:hover:active,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button:hover:active,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container button:hover:active{opacity:.6}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button:hover:not(:disabled),body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button:hover:not(:disabled),body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button:hover:not(:disabled),body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container button:hover:not(:disabled){transition:none}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=prev],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=prev],body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button.bespoke-marp-presenter-info-page-prev{background:transparent url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI1IiBkPSJNNjggOTAgMjggNTBsNDAtNDAiLz48L3N2Zz4=") no-repeat 50%;background-size:contain;overflow:hidden;text-indent:100%;white-space:nowrap}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=next],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=next],body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button.bespoke-marp-presenter-info-page-next{background:transparent url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI1IiBkPSJtMzIgOTAgNDAtNDAtNDAtNDAiLz48L3N2Zz4=") no-repeat 50%;background-size:contain;overflow:hidden;text-indent:100%;white-space:nowrap}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=fullscreen],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=fullscreen]{background:transparent url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48ZGVmcz48c3R5bGU+LmF7ZmlsbDpub25lO3N0cm9rZTojZmZmO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6NXB4fTwvc3R5bGU+PC9kZWZzPjxyZWN0IHdpZHRoPSI4MCIgaGVpZ2h0PSI2MCIgeD0iMTAiIHk9IjIwIiBjbGFzcz0iYSIgcng9IjUuNjciLz48cGF0aCBkPSJNNDAgNzBIMjBWNTBtMjAgMEwyMCA3MG00MC00MGgyMHYyMG0tMjAgMCAyMC0yMCIgY2xhc3M9ImEiLz48L3N2Zz4=") no-repeat 50%;background-size:contain;overflow:hidden;text-indent:100%;white-space:nowrap}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button.exit[data-bespoke-marp-osc=fullscreen],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button.exit[data-bespoke-marp-osc=fullscreen]{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48ZGVmcz48c3R5bGU+LmF7ZmlsbDpub25lO3N0cm9rZTojZmZmO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6NXB4fTwvc3R5bGU+PC9kZWZzPjxyZWN0IHdpZHRoPSI4MCIgaGVpZ2h0PSI2MCIgeD0iMTAiIHk9IjIwIiBjbGFzcz0iYSIgcng9IjUuNjciLz48cGF0aCBkPSJNMjAgNTBoMjB2MjBtLTIwIDAgMjAtMjBtNDAgMEg2MFYzMG0yMCAwTDYwIDUwIiBjbGFzcz0iYSIvPjwvc3ZnPg==")}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=presenter],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=presenter]{background:transparent url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI1IiBkPSJNODcuOCA0Ny41Qzg5IDUwIDg3LjcgNTIgODUgNTJIMzVhOC43IDguNyAwIDAgMS03LjItNC41bC0xNS42LTMxQzExIDE0IDEyLjIgMTIgMTUgMTJoNTBhOC44IDguOCAwIDAgMSA3LjIgNC41ek02MCA1MnYzNm0tMTAgMGgyME00NSA0MmgyMCIvPjwvc3ZnPg==") no-repeat 50%;background-size:contain;overflow:hidden;text-indent:100%;white-space:nowrap}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container button.bespoke-marp-presenter-note-bigger{background:transparent url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI1IiBkPSJNMTIgNTBoODBNNTIgOTBWMTAiLz48L3N2Zz4=") no-repeat 50%;background-size:contain;overflow:hidden;text-indent:100%;white-space:nowrap}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container button.bespoke-marp-presenter-note-smaller{background:transparent url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI1IiBkPSJNMTIgNTBoODAiLz48L3N2Zz4=") no-repeat 50%;background-size:contain;overflow:hidden;text-indent:100%;white-space:nowrap}}@keyframes __bespoke_marp_transition_reduced_outgoing__{0%{opacity:1}to{opacity:0}}@keyframes __bespoke_marp_transition_reduced_incoming__{0%{mix-blend-mode:plus-lighter;opacity:0}to{mix-blend-mode:plus-lighter;opacity:1}}.bespoke-marp-note,.bespoke-marp-osc,.bespoke-progress-parent{display:none;transition:none}@media screen{::view-transition-group(*){animation-duration:var(--marp-bespoke-transition-animation-duration,.5s);animation-timing-function:ease}::view-transition-new(*),::view-transition-old(*){animation-delay:0s;animation-direction:var(--marp-bespoke-transition-animation-direction,normal);animation-duration:var(--marp-bespoke-transition-animation-duration,.5s);animation-fill-mode:both;animation-name:var(--marp-bespoke-transition-animation-name,var(--marp-bespoke-transition-animation-name-fallback,__bespoke_marp_transition_no_animation__));mix-blend-mode:normal}::view-transition-old(*){--marp-bespoke-transition-animation-name-fallback:__bespoke_marp_transition_reduced_outgoing__;animation-timing-function:ease}::view-transition-new(*){--marp-bespoke-transition-animation-name-fallback:__bespoke_marp_transition_reduced_incoming__;animation-timing-function:ease}::view-transition-new(root),::view-transition-old(root){animation-timing-function:linear}::view-transition-new(__bespoke_marp_transition_osc__),::view-transition-old(__bespoke_marp_transition_osc__){animation-duration:0s!important;animation-name:__bespoke_marp_transition_osc__!important}::view-transition-new(__bespoke_marp_transition_osc__){opacity:0!important}.bespoke-marp-transition-warming-up::view-transition-group(*),.bespoke-marp-transition-warming-up::view-transition-new(*),.bespoke-marp-transition-warming-up::view-transition-old(*){animation-play-state:paused!important}body,html{height:100%;margin:0}body{background:#000;overflow:hidden}svg.bespoke-marp-slide{content-visibility:hidden;opacity:0;pointer-events:none;z-index:-1}svg.bespoke-marp-slide:not(.bespoke-marp-active) *{view-transition-name:none!important}svg.bespoke-marp-slide.bespoke-marp-active{content-visibility:visible;opacity:1;pointer-events:auto;z-index:0}svg.bespoke-marp-slide.bespoke-marp-active.bespoke-marp-active-ready *{animation-name:__bespoke_marp__!important}@supports not (content-visibility:hidden){svg.bespoke-marp-slide[data-bespoke-marp-load=hideable]{display:none}svg.bespoke-marp-slide[data-bespoke-marp-load=hideable].bespoke-marp-active{display:block}}}@media screen and (prefers-reduced-motion:reduce){svg.bespoke-marp-slide *{view-transition-name:none!important}}@media screen{[data-bespoke-marp-fragment=inactive]{visibility:hidden}body[data-bespoke-view=""] .bespoke-marp-parent,body[data-bespoke-view=next] .bespoke-marp-parent{inset:0;position:absolute}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc{view-transition-name:__bespoke_marp_transition_osc__;background:rgba(0,0,0,.65);border-radius:7px;bottom:50px;color:#fff;contain:paint;display:block;font-family:Helvetica,Arial,sans-serif;font-size:16px;left:50%;line-height:0;opacity:1;padding:12px;position:absolute;touch-action:manipulation;transform:translateX(-50%);transition:opacity .2s linear;-webkit-user-select:none;user-select:none;white-space:nowrap;will-change:transform;z-index:1}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>*,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>*{margin-left:6px}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>:first-child,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>:first-child{margin-left:0}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>span,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>span{opacity:.8}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>span[data-bespoke-marp-osc=page],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>span[data-bespoke-marp-osc=page]{display:inline-block;min-width:140px;text-align:center}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=fullscreen],body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=next],body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=presenter],body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=prev],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=fullscreen],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=next],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=presenter],body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc>button[data-bespoke-marp-osc=prev]{height:32px;line-height:32px;width:32px}body[data-bespoke-view=""] .bespoke-marp-parent.bespoke-marp-inactive,body[data-bespoke-view=next] .bespoke-marp-parent.bespoke-marp-inactive{cursor:none}body[data-bespoke-view=""] .bespoke-marp-parent.bespoke-marp-inactive>.bespoke-marp-osc,body[data-bespoke-view=next] .bespoke-marp-parent.bespoke-marp-inactive>.bespoke-marp-osc{opacity:0;pointer-events:none}body[data-bespoke-view=""] svg.bespoke-marp-slide,body[data-bespoke-view=next] svg.bespoke-marp-slide{height:100%;left:0;position:absolute;top:0;width:100%}body[data-bespoke-view=""] .bespoke-progress-parent{background:#222;display:flex;height:5px;width:100%}body[data-bespoke-view=""] .bespoke-progress-parent+.bespoke-marp-parent{top:5px}body[data-bespoke-view=""] .bespoke-progress-parent .bespoke-progress-bar{background:#0288d1;flex:0 0 0;transition:flex-basis .2s cubic-bezier(0,1,1,1)}body[data-bespoke-view=next]{background:transparent}body[data-bespoke-view=presenter]{background:#161616}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container{display:grid;font-family:Helvetica,Arial,sans-serif;grid-template:"current dragbar next" minmax(140px,1fr) "current dragbar note" 2fr "info dragbar note" 3em;grid-template-columns:minmax(3px,var(--bespoke-marp-presenter-split-ratio,66%)) 0 minmax(3px,1fr);height:100%;left:0;position:absolute;top:0;width:100%}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-parent{grid-area:current;overflow:hidden;position:relative}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-parent svg.bespoke-marp-slide{height:calc(100% - 40px);left:20px;pointer-events:none;position:absolute;top:20px;-webkit-user-select:none;user-select:none;width:calc(100% - 40px)}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-parent svg.bespoke-marp-slide.bespoke-marp-active{filter:drop-shadow(0 3px 10px rgba(0,0,0,.5))}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-dragbar-container{background:#0288d1;cursor:col-resize;grid-area:dragbar;margin-left:-3px;opacity:0;position:relative;transition:opacity .4s linear .1s;width:6px;z-index:10}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-dragbar-container:hover{opacity:1}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-dragbar-container.active{opacity:1;transition-delay:0s}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-next-container{background:#222;cursor:pointer;display:none;grid-area:next;overflow:hidden;position:relative}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-next-container.active{display:block}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-next-container iframe.bespoke-marp-presenter-next{background:transparent;border:0;display:block;filter:drop-shadow(0 3px 10px rgba(0,0,0,.5));height:calc(100% - 40px);left:20px;pointer-events:none;position:absolute;top:20px;-webkit-user-select:none;user-select:none;width:calc(100% - 40px)}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container{background:#222;color:#eee;grid-area:note;position:relative;z-index:1}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container button{height:1.5em;line-height:1.5em;width:1.5em}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-presenter-note-wrapper{display:block;inset:0;position:absolute}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-presenter-note-buttons{background:rgba(0,0,0,.65);border-radius:4px;bottom:0;display:flex;gap:4px;margin:12px;opacity:0;padding:6px;pointer-events:none;position:absolute;right:0;transition:opacity .2s linear}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-presenter-note-buttons:focus-within,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-presenter-note-wrapper:focus-within+.bespoke-marp-presenter-note-buttons,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container:hover .bespoke-marp-presenter-note-buttons{opacity:1;pointer-events:auto}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note{word-wrap:break-word;box-sizing:border-box;font-size:calc(1.1em*var(--bespoke-marp-note-font-scale, 1));height:calc(100% - 40px);margin:20px;overflow:auto;padding-right:3px;scrollbar-color:hsla(0,0%,93%,.5) transparent;scrollbar-width:thin;white-space:pre-wrap;width:calc(100% - 40px)}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note::-webkit-scrollbar{width:6px}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note::-webkit-scrollbar-track{background:transparent}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note::-webkit-scrollbar-thumb{background:hsla(0,0%,93%,.5);border-radius:6px}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note:empty{pointer-events:none}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note.active{display:block}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note p:first-child{margin-top:0}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note p:last-child{margin-bottom:0}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container{align-items:center;box-sizing:border-box;color:#eee;display:flex;flex-wrap:nowrap;grid-area:info;justify-content:center;overflow:hidden;padding:0 10px}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-page,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-time,body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-timer{box-sizing:border-box;display:block;padding:0 10px;white-space:nowrap;width:100%}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container button{height:1.5em;line-height:1.5em;width:1.5em}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-page{order:2;text-align:center}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-page .bespoke-marp-presenter-info-page-text{display:inline-block;min-width:120px;text-align:center}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-time{color:#999;order:1;text-align:left}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-timer{color:#999;order:3;text-align:right}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-info-container .bespoke-marp-presenter-info-timer:hover{cursor:pointer}}@media print{.bespoke-marp-presenter-info-container,.bespoke-marp-presenter-next-container,.bespoke-marp-presenter-note-container{display:none}}</style><style>@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,700;1,400;1,700&display=swap');@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap');@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@700&display=swap');div#\:\$p>svg>foreignObject>section{width:1280px;height:720px;box-sizing:border-box;overflow:hidden;position:relative;scroll-snap-align:center center}div#\:\$p>svg>foreignObject>section:after{bottom:0;content:attr(data-marpit-pagination);padding:inherit;pointer-events:none;position:absolute;right:0}div#\:\$p>svg>foreignObject>section:not([data-marpit-pagination]):after{display:none}/* Normalization */div#\:\$p>svg>foreignObject>section :is(h1,marp-h1){font-size:2em;margin:0.67em 0}div#\:\$p>svg>foreignObject>section video::-webkit-media-controls{will-change:transform}@page{size:1280px 720px;margin:0}@media print{body,html{background-color:#fff;margin:0;page-break-inside:avoid;break-inside:avoid-page}div#\:\$p>svg>foreignObject>section{page-break-before:always;break-before:page}div#\:\$p>svg>foreignObject>section,div#\:\$p>svg>foreignObject>section *{-webkit-print-color-adjust:exact!important;animation-delay:0s!important;animation-duration:0s!important;color-adjust:exact!important;transition:none!important}div#\:\$p>svg[data-marpit-svg]{display:block;height:100vh;width:100vw}}
/*!
* Marp default theme.
*
* @theme default
* @author Yuki Hattori
*
* @auto-scaling true
* @size 16:9 1280px 720px
* @size 4:3 960px 720px
*/div#\:\$p>svg>foreignObject>section{--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-brackethighlighter-unmatched:#82071e;--color-prettylights-syntax-invalid-illegal-text:#f6f8fa;--color-prettylights-syntax-invalid-illegal-bg:#82071e;--color-prettylights-syntax-carriage-return-text:#f6f8fa;--color-prettylights-syntax-carriage-return-bg:#cf222e;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-markup-list:#3b2300;--color-prettylights-syntax-markup-heading:#0550ae;--color-prettylights-syntax-markup-italic:#24292f;--color-prettylights-syntax-markup-bold:#24292f;--color-prettylights-syntax-markup-deleted-text:#82071e;--color-prettylights-syntax-markup-deleted-bg:#ffebe9;--color-prettylights-syntax-markup-inserted-text:#116329;--color-prettylights-syntax-markup-inserted-bg:#dafbe1;--color-prettylights-syntax-markup-changed-text:#953800;--color-prettylights-syntax-markup-changed-bg:#ffd8b5;--color-prettylights-syntax-markup-ignored-text:#eaeef2;--color-prettylights-syntax-markup-ignored-bg:#0550ae;--color-prettylights-syntax-meta-diff-range:#8250df;--color-prettylights-syntax-brackethighlighter-angle:#57606a;--color-prettylights-syntax-sublimelinter-gutter-mark:#8c959f;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-fg-subtle:#6e7781;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-attention-subtle:#fff8c5;--color-danger-fg:#cf222e;color-scheme:light}div#\:\$p>svg>foreignObject>section:where(.invert){--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-brackethighlighter-unmatched:#f85149;--color-prettylights-syntax-invalid-illegal-text:#f0f6fc;--color-prettylights-syntax-invalid-illegal-bg:#8e1519;--color-prettylights-syntax-carriage-return-text:#f0f6fc;--color-prettylights-syntax-carriage-return-bg:#b62324;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-markup-list:#f2cc60;--color-prettylights-syntax-markup-heading:#1f6feb;--color-prettylights-syntax-markup-italic:#c9d1d9;--color-prettylights-syntax-markup-bold:#c9d1d9;--color-prettylights-syntax-markup-deleted-text:#ffdcd7;--color-prettylights-syntax-markup-deleted-bg:#67060c;--color-prettylights-syntax-markup-inserted-text:#aff5b4;--color-prettylights-syntax-markup-inserted-bg:#033a16;--color-prettylights-syntax-markup-changed-text:#ffdfb6;--color-prettylights-syntax-markup-changed-bg:#5a1e02;--color-prettylights-syntax-markup-ignored-text:#c9d1d9;--color-prettylights-syntax-markup-ignored-bg:#1158c7;--color-prettylights-syntax-meta-diff-range:#d2a8ff;--color-prettylights-syntax-brackethighlighter-angle:#8b949e;--color-prettylights-syntax-sublimelinter-gutter-mark:#484f58;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-fg-subtle:#6e7681;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:hsla(215,8%,47%,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-attention-subtle:rgba(187,128,9,.15);--color-danger-fg:#f85149;color-scheme:dark}div#\:\$p>svg>foreignObject>section{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;word-wrap:break-word;background-color:var(--color-canvas-default);color:var(--color-fg-default);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Noto Sans,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:16px;line-height:1.5;margin:0}div#\:\$p>svg>foreignObject>section{--marpit-root-font-size:16px}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1):hover .anchor .octicon-link:before,div#\:\$p>svg>foreignObject>section :is(h2,marp-h2):hover .anchor .octicon-link:before,div#\:\$p>svg>foreignObject>section :is(h3,marp-h3):hover .anchor .octicon-link:before,div#\:\$p>svg>foreignObject>section :is(h4,marp-h4):hover .anchor .octicon-link:before,div#\:\$p>svg>foreignObject>section :is(h5,marp-h5):hover .anchor .octicon-link:before,div#\:\$p>svg>foreignObject>section :is(h6,marp-h6):hover .anchor .octicon-link:before{background-color:currentColor;content:" ";display:inline-block;height:16px;-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 0 0 1.06 1.06l1.25-1.25a2 2 0 1 1 2.83 2.83l-2.5 2.5a2 2 0 0 1-2.83 0 .75.75 0 0 0-1.06 1.06 3.5 3.5 0 0 0 4.95 0l2.5-2.5a3.5 3.5 0 0 0-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 0 1 0-2.83l2.5-2.5a2 2 0 0 1 2.83 0 .75.75 0 0 0 1.06-1.06 3.5 3.5 0 0 0-4.95 0l-2.5 2.5a3.5 3.5 0 0 0 4.95 4.95l1.25-1.25a.75.75 0 0 0-1.06-1.06l-1.25 1.25a2 2 0 0 1-2.83 0z"/></svg>');mask-image:url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 0 0 1.06 1.06l1.25-1.25a2 2 0 1 1 2.83 2.83l-2.5 2.5a2 2 0 0 1-2.83 0 .75.75 0 0 0-1.06 1.06 3.5 3.5 0 0 0 4.95 0l2.5-2.5a3.5 3.5 0 0 0-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 0 1 0-2.83l2.5-2.5a2 2 0 0 1 2.83 0 .75.75 0 0 0 1.06-1.06 3.5 3.5 0 0 0-4.95 0l-2.5 2.5a3.5 3.5 0 0 0 4.95 4.95l1.25-1.25a.75.75 0 0 0-1.06-1.06l-1.25 1.25a2 2 0 0 1-2.83 0z"/></svg>');width:16px}div#\:\$p>svg>foreignObject>section details,div#\:\$p>svg>foreignObject>section figcaption,div#\:\$p>svg>foreignObject>section figure{display:block}div#\:\$p>svg>foreignObject>section summary{display:list-item}div#\:\$p>svg>foreignObject>section [hidden]{display:none!important}div#\:\$p>svg>foreignObject>section a{background-color:transparent;color:var(--color-accent-fg);text-decoration:none}div#\:\$p>svg>foreignObject>section abbr[title]{border-bottom:none;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}div#\:\$p>svg>foreignObject>section b,div#\:\$p>svg>foreignObject>section strong{font-weight:var(--base-text-weight-semibold,600)}div#\:\$p>svg>foreignObject>section dfn{font-style:italic}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1){border-bottom:1px solid var(--color-border-muted);font-size:2em;font-weight:var(--base-text-weight-semibold,600);margin:.67em 0;padding-bottom:.3em}div#\:\$p>svg>foreignObject>section mark{background-color:var(--color-attention-subtle);color:var(--color-fg-default)}div#\:\$p>svg>foreignObject>section small{font-size:90%}div#\:\$p>svg>foreignObject>section sub,div#\:\$p>svg>foreignObject>section sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}div#\:\$p>svg>foreignObject>section sub{bottom:-.25em}div#\:\$p>svg>foreignObject>section sup{top:-.5em}div#\:\$p>svg>foreignObject>section img{background-color:var(--color-canvas-default);border-style:none;box-sizing:content-box;max-width:100%}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre),div#\:\$p>svg>foreignObject>section code,div#\:\$p>svg>foreignObject>section kbd,div#\:\$p>svg>foreignObject>section samp{font-family:monospace;font-size:1em}div#\:\$p>svg>foreignObject>section figure{margin:1em 40px}div#\:\$p>svg>foreignObject>section hr{background:transparent;background-color:var(--color-border-default);border:0;box-sizing:content-box;height:.25em;margin:24px 0;overflow:hidden;padding:0}div#\:\$p>svg>foreignObject>section input{font:inherit;font-family:inherit;font-size:inherit;line-height:inherit;margin:0;overflow:visible}div#\:\$p>svg>foreignObject>section [type=button],div#\:\$p>svg>foreignObject>section [type=reset],div#\:\$p>svg>foreignObject>section [type=submit]{-webkit-appearance:button}div#\:\$p>svg>foreignObject>section [type=checkbox],div#\:\$p>svg>foreignObject>section [type=radio]{box-sizing:border-box;padding:0}div#\:\$p>svg>foreignObject>section [type=number]::-webkit-inner-spin-button,div#\:\$p>svg>foreignObject>section [type=number]::-webkit-outer-spin-button{height:auto}div#\:\$p>svg>foreignObject>section [type=search]::-webkit-search-cancel-button,div#\:\$p>svg>foreignObject>section [type=search]::-webkit-search-decoration{-webkit-appearance:none}div#\:\$p>svg>foreignObject>section ::-webkit-input-placeholder{color:inherit;opacity:.54}div#\:\$p>svg>foreignObject>section ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}div#\:\$p>svg>foreignObject>section a:hover{text-decoration:underline}div#\:\$p>svg>foreignObject>section ::-moz-placeholder{color:var(--color-fg-subtle);opacity:1}div#\:\$p>svg>foreignObject>section ::placeholder{color:var(--color-fg-subtle);opacity:1}div#\:\$p>svg>foreignObject>section hr:after,div#\:\$p>svg>foreignObject>section hr:before{content:"";display:table}div#\:\$p>svg>foreignObject>section hr:after{clear:both}div#\:\$p>svg>foreignObject>section table{border-collapse:collapse;border-spacing:0;display:block;max-width:100%;overflow:auto;width:-moz-max-content;width:max-content}div#\:\$p>svg>foreignObject>section td,div#\:\$p>svg>foreignObject>section th{padding:0}div#\:\$p>svg>foreignObject>section details summary{cursor:pointer}div#\:\$p>svg>foreignObject>section details:not([open])>:not(summary){display:none!important}div#\:\$p>svg>foreignObject>section [role=button]:focus,div#\:\$p>svg>foreignObject>section a:focus,div#\:\$p>svg>foreignObject>section input[type=checkbox]:focus,div#\:\$p>svg>foreignObject>section input[type=radio]:focus{box-shadow:none;outline:2px solid var(--color-accent-fg);outline-offset:-2px}div#\:\$p>svg>foreignObject>section [role=button]:focus:not(:focus-visible),div#\:\$p>svg>foreignObject>section a:focus:not(:focus-visible),div#\:\$p>svg>foreignObject>section input[type=checkbox]:focus:not(:focus-visible),div#\:\$p>svg>foreignObject>section input[type=radio]:focus:not(:focus-visible){outline:1px solid transparent}div#\:\$p>svg>foreignObject>section [role=button]:focus-visible,div#\:\$p>svg>foreignObject>section a:focus-visible,div#\:\$p>svg>foreignObject>section input[type=checkbox]:focus-visible,div#\:\$p>svg>foreignObject>section input[type=radio]:focus-visible{box-shadow:none;outline:2px solid var(--color-accent-fg);outline-offset:-2px}div#\:\$p>svg>foreignObject>section a:not([class]):focus,div#\:\$p>svg>foreignObject>section a:not([class]):focus-visible,div#\:\$p>svg>foreignObject>section input[type=checkbox]:focus,div#\:\$p>svg>foreignObject>section input[type=checkbox]:focus-visible,div#\:\$p>svg>foreignObject>section input[type=radio]:focus,div#\:\$p>svg>foreignObject>section input[type=radio]:focus-visible{outline-offset:0}div#\:\$p>svg>foreignObject>section kbd{background-color:var(--color-canvas-subtle);border-bottom-color:var(--color-neutral-muted);border:1px solid var(--color-neutral-muted);border-radius:6px;box-shadow:inset 0 -1px 0 var(--color-neutral-muted);color:var(--color-fg-default);display:inline-block;font:11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;line-height:10px;padding:3px 5px;vertical-align:middle}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1),div#\:\$p>svg>foreignObject>section :is(h2,marp-h2),div#\:\$p>svg>foreignObject>section :is(h3,marp-h3),div#\:\$p>svg>foreignObject>section :is(h4,marp-h4),div#\:\$p>svg>foreignObject>section :is(h5,marp-h5),div#\:\$p>svg>foreignObject>section :is(h6,marp-h6){font-weight:var(--base-text-weight-semibold,600);line-height:1.25;margin-bottom:16px;margin-top:24px}div#\:\$p>svg>foreignObject>section :is(h2,marp-h2){border-bottom:1px solid var(--color-border-muted);font-size:1.5em;padding-bottom:.3em}div#\:\$p>svg>foreignObject>section :is(h2,marp-h2),div#\:\$p>svg>foreignObject>section :is(h3,marp-h3){font-weight:var(--base-text-weight-semibold,600)}div#\:\$p>svg>foreignObject>section :is(h3,marp-h3){font-size:1.25em}div#\:\$p>svg>foreignObject>section :is(h4,marp-h4){font-size:1em}div#\:\$p>svg>foreignObject>section :is(h4,marp-h4),div#\:\$p>svg>foreignObject>section :is(h5,marp-h5){font-weight:var(--base-text-weight-semibold,600)}div#\:\$p>svg>foreignObject>section :is(h5,marp-h5){font-size:.875em}div#\:\$p>svg>foreignObject>section :is(h6,marp-h6){color:var(--color-fg-muted);font-size:.85em;font-weight:var(--base-text-weight-semibold,600)}div#\:\$p>svg>foreignObject>section p{margin-bottom:10px;margin-top:0}div#\:\$p>svg>foreignObject>section blockquote{border-left:.25em solid var(--color-border-default);color:var(--color-fg-muted);margin:0;padding:0 1em}div#\:\$p>svg>foreignObject>section ol,div#\:\$p>svg>foreignObject>section ul{margin-bottom:0;margin-top:0;padding-left:2em}div#\:\$p>svg>foreignObject>section ol ol,div#\:\$p>svg>foreignObject>section ul ol{list-style-type:lower-roman}div#\:\$p>svg>foreignObject>section ol ol ol,div#\:\$p>svg>foreignObject>section ol ul ol,div#\:\$p>svg>foreignObject>section ul ol ol,div#\:\$p>svg>foreignObject>section ul ul ol{list-style-type:lower-alpha}div#\:\$p>svg>foreignObject>section dd{margin-left:0}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre),div#\:\$p>svg>foreignObject>section code,div#\:\$p>svg>foreignObject>section samp,div#\:\$p>svg>foreignObject>section tt{font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;font-size:12px}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre){word-wrap:normal;margin-bottom:0;margin-top:0}div#\:\$p>svg>foreignObject>section .octicon{fill:currentColor;display:inline-block;overflow:visible!important;vertical-align:text-bottom}div#\:\$p>svg>foreignObject>section input::-webkit-inner-spin-button,div#\:\$p>svg>foreignObject>section input::-webkit-outer-spin-button{-webkit-appearance:none;appearance:none;margin:0}div#\:\$p>svg>foreignObject>section:after,div#\:\$p>svg>foreignObject>section:before{
/* content:""; */display:table}div#\:\$p>svg>foreignObject>section:after{clear:both}div#\:\$p>svg>foreignObject>section>:first-child{margin-top:0!important}div#\:\$p>svg>foreignObject>section>:last-child{margin-bottom:0!important}div#\:\$p>svg>foreignObject>section a:not([href]){color:inherit;text-decoration:none}div#\:\$p>svg>foreignObject>section .absent{color:var(--color-danger-fg)}div#\:\$p>svg>foreignObject>section .anchor{float:left;line-height:1;margin-left:-20px;padding-right:4px}div#\:\$p>svg>foreignObject>section .anchor:focus{outline:none}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre),div#\:\$p>svg>foreignObject>section blockquote,div#\:\$p>svg>foreignObject>section details,div#\:\$p>svg>foreignObject>section dl,div#\:\$p>svg>foreignObject>section ol,div#\:\$p>svg>foreignObject>section p,div#\:\$p>svg>foreignObject>section table,div#\:\$p>svg>foreignObject>section ul{margin-bottom:16px;margin-top:0}div#\:\$p>svg>foreignObject>section blockquote>:first-child{margin-top:0}div#\:\$p>svg>foreignObject>section blockquote>:last-child{margin-bottom:0}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1) .octicon-link,div#\:\$p>svg>foreignObject>section :is(h2,marp-h2) .octicon-link,div#\:\$p>svg>foreignObject>section :is(h3,marp-h3) .octicon-link,div#\:\$p>svg>foreignObject>section :is(h4,marp-h4) .octicon-link,div#\:\$p>svg>foreignObject>section :is(h5,marp-h5) .octicon-link,div#\:\$p>svg>foreignObject>section :is(h6,marp-h6) .octicon-link{color:var(--color-fg-default);vertical-align:middle;visibility:hidden}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1):hover .anchor,div#\:\$p>svg>foreignObject>section :is(h2,marp-h2):hover .anchor,div#\:\$p>svg>foreignObject>section :is(h3,marp-h3):hover .anchor,div#\:\$p>svg>foreignObject>section :is(h4,marp-h4):hover .anchor,div#\:\$p>svg>foreignObject>section :is(h5,marp-h5):hover .anchor,div#\:\$p>svg>foreignObject>section :is(h6,marp-h6):hover .anchor{text-decoration:none}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1):hover .anchor .octicon-link,div#\:\$p>svg>foreignObject>section :is(h2,marp-h2):hover .anchor .octicon-link,div#\:\$p>svg>foreignObject>section :is(h3,marp-h3):hover .anchor .octicon-link,div#\:\$p>svg>foreignObject>section :is(h4,marp-h4):hover .anchor .octicon-link,div#\:\$p>svg>foreignObject>section :is(h5,marp-h5):hover .anchor .octicon-link,div#\:\$p>svg>foreignObject>section :is(h6,marp-h6):hover .anchor .octicon-link{visibility:visible}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1) code,div#\:\$p>svg>foreignObject>section :is(h1,marp-h1) tt,div#\:\$p>svg>foreignObject>section :is(h2,marp-h2) code,div#\:\$p>svg>foreignObject>section :is(h2,marp-h2) tt,div#\:\$p>svg>foreignObject>section :is(h3,marp-h3) code,div#\:\$p>svg>foreignObject>section :is(h3,marp-h3) tt,div#\:\$p>svg>foreignObject>section :is(h4,marp-h4) code,div#\:\$p>svg>foreignObject>section :is(h4,marp-h4) tt,div#\:\$p>svg>foreignObject>section :is(h5,marp-h5) code,div#\:\$p>svg>foreignObject>section :is(h5,marp-h5) tt,div#\:\$p>svg>foreignObject>section :is(h6,marp-h6) code,div#\:\$p>svg>foreignObject>section :is(h6,marp-h6) tt{font-size:inherit;padding:0 .2em}div#\:\$p>svg>foreignObject>section summary :is(h1,marp-h1),div#\:\$p>svg>foreignObject>section summary :is(h2,marp-h2),div#\:\$p>svg>foreignObject>section summary :is(h3,marp-h3),div#\:\$p>svg>foreignObject>section summary :is(h4,marp-h4),div#\:\$p>svg>foreignObject>section summary :is(h5,marp-h5),div#\:\$p>svg>foreignObject>section summary :is(h6,marp-h6){display:inline-block}div#\:\$p>svg>foreignObject>section summary :is(h1,marp-h1) .anchor,div#\:\$p>svg>foreignObject>section summary :is(h2,marp-h2) .anchor,div#\:\$p>svg>foreignObject>section summary :is(h3,marp-h3) .anchor,div#\:\$p>svg>foreignObject>section summary :is(h4,marp-h4) .anchor,div#\:\$p>svg>foreignObject>section summary :is(h5,marp-h5) .anchor,div#\:\$p>svg>foreignObject>section summary :is(h6,marp-h6) .anchor{margin-left:-40px}div#\:\$p>svg>foreignObject>section summary :is(h1,marp-h1),div#\:\$p>svg>foreignObject>section summary :is(h2,marp-h2){border-bottom:0;padding-bottom:0}div#\:\$p>svg>foreignObject>section ol.no-list,div#\:\$p>svg>foreignObject>section ul.no-list{list-style-type:none;padding:0}div#\:\$p>svg>foreignObject>section ol[type=a]{list-style-type:lower-alpha}div#\:\$p>svg>foreignObject>section ol[type=A]{list-style-type:upper-alpha}div#\:\$p>svg>foreignObject>section ol[type=i]{list-style-type:lower-roman}div#\:\$p>svg>foreignObject>section ol[type=I]{list-style-type:upper-roman}div#\:\$p>svg>foreignObject>section div>ol:not([type]),div#\:\$p>svg>foreignObject>section ol[type="1"]{list-style-type:decimal}div#\:\$p>svg>foreignObject>section ol ol,div#\:\$p>svg>foreignObject>section ol ul,div#\:\$p>svg>foreignObject>section ul ol,div#\:\$p>svg>foreignObject>section ul ul{margin-bottom:0;margin-top:0}div#\:\$p>svg>foreignObject>section li>p{margin-top:16px}div#\:\$p>svg>foreignObject>section li+li{margin-top:.25em}div#\:\$p>svg>foreignObject>section dl{padding:0}div#\:\$p>svg>foreignObject>section dl dt{font-size:1em;font-style:italic;font-weight:var(--base-text-weight-semibold,600);margin-top:16px;padding:0}div#\:\$p>svg>foreignObject>section dl dd{margin-bottom:16px;padding:0 16px}div#\:\$p>svg>foreignObject>section table th{font-weight:var(--base-text-weight-semibold,600)}div#\:\$p>svg>foreignObject>section table td,div#\:\$p>svg>foreignObject>section table th{border:1px solid var(--color-border-default);padding:6px 13px}div#\:\$p>svg>foreignObject>section table tr{background-color:var(--color-canvas-default);border-top:1px solid var(--color-border-muted)}div#\:\$p>svg>foreignObject>section table tr:nth-child(2n){background-color:var(--color-canvas-subtle)}div#\:\$p>svg>foreignObject>section table img{background-color:transparent}div#\:\$p>svg>foreignObject>section img[align=right]{padding-left:20px}div#\:\$p>svg>foreignObject>section img[align=left]{padding-right:20px}div#\:\$p>svg>foreignObject>section .emoji{background-color:transparent;max-width:none;vertical-align:text-top}div#\:\$p>svg>foreignObject>section :is(span,marp-span).frame,div#\:\$p>svg>foreignObject>section :is(span,marp-span).frame>:is(span,marp-span){display:block;overflow:hidden}div#\:\$p>svg>foreignObject>section :is(span,marp-span).frame>:is(span,marp-span){border:1px solid var(--color-border-default);float:left;margin:13px 0 0;padding:7px;width:auto}div#\:\$p>svg>foreignObject>section :is(span,marp-span).frame :is(span,marp-span) img{display:block;float:left}div#\:\$p>svg>foreignObject>section :is(span,marp-span).frame :is(span,marp-span) :is(span,marp-span){clear:both;color:var(--color-fg-default);display:block;padding:5px 0 0}div#\:\$p>svg>foreignObject>section :is(span,marp-span).align-center{clear:both;display:block;overflow:hidden}div#\:\$p>svg>foreignObject>section :is(span,marp-span).align-center>:is(span,marp-span){display:block;margin:13px auto 0;overflow:hidden;text-align:center}div#\:\$p>svg>foreignObject>section :is(span,marp-span).align-center :is(span,marp-span) img{margin:0 auto;text-align:center}div#\:\$p>svg>foreignObject>section :is(span,marp-span).align-right{clear:both;display:block;overflow:hidden}div#\:\$p>svg>foreignObject>section :is(span,marp-span).align-right>:is(span,marp-span){display:block;margin:13px 0 0;overflow:hidden;text-align:right}div#\:\$p>svg>foreignObject>section :is(span,marp-span).align-right :is(span,marp-span) img{margin:0;text-align:right}div#\:\$p>svg>foreignObject>section :is(span,marp-span).float-left{display:block;float:left;margin-right:13px;overflow:hidden}div#\:\$p>svg>foreignObject>section :is(span,marp-span).float-left :is(span,marp-span){margin:13px 0 0}div#\:\$p>svg>foreignObject>section :is(span,marp-span).float-right{display:block;float:right;margin-left:13px;overflow:hidden}div#\:\$p>svg>foreignObject>section :is(span,marp-span).float-right>:is(span,marp-span){display:block;margin:13px auto 0;overflow:hidden;text-align:right}div#\:\$p>svg>foreignObject>section code,div#\:\$p>svg>foreignObject>section tt{background-color:var(--color-neutral-muted);border-radius:6px;font-size:85%;margin:0;padding:.2em .4em;white-space:break-spaces}div#\:\$p>svg>foreignObject>section code br,div#\:\$p>svg>foreignObject>section tt br{display:none}div#\:\$p>svg>foreignObject>section del code{text-decoration:inherit}div#\:\$p>svg>foreignObject>section samp{font-size:85%}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) code{font-size:100%}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre)>code{background:transparent;border:0;margin:0;padding:0;white-space:pre;word-break:normal}div#\:\$p>svg>foreignObject>section .highlight{margin-bottom:16px}div#\:\$p>svg>foreignObject>section .highlight :is(pre,marp-pre){margin-bottom:0;word-break:normal}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre){background-color:var(--color-canvas-subtle);border-radius:6px;font-size:85%;line-height:1.45;overflow:auto;padding:16px}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) code,div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) tt{word-wrap:normal;background-color:transparent;border:0;display:inline;line-height:inherit;margin:0;max-width:auto;overflow:visible;padding:0}div#\:\$p>svg>foreignObject>section .csv-data td,div#\:\$p>svg>foreignObject>section .csv-data th{font-size:12px;line-height:1;overflow:hidden;padding:5px;text-align:left;white-space:nowrap}div#\:\$p>svg>foreignObject>section .csv-data .blob-num{background:var(--color-canvas-default);border:0;padding:10px 8px 9px;text-align:right}div#\:\$p>svg>foreignObject>section .csv-data tr{border-top:0}div#\:\$p>svg>foreignObject>section .csv-data th{background:var(--color-canvas-subtle);border-top:0;font-weight:var(--base-text-weight-semibold,600)}div#\:\$p>svg>foreignObject>section [data-footnote-ref]:before{content:"["}div#\:\$p>svg>foreignObject>section [data-footnote-ref]:after{content:"]"}div#\:\$p>svg>foreignObject>section .footnotes{border-top:1px solid var(--color-border-default);color:var(--color-fg-muted);font-size:12px}div#\:\$p>svg>foreignObject>section div#\:\$p>svg>foreignObject>section section.footnotes{--marpit-root-font-size:12px}div#\:\$p>svg>foreignObject>section .footnotes ol{padding-left:16px}div#\:\$p>svg>foreignObject>section .footnotes ol ul{display:inline-block;margin-top:16px;padding-left:16px}div#\:\$p>svg>foreignObject>section .footnotes li{position:relative}div#\:\$p>svg>foreignObject>section .footnotes li:target:before{border:2px solid var(--color-accent-emphasis);border-radius:6px;bottom:-8px;content:"";left:-24px;pointer-events:none;position:absolute;right:-8px;top:-8px}div#\:\$p>svg>foreignObject>section .footnotes li:target{color:var(--color-fg-default)}div#\:\$p>svg>foreignObject>section .footnotes .data-footnote-backref g-emoji{font-family:monospace}div#\:\$p>svg>foreignObject>section .pl-c{color:var(--color-prettylights-syntax-comment)}div#\:\$p>svg>foreignObject>section .pl-c1,div#\:\$p>svg>foreignObject>section .pl-s .pl-v{color:var(--color-prettylights-syntax-constant)}div#\:\$p>svg>foreignObject>section .pl-e,div#\:\$p>svg>foreignObject>section .pl-en{color:var(--color-prettylights-syntax-entity)}div#\:\$p>svg>foreignObject>section .pl-s .pl-s1,div#\:\$p>svg>foreignObject>section .pl-smi{color:var(--color-prettylights-syntax-storage-modifier-import)}div#\:\$p>svg>foreignObject>section .pl-ent{color:var(--color-prettylights-syntax-entity-tag)}div#\:\$p>svg>foreignObject>section .pl-k{color:var(--color-prettylights-syntax-keyword)}div#\:\$p>svg>foreignObject>section .pl-pds,div#\:\$p>svg>foreignObject>section .pl-s,div#\:\$p>svg>foreignObject>section .pl-s .pl-pse .pl-s1,div#\:\$p>svg>foreignObject>section .pl-sr,div#\:\$p>svg>foreignObject>section .pl-sr .pl-cce,div#\:\$p>svg>foreignObject>section .pl-sr .pl-sra,div#\:\$p>svg>foreignObject>section .pl-sr .pl-sre{color:var(--color-prettylights-syntax-string)}div#\:\$p>svg>foreignObject>section .pl-smw,div#\:\$p>svg>foreignObject>section .pl-v{color:var(--color-prettylights-syntax-variable)}div#\:\$p>svg>foreignObject>section .pl-bu{color:var(--color-prettylights-syntax-brackethighlighter-unmatched)}div#\:\$p>svg>foreignObject>section .pl-ii{background-color:var(--color-prettylights-syntax-invalid-illegal-bg);color:var(--color-prettylights-syntax-invalid-illegal-text)}div#\:\$p>svg>foreignObject>section .pl-c2{background-color:var(--color-prettylights-syntax-carriage-return-bg);color:var(--color-prettylights-syntax-carriage-return-text)}div#\:\$p>svg>foreignObject>section .pl-sr .pl-cce{color:var(--color-prettylights-syntax-string-regexp);font-weight:700}div#\:\$p>svg>foreignObject>section .pl-ml{color:var(--color-prettylights-syntax-markup-list)}div#\:\$p>svg>foreignObject>section .pl-mh,div#\:\$p>svg>foreignObject>section .pl-mh .pl-en,div#\:\$p>svg>foreignObject>section .pl-ms{color:var(--color-prettylights-syntax-markup-heading);font-weight:700}div#\:\$p>svg>foreignObject>section .pl-mi{color:var(--color-prettylights-syntax-markup-italic);font-style:italic}div#\:\$p>svg>foreignObject>section .pl-mb{color:var(--color-prettylights-syntax-markup-bold);font-weight:700}div#\:\$p>svg>foreignObject>section .pl-md{background-color:var(--color-prettylights-syntax-markup-deleted-bg);color:var(--color-prettylights-syntax-markup-deleted-text)}div#\:\$p>svg>foreignObject>section .pl-mi1{background-color:var(--color-prettylights-syntax-markup-inserted-bg);color:var(--color-prettylights-syntax-markup-inserted-text)}div#\:\$p>svg>foreignObject>section .pl-mc{background-color:var(--color-prettylights-syntax-markup-changed-bg);color:var(--color-prettylights-syntax-markup-changed-text)}div#\:\$p>svg>foreignObject>section .pl-mi2{background-color:var(--color-prettylights-syntax-markup-ignored-bg);color:var(--color-prettylights-syntax-markup-ignored-text)}div#\:\$p>svg>foreignObject>section .pl-mdr{color:var(--color-prettylights-syntax-meta-diff-range);font-weight:700}div#\:\$p>svg>foreignObject>section .pl-ba{color:var(--color-prettylights-syntax-brackethighlighter-angle)}div#\:\$p>svg>foreignObject>section .pl-sg{color:var(--color-prettylights-syntax-sublimelinter-gutter-mark)}div#\:\$p>svg>foreignObject>section .pl-corl{color:var(--color-prettylights-syntax-constant-other-reference-link);text-decoration:underline}div#\:\$p>svg>foreignObject>section g-emoji{display:inline-block;font-family:Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-size:1em;font-style:normal!important;font-weight:var(--base-text-weight-normal,400);line-height:1;min-width:1ch;vertical-align:-.075em}div#\:\$p>svg>foreignObject>section g-emoji img{height:1em;width:1em}div#\:\$p>svg>foreignObject>section .task-list-item{list-style-type:none}div#\:\$p>svg>foreignObject>section .task-list-item label{font-weight:var(--base-text-weight-normal,400)}div#\:\$p>svg>foreignObject>section .task-list-item.enabled label{cursor:pointer}div#\:\$p>svg>foreignObject>section .task-list-item+.task-list-item{margin-top:4px}div#\:\$p>svg>foreignObject>section .task-list-item .handle{display:none}div#\:\$p>svg>foreignObject>section .task-list-item-checkbox{margin:0 .2em .25em -1.4em;vertical-align:middle}div#\:\$p>svg>foreignObject>section .contains-task-list:dir(rtl) .task-list-item-checkbox{margin:0 -1.6em .25em .2em}div#\:\$p>svg>foreignObject>section .contains-task-list{position:relative}div#\:\$p>svg>foreignObject>section .contains-task-list:focus-within .task-list-item-convert-container,div#\:\$p>svg>foreignObject>section .contains-task-list:hover .task-list-item-convert-container{clip:auto;display:block;height:24px;overflow:visible;width:auto}div#\:\$p>svg>foreignObject>section ::-webkit-calendar-picker-indicator{filter:invert(50%)}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1){color:var(--h1-color);font-size:1.6em}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1),div#\:\$p>svg>foreignObject>section :is(h2,marp-h2){border-bottom:none}div#\:\$p>svg>foreignObject>section :is(h2,marp-h2){font-size:1.3em}div#\:\$p>svg>foreignObject>section :is(h3,marp-h3){font-size:1.1em}div#\:\$p>svg>foreignObject>section :is(h4,marp-h4){font-size:1.05em}div#\:\$p>svg>foreignObject>section :is(h5,marp-h5){font-size:1em}div#\:\$p>svg>foreignObject>section :is(h6,marp-h6){font-size:.9em}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1) strong,div#\:\$p>svg>foreignObject>section :is(h2,marp-h2) strong,div#\:\$p>svg>foreignObject>section :is(h3,marp-h3) strong,div#\:\$p>svg>foreignObject>section :is(h4,marp-h4) strong,div#\:\$p>svg>foreignObject>section :is(h5,marp-h5) strong,div#\:\$p>svg>foreignObject>section :is(h6,marp-h6) strong{color:var(--heading-strong-color);font-weight:inherit}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1)::part(auto-scaling),div#\:\$p>svg>foreignObject>section :is(h2,marp-h2)::part(auto-scaling),div#\:\$p>svg>foreignObject>section :is(h3,marp-h3)::part(auto-scaling),div#\:\$p>svg>foreignObject>section :is(h4,marp-h4)::part(auto-scaling),div#\:\$p>svg>foreignObject>section :is(h5,marp-h5)::part(auto-scaling),div#\:\$p>svg>foreignObject>section :is(h6,marp-h6)::part(auto-scaling){max-height:563px}div#\:\$p>svg>foreignObject>section hr{height:0;padding-top:.25em}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre){border:1px solid var(--color-border-default);line-height:1.15;overflow:visible}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre)::part(auto-scaling){max-height:529px}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs){color:var(--color-prettylights-syntax-storage-modifier-import)}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-doctag),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-keyword),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-meta .hljs-keyword),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-template-tag),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-template-variable),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-type),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-variable.language_){color:var(--color-prettylights-syntax-keyword)}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-title),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-title.class_),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-title.class_.inherited__),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-title.function_){color:var(--color-prettylights-syntax-entity)}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-attr),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-attribute),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-literal),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-meta),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-number),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-operator),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-selector-attr),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-selector-class),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-selector-id),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-variable){color:var(--color-prettylights-syntax-constant)}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-meta .hljs-string),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-regexp),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-string){color:var(--color-prettylights-syntax-string)}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-built_in),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-symbol){color:var(--color-prettylights-syntax-variable)}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-code),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-comment),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-formula){color:var(--color-prettylights-syntax-comment)}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-name),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-quote),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-selector-pseudo),div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-selector-tag){color:var(--color-prettylights-syntax-entity-tag)}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-subst){color:var(--color-prettylights-syntax-storage-modifier-import)}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-section){color:var(--color-prettylights-syntax-markup-heading);font-weight:700}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-bullet){color:var(--color-prettylights-syntax-markup-list)}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-emphasis){color:var(--color-prettylights-syntax-markup-italic);font-style:italic}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-strong){color:var(--color-prettylights-syntax-markup-bold);font-weight:700}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-addition){background-color:var(--color-prettylights-syntax-markup-inserted-bg);color:var(--color-prettylights-syntax-markup-inserted-text)}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) :where(.hljs-deletion){background-color:var(--color-prettylights-syntax-markup-deleted-bg);color:var(--color-prettylights-syntax-markup-deleted-text)}div#\:\$p>svg>foreignObject>section footer,div#\:\$p>svg>foreignObject>section header{color:var(--header-footer-color);font-size:18px;left:30px;margin:0;position:absolute}div#\:\$p>svg>foreignObject>section header{top:21px}div#\:\$p>svg>foreignObject>section footer{bottom:21px}div#\:\$p>svg>foreignObject>section{--h1-color:#246;--header-footer-color:hsla(0,0%,40%,.75);--heading-strong-color:#48c;--paginate-color:#777;align-items:stretch;display:flex;flex-flow:column nowrap;font-size:29px;height:720px;justify-content:center;padding:78.5px;width:1280px}div#\:\$p>svg>foreignObject>section{--marpit-root-font-size:29px}div#\:\$p>svg>foreignObject>section:where(.invert){--h1-color:#cee7ff;--header-footer-color:hsla(0,0%,60%,.75);--heading-strong-color:#7bf;--paginate-color:#999}div#\:\$p>svg>foreignObject>section>:last-child,div#\:\$p>svg>foreignObject>section[data-footer]>:nth-last-child(2){margin-bottom:0}div#\:\$p>svg>foreignObject>section>:first-child,div#\:\$p>svg>foreignObject>section>header:first-child+*{margin-top:0}div#\:\$p>svg>foreignObject>section:after{bottom:21px;color:var(--paginate-color);font-size:24px;padding:0;position:absolute;right:30px}div#\:\$p>svg>foreignObject>section:after{--marpit-root-font-size:24px}div#\:\$p>svg>foreignObject>section[data-color] :is(h1,marp-h1),div#\:\$p>svg>foreignObject>section[data-color] :is(h2,marp-h2),div#\:\$p>svg>foreignObject>section[data-color] :is(h3,marp-h3),div#\:\$p>svg>foreignObject>section[data-color] :is(h4,marp-h4),div#\:\$p>svg>foreignObject>section[data-color] :is(h5,marp-h5),div#\:\$p>svg>foreignObject>section[data-color] :is(h6,marp-h6){color:currentcolor}div#\:\$p>svg>foreignObject>section img[data-marp-twemoji]{background:transparent;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em;width:1em}
/* @theme unpoly3-slides */div#\:\$p>svg>foreignObject>section html{
/* The Marp default CSS sets `font-weight: 600` for elements like headings,
but our font has no 600 weight available. */--base-text-weight-semibold:700}div#\:\$p>svg>foreignObject>section{
/* --highlightColor-color: #dd4232; */font-family:Roboto,sans-serif;font-size:25px;line-height:1.35;--highlightColor:#ff4433;--textColor:#24292e;--secondaryColor:#3182bd}div#\:\$p>svg>foreignObject>section{--marpit-root-font-size:25px}div#\:\$p>svg>foreignObject>section:not(.no-watermark):not(.title):not(.separator):not(.tweet){background-image:url('./images/unpoly3.svg');background-position:top 40px right 40px;background-size:250px;background-repeat:no-repeat}div#\:\$p>svg>foreignObject>section.rails-specific{background-image:url('./images/rails.svg')!important;background-size:180px!important;--highlightColor:#D30001;box-shadow:inset 0 15px 0 var(--highlightColor),inset 0 -15px 0 var(--highlightColor)}div#\:\$p>svg>foreignObject>section .muted{opacity:0.5}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre),div#\:\$p>svg>foreignObject>section code{background-color:rgba(245,245,245,0.8);background-color:rgba(0,0,0,0.05);border-radius:2px;font-family:'Roboto Mono',monospace}
/*
code {
background-color: transparent;
padding: 0 0.2em;
font-size: 1.02em;
}
*/div#\:\$p>svg>foreignObject>section .center{text-align:center}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1){color:var(--highlightColor);position:relative}div#\:\$p>svg>foreignObject>section.secondary-color{--highlightColor:var(--secondaryColor)}div#\:\$p>svg>foreignObject>section.separator{background-color:#555555}div#\:\$p>svg>foreignObject>section.tweet{background-color:#1DA1F2;color:white;font-weight:bold}div#\:\$p>svg>foreignObject>section.tweet time{display:block;opacity:0.6}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1) code,div#\:\$p>svg>foreignObject>section :is(h2,marp-h2) code,div#\:\$p>svg>foreignObject>section :is(h3,marp-h3) code,div#\:\$p>svg>foreignObject>section :is(h4,marp-h4) code{background:none;padding:0}
/*
h1:before {
content: '';
position: absolute;
top: 0.2em;
left: -5em;
width: 4.5em;
background-color: #f43;
height: 0.8em;
opacity: 0.5;
}
*/div#\:\$p>svg>foreignObject>section :is(h4,marp-h4){margin-top:0.3em;margin-bottom:0.4em}div#\:\$p>svg>foreignObject>section .row{display:flex;gap:2em;justify-content:flex-start}div#\:\$p>svg>foreignObject>section .col{flex:1 1 0}div#\:\$p>svg>foreignObject>section .title{margin:0 auto}div#\:\$p>svg>foreignObject>section .title--logo{width:600px}div#\:\$p>svg>foreignObject>section .title--author{border-top:2px solid currentColor;font-weight:bold;width:380px;text-align:center;margin:15px auto 0;padding-top:10px}div#\:\$p>svg>foreignObject>section .title--author a[href]{color:var(--textColor);text-decoration:none}div#\:\$p>svg>foreignObject>section.topic :is(h1,marp-h1){font-size:2.6em;font-family:Orbitron,sans-serif;text-transform:uppercase}div#\:\$p>svg>foreignObject>section.topic :is(h1,marp-h1)+:is(h2,marp-h2){margin-top:-0.5em;color:#777777}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1).topic{font-size:2.2em;font-family:Orbitron,sans-serif;text-transform:uppercase}div#\:\$p>svg>foreignObject>section.new:before,div#\:\$p>svg>foreignObject>section.pro:before{content:'EXT';background-color:white;position:absolute;left:-110px;top:-60px;top:0;left:0;text-align:center;padding:90px 0 10px;width:260px;color:white;background-color:var(--textColor);font-size:26px;font-weight:bold;transform:rotate(-45deg);xtransform-origin:0% 100%;transform-origin:0% 135%
/* box-shadow: 0 5px 40px rgba(40, 40, 40, 0.2); */}div#\:\$p>svg>foreignObject>section.new:before,div#\:\$p>svg>foreignObject>section.pro:before{--marpit-root-font-size:26px}div#\:\$p>svg>foreignObject>section.new:before{content:'NEW';background-color:var(--highlightColor)}div#\:\$p>svg>foreignObject>section.align-top{justify-content:flex-start}div#\:\$p>svg>foreignObject>section.no-padding{padding:0}div#\:\$p>svg>foreignObject>section.small-padding{padding:2em}div#\:\$p>svg>foreignObject>section .positive{color:#35be4c}div#\:\$p>svg>foreignObject>section .negative{color:#ff5050}
/*
.learning-box {
display: block;
margin-bottom: 1em;
font-weight: bold;
border: 1px solid #f83;
border-radius: 3px;
padding: 0.5em;
padding-left: 2em;
background-color: #fff6ee;
position: relative;
}
.learning-box::before {
content: '💡';
position: absolute;
left: 0.5em;
top: 0.5em;
}
*/div#\:\$p>svg>foreignObject>section th{text-align:left}
/* img {
width: auto;
max-width: 100%;
display: inline-block;
margin: 0 !important;
} */div#\:\$p>svg>foreignObject>section img.picture{border:1px solid #555;border-radius:3px;box-shadow:0 4px 9px rgba(40,40,40,0.2)}div#\:\$p>svg>foreignObject>section a[href]{text-decoration:underline;color:var(--highlightColor)}div#\:\$p>svg>foreignObject>section .hljs-comment{color:#868886!important}div#\:\$p>svg>foreignObject>section ul{list-style-type:none;padding:0}div#\:\$p>svg>foreignObject>section li{padding-left:1em;position:relative}div#\:\$p>svg>foreignObject>section li:before{content:'●';xfont-size:0.8em;color:var(--secondaryColor);position:absolute;left:0}div#\:\$p>svg>foreignObject>section .tag{background-color:var(--textColor);color:white;font-weight:bold;display:inline-block;padding:0.1em 0.3em 0.05em;border-radius:0.1em;font-size:0.8em;text-transform:uppercase}div#\:\$p>svg>foreignObject>section section.tag{--marpit-root-font-size:0.8em}div#\:\$p>svg>foreignObject>section .admonition{padding:0.7em;background-color:#eee;color:#444;background-color:#fdf3d0;color:#6a4b16;margin:0.6em 0}div#\:\$p>svg>foreignObject>section .admonition>:last-child{margin-bottom:0}div#\:\$p>svg>foreignObject>section .color-text{color:var(--textColor)}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]{columns:initial!important;display:block!important;padding:0!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]:after,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]:before,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content]:after,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content]:before{display:none!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]{all:initial;display:flex;flex-direction:row;height:100%;overflow:hidden;width:100%}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container][data-marpit-advanced-background-direction=vertical]{flex-direction:column}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background][data-marpit-advanced-background-split]>div[data-marpit-advanced-background-container]{width:var(--marpit-advanced-background-split,50%)}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background][data-marpit-advanced-background-split=right]>div[data-marpit-advanced-background-container]{margin-left:calc(100% - var(--marpit-advanced-background-split, 50%))}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]>figure{all:initial;background-position:center;background-repeat:no-repeat;background-size:cover;flex:auto;margin:0}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content],div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=pseudo]{background:transparent!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=pseudo],div#\:\$p>svg[data-marpit-svg]>foreignObject[data-marpit-advanced-background=pseudo]{pointer-events:none!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background-split]{width:100%;height:100%}</style></head><body><div class="bespoke-marp-osc"><button data-bespoke-marp-osc="prev" tabindex="-1" title="Previous slide">Previous slide</button><span data-bespoke-marp-osc="page"></span><button data-bespoke-marp-osc="next" tabindex="-1" title="Next slide">Next slide</button><button data-bespoke-marp-osc="fullscreen" tabindex="-1" title="Toggle fullscreen (f)">Toggle fullscreen</button><button data-bespoke-marp-osc="presenter" tabindex="-1" title="Open presenter view (p)">Open presenter view</button></div><div id=":$p"><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="1" data-class="no-watermark" data-theme="unpoly3-slides" class="no-watermark" style="--class:no-watermark;--theme:unpoly3-slides;"><div class="title">
<img src="./images/unpoly3.svg" alt="Unpoly 3" class="title--logo" />
<div class="title--author">
Henning Koch <a href="https://twitter.com/triskweline">@triskweline</a>
</div>
</div>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="2" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="about-unpoly">About Unpoly</h1>
<p><a href="https://unpoly.com">Unpoly</a> is an unobtrusive JavaScript framework for server-side web applications.<br />
It enables fast and flexible frontends while keeping rendering logic on the server.</p>
<p><strong>This presentation is for experienced Unpoly users<br />
who want to learn about the major changes in Unpoly 3.</strong></p>
<p>Unpoly 3 has been released on April 17th, 2023.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="3" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 class="topic">overview</h1>
<div class="row">
<div class="col" style="flex-grow: 1.5">
<h3 id="fixing-all-the-concurrency-issues">Fixing all the concurrency issues</h3>
<ul>
<li>User clicking faster than the server can respond</li>
<li>Multiple requests targeting the same fragment</li>
<li>Responses arrive in different order than requests</li>
<li>Forms where everything depends on everything else</li>
<li>Multiple users working on the same backend data (stale caches)</li>
<li>User losing connection while requests are in flight</li>
<li>Lie-Fi (spotty Wi-Fi, EDGE, tunnel)</li>
</ul>
</div>
<div class="col">
<h3 id="quality-of-life-improvements">Quality of life improvements</h3>
<ul>
<li>Optional targets</li>
<li>Idempotent <code>up.hello()</code></li>
<li>More control over <code>[up-hungry]</code></li>
<li>HTML5 data attributes</li>
<li>Extensive render callbacks</li>
<li>Strict target derivation</li>
<li>Log separated by user interaction</li>
<li>Foreign overlays</li>
</ul>
</div>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="4" data-class="topic" data-theme="unpoly3-slides" class="topic" style="--class:topic;--theme:unpoly3-slides;">
<h1 id="fixing-concurrency">Fixing concurrency</h1>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="5" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="cache-revalidation">Cache revalidation</h1>
<p>Unpoly has always cached <code>GET</code> responses for a few minutes.<br />
This allowed for instant navigation between pages we visited earlier, but had some issues:</p>
<ul>
<li>Users were sometimes seeing stale content.<br />
Because of this many projects configured cache expiry down to a few seconds.</li>
<li>Every non-GET request cleared the entire cache.<br />
This caused unnecessary cache misses after submitting a form.</li>
</ul>
<p>Unpoly 3 fixes all of this with <em>cache revalidation</em>.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="6" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="stale-while-revalidate">Stale while revalidate</h2>
<p>After rendering stale content from the cache, Unpoly 3 automatically reloads the fragment.</p>
<p>This process is called <em>cache revalidation</em>.</p>
<p>Unpoly renders twice:</p>
<ul>
<li>A first render pass from the cache (which may be stale)</li>
<li>A second render pass from the server (which is always fresh)</li>
</ul>
<p>This has many benefits:</p>
<ul>
<li>We can have long cache eviction times, allowing instant navigation for 90 minutes.</li>
<li>We no longer need to clear the cache after a form submission.<br />
We just mark all cache entries as stale.</li>
<li>Because we always revalidate cached content, the user always sees fresh content</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="7" data-class="no-watermark small-padding" data-theme="unpoly3-slides" class="no-watermark small-padding" style="--class:no-watermark small-padding;--theme:unpoly3-slides;"><img src="images/cache.svg" height="100%" />
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="8" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h3 id="upnetworkconfigcacheexpireage"><code>up.network.config.cacheExpireAge</code></h3>
<ul>
<li>When cached content exceeds this age we perform cache revalidation<br />
(reload a rendered fragment)</li>
<li>This should only cover the time between preloading and rendering.</li>
<li>Defaults to 15 seconds (down from 5 minutes in Unpoly 2)</li>
</ul>
<h3 id="upnetworkconfigcacheevictage"><code>up.network.config.cacheEvictAge</code></h3>
<ul>
<li>When cached content exceeds this age we delete it from the cache</li>
<li>This limit exists mostly to limit memory consumption.</li>
<li>Defaults to 90 minutes.</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="9" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="controlling-when-to-revalidate">Controlling when to revalidate</h2>
<p>You may configure what responses to revalidate, e.g. to exclude certain paths.</p>
<p>The default setting is:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js"><span class="hljs-comment">// A response older than up.network.config.cacheExpireAge is expired</span>
up.<span class="hljs-property">fragment</span>.<span class="hljs-property">config</span>.<span class="hljs-property">autoRevalidate</span> = <span class="hljs-function">(<span class="hljs-params">response</span>) =></span> response.<span class="hljs-property">expired</span>
</code></pre>
<p>You may also disable revalidation for an individual link:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/path"</span> <span class="hljs-attr">up-follow</span> <span class="hljs-attr">up-revalidate</span>=<span class="hljs-string">"false"</span>></span>
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="10" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="does-revalidation-cause-more-requests">Does revalidation cause more requests?</h2>
<p>With revalidation your app will make as many requests as a plain web app<br />
<strong>or</strong> as an Unpoly 2 app with short cache expiry.</p>
<p>Remember that many of our projects have configured cache expiry to "fix" stale content.</p>
<p><strong>Optionally</strong> your app can support <em>conditional requests</em> so reloading is effectively free for the server.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="11" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="conditional-request-support">Conditional request support</h1>
<p>Conditional requests is an old HTTP feature (<a href="https://datatracker.ietf.org/doc/html/rfc7232">RFC 7232</a>).</p>
<p>It lets browser ask for content newer than a known modification time,<br />
or content different from a known content hash.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="12" data-class="no-watermark" data-theme="unpoly3-slides" class="no-watermark" style="--class:no-watermark;--theme:unpoly3-slides;">
<h2 id="requesting-content-newer-than-a-known-modification-time">Requesting content newer than a known modification time</h2>
<p>Browser requests a URL for the first time:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-http"><span class="hljs-keyword">GET</span> <span class="hljs-string">/foo</span> <span class="hljs-meta">HTTP/1.1</span>
</code></pre>
<p>Server responds with content and last modification time (e.g. <code>#updated_at</code>):</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-http"><span class="hljs-meta">HTTP/1.1</span> <span class="hljs-number">200</span> OK
<span class="hljs-attribute">Last-Modified</span><span class="hljs-punctuation">: </span>Wed, 15 Nov 2000 13:11:22 GMT
...
</code></pre>
<p>When the browser revisits the same URL is echoes the earlier modification time:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-http"><span class="hljs-keyword">GET</span> <span class="hljs-string">/foo</span> <span class="hljs-meta">HTTP/1.1</span>
<span class="hljs-attribute">If-Modified-Since</span><span class="hljs-punctuation">: </span>Wed, 15 Nov 2000 13:11:22 GMT
</code></pre>
<p>The server checks modification time and may respond <strong>without content</strong>:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-http"><span class="hljs-meta">HTTP/1.1</span> <span class="hljs-number">304</span> Not Modified
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="13" data-class="no-watermark" data-theme="unpoly3-slides" class="no-watermark" style="--class:no-watermark;--theme:unpoly3-slides;">
<h2 id="requesting-content-changed-from-a-known-content-hash">Requesting content changed from a known content hash</h2>
<p>Browser requests a URL for the first time:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-http"><span class="hljs-keyword">GET</span> <span class="hljs-string">/foo</span> <span class="hljs-meta">HTTP/1.1</span>
</code></pre>
<p>Server responds with content and a hash over the underlying data:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-http"><span class="hljs-meta">HTTP/1.1</span> <span class="hljs-number">200</span> OK
<span class="hljs-attribute">ETag</span><span class="hljs-punctuation">: </span>"x234dff"
...
</code></pre>
<p>When the browser revisits the same URL is echoes the earlier hash:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-http"><span class="hljs-keyword">GET</span> <span class="hljs-string">/foo</span> <span class="hljs-meta">HTTP/1.1</span>
<span class="hljs-attribute">If-None-Match</span><span class="hljs-punctuation">: </span>"x234dff"
</code></pre>
<p>The server checks data hash and may respond <strong>without content</strong>:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-http"><span class="hljs-meta">HTTP/1.1</span> <span class="hljs-number">304</span> Not Modified
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="14" data-class="no-watermark" data-theme="unpoly3-slides" class="no-watermark" style="--class:no-watermark;--theme:unpoly3-slides;">
<h2 id="unpoly-3-supports-conditional-requests-when-reloading">Unpoly 3 supports conditional requests when reloading</h2>
<p>Unpoly remembers the <code>Last-Modified</code> and <code>ETag</code> headers a fragment was delivered with:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">'messages'</span> <span class="hljs-attr">up-time</span>=<span class="hljs-string">'Wed, 21 Oct 2015 07:28:00 GMT'</span> <span class="hljs-attr">etag</span>=<span class="hljs-string">'"x234dff"'</span>></span>
...
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
</code></pre>
<p>When the fragment is reloaded, Unpoly echoes these values using standard HTTP headers:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-http"><span class="hljs-keyword">GET</span> <span class="hljs-string">/messages</span> <span class="hljs-meta">HTTP/1.1</span>
<span class="hljs-attribute">X-Up-Target</span><span class="hljs-punctuation">: </span>.messages
<span class="hljs-attribute">If-Modified-Since</span><span class="hljs-punctuation">: </span>Wed, 21 Oct 2015 07:28:00 GMT
<span class="hljs-attribute">If-None-Match</span><span class="hljs-punctuation">: </span>"x234dff"
</code></pre>
<p>If no fresher data exists, the server may skip rendering and respond without content:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-http"><span class="hljs-meta">HTTP/1.1</span> <span class="hljs-number">304</span> Not Modified
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="15" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="modification-time-or-content-hash">Modification time or content hash?</h2>
<p>Servers can use both <code>Last-Modified</code> and <code>ETag</code>, but <code>ETag</code> always takes precedence.</p>
<p>It's easier to mix in additional data into an <code>ETag</code>, e.g. the ID of the logged in user or the currently deployed commit revision.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="16" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="benefits-of-implementing-conditional-requests">Benefits of implementing conditional requests</h2>
<p>Conditional requests can improve cases where the user re-visit a page, in particular:</p>
<ul>
<li>Cache revalidation</li>
<li>Polling with <a href="https://unpoly.com/up-poll"><code>[up-poll]</code></a></li>
<li></li>
</ul>
<p>By sending <code>304 Not Modified</code> for unchanged content you get:</p>
<ul>
<li>Reduced server load (no server-side rendering required)</li>
<li>Fewer data transmitted (think slow connections, mobile data plans)</li>
<li>Prevent unnecessary DOM swaps of near-identical content</li>
</ul>
<div class="admonition">
<p><strong>Note:</strong> Without conditional request support Unpoly will still
discard identical responses when revalidating. You may define additional
rules in <a href="https://unpoly.com/up.fragment.config#config.skipResponse"><code>up.fragment.config.skipResponse</code></a></p>
</div>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="17" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="span-classnegativechallengesspan-of-implementing-conditional-requests"><span class="negative">Challenges</span> of implementing conditional requests</h2>
<ul>
<li>Reloading the page during development may not always pick up changes</li>
<li>Pages that display many records must either<br />
(1) build ETags from multiple records or<br />
(2) propagate update timestamps through associations</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="18" data-class="rails-specific" data-theme="unpoly3-slides" class="rails-specific" style="--class:rails-specific;--theme:unpoly3-slides;">
<h1 id="implementing-conditional-requests-in-ruby-on-rails">Implementing conditional requests in Ruby on Rails</h1>
<p><strong>If you're not a Rails user</strong>, skip to a slide without the Rails logo in the corner <img class="emoji" draggable="false" alt="↗" src="https://cdn.jsdelivr.net/gh/jdecked/[email protected]/assets/svg/2197.svg" data-marp-twemoji=""/>.</p>
<p>While you can manually manage and compare <code>request.headers['ETag']</code> and <code>response.headers['ETag']</code> in your controllers, Rails ships with helpers to help deal with ETags.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="19" data-class="rails-specific" data-theme="unpoly3-slides" class="rails-specific" style="--class:rails-specific;--theme:unpoly3-slides;">
<h2 id="controller-example">Controller example</h2>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-ruby"><span class="hljs-keyword">class</span> <span class="hljs-title class_">PostsController</span> < <span class="hljs-title class_ inherited__">AppplicationController</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">show</span>
<span class="hljs-variable">@post</span> = <span class="hljs-title class_">Post</span>.find(params[<span class="hljs-symbol">:id</span>])
<span class="hljs-comment"># Produces ETag from (1) class name (2) <span class="hljs-doctag">@post</span>.id</span>
<span class="hljs-comment"># (3) <span class="hljs-doctag">@post</span>.updated_at (4) view template code (5) flashes.</span>
<span class="hljs-comment"># Renders 304 Not Modified if ETag matches If-None-Match header.</span>
fresh_when <span class="hljs-variable">@post</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">index</span>
<span class="hljs-variable">@posts</span> = <span class="hljs-title class_">Post</span>.order(<span class="hljs-symbol">created_at:</span> <span class="hljs-symbol">:desc</span>)
<span class="hljs-comment"># Produces ETag from (1) class name (2) scope conditions</span>
<span class="hljs-comment"># (3) <span class="hljs-doctag">@posts</span>.maximum(:updated_at) (4) view template code (5) flashes.</span>
<span class="hljs-comment"># Renders 304 Not Modified if ETag matches If-None-Match header.</span>
fresh_when <span class="hljs-variable">@posts</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="20" data-class="rails-specific" data-theme="unpoly3-slides" class="rails-specific" style="--class:rails-specific;--theme:unpoly3-slides;">
<h2 id="composing-an-etag-from-multiple-records">Composing an ETag from multiple records</h2>
<p>When associated records are rendered on the view they should (ideally) be included in the ETag:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-ruby"><span class="hljs-keyword">class</span> <span class="hljs-title class_">PostsController</span> < <span class="hljs-title class_ inherited__">ApplicationController</span>
etag { current_user&.id } <span class="hljs-comment"># Produce different ETags for different users</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">show</span>
<span class="hljs-variable">@post</span> = <span class="hljs-title class_">Post</span>.find(params[<span class="hljs-symbol">:id</span>])
<span class="hljs-comment"># The show template also renders the posts's author.</span>
fresh_when [<span class="hljs-variable">@post</span>, <span class="hljs-variable">@post</span>.author]
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">index</span>
<span class="hljs-variable">@posts</span> = <span class="hljs-title class_">Post</span>.order(<span class="hljs-symbol">created_at:</span> <span class="hljs-symbol">:desc</span>).preload(<span class="hljs-symbol">:author</span>).to_a
<span class="hljs-comment"># The index template also renders the post authors.</span>
fresh_when [*<span class="hljs-variable">@posts</span>, *<span class="hljs-variable">@posts</span>.map(&<span class="hljs-symbol">:author</span>)]
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="21" data-class="rails-specific" data-theme="unpoly3-slides" class="rails-specific" style="--class:rails-specific;--theme:unpoly3-slides;">
<h2 id="default-etags-in-rails">Default ETags in Rails</h2>
<p>Even without <code>fresh_when</code> Rails <a href="https://rdoc.info/github/rack/rack/Rack/ETag">produces a default ETag</a> by hashing the response body.</p>
<p><img class="emoji" draggable="false" alt="❌" src="https://cdn.jsdelivr.net/gh/jdecked/[email protected]/assets/svg/274c.svg" data-marp-twemoji=""/> You still pay the rendering time.<br />
<img class="emoji" draggable="false" alt="✅" src="https://cdn.jsdelivr.net/gh/jdecked/[email protected]/assets/svg/2705.svg" data-marp-twemoji=""/> You won't transmit unchanged HTML.<br />
<img class="emoji" draggable="false" alt="✅" src="https://cdn.jsdelivr.net/gh/jdecked/[email protected]/assets/svg/2705.svg" data-marp-twemoji=""/> You don't need to care what goes into an ETag.</p>
<p>When you find it hard to produce a correct ETag for a complex view, you may find it easier to make the view fast (using <a href="https://guides.rubyonrails.org/caching_with_rails.html#fragment-caching">fragment caching</a>) and rely on the default ETag.</p>
<div class="admonition">
<p><strong>Tip:</strong> The default <code>Rack::ETag</code> middleware has issues with random tokens (CSRF token, CSP nonce),
causing ETags to never match. Use our <a href="https://github.com/makandra/rack-steady_etag"><code>Rack::SteadyETag</code></a> gem to address this.</p>
</div>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="22" data-class="secondary-color" data-theme="unpoly3-slides" class="secondary-color" style="--class:secondary-color;--theme:unpoly3-slides;">
<h2 id="cache-revalidation-works-without-conditional-requests">Cache revalidation works without conditional requests</h2>
<p>Unpoly's cache revalidation will work <strong>with or without</strong> conditional request support.</p>
<p>If your app does not support conditional requests, cache revalidation will cause just as many requests as an Unpoly 2 app with short cache expiry.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="23" data-class="no-watermark" data-theme="unpoly3-slides" class="no-watermark" style="--class:no-watermark;--theme:unpoly3-slides;">
<h1 id="concurrent-updates-to-the-same-fragment">Concurrent updates to the same fragment</h1>
<div class="row">
<div class="col" style="flex-basis: auto">
<img src="images/layout-side-main.svg" style="height: 135px; margin: 0;" />
</div>
<div class="col" style="flex-basis: auto">
<p>When two requests target <code>main</code>, what should happen?</p>
<p>The answer to that changed throughout Unpoly's history.</p>
</div>
</div>
<h3 id="unpoly-1-do-nothing">Unpoly 1: Do nothing</h3>
<p>Responses would be rendered in whatever order they arrive.</p>
<h3 id="unpoly-2-abort-all-pending-requests-when-navigating">Unpoly 2: Abort all pending requests when navigating</h3>
<p>This mimics standard browser behavior, where clicking a link aborts earlier clicks.<br />
Unfortunately this also aborted background requests, or requests for unrelated regions like <code>side</code>.</p>
<h3 id="unpoly-3-abort-requests-within-the-targeted-fragment-only">Unpoly 3: Abort requests within the targeted fragment only</h3>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="24" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="targeting-a-fragment-will-abort-conflicting-requests">Targeting a fragment will abort conflicting requests</h2>
<p>Clicking this link will automatically cancel requests targeting <code>.region</code> or its descendants:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/path"</span> <span class="hljs-attr">up-target</span>=<span class="hljs-string">".region"</span>></span>
</code></pre>
<p>Same when rendering programmatically:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-title function_">render</span>({ <span class="hljs-attr">url</span>: <span class="hljs-string">'/path'</span>, <span class="hljs-attr">target</span>: <span class="hljs-string">'.region'</span> })
</code></pre>
<p>Aborting without rendering:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-property">fragment</span>.<span class="hljs-title function_">abort</span>(<span class="hljs-string">'.region'</span>)
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="25" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="what-about-preloading">What about preloading?</h2>
<p>Preloading never aborts targeted fragments.</p>
<p>Imperative preloading with <code>up.link.preload()</code> is no longer abortable by default in Unpoly 3.</p>
<p>You can use this to populate the cache while the user is navigating:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-title function_">compiler</span>(<span class="hljs-string">'.main-nav'</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params">nav</span>) {
nav.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-string">'a[href]'</span>).<span class="hljs-title function_">forEach</span>(up.<span class="hljs-property">link</span>.<span class="hljs-property">preload</span>)
})
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="26" data-class="no-watermark" data-theme="unpoly3-slides" class="no-watermark" style="--class:no-watermark;--theme:unpoly3-slides;">
<h1 id="forms-where-everything-depends-on-everything">Forms where everything depends on everything</h1>
<p>A common challenge in development are forms here fields have many dependencies on other fields:</p>
<img src="./images/form-from-hell.svg" alt="A form with many dependent fields" class="picture" style="width: 80%" />
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="27" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="we-can-build-this-with-up-validate-but">We can build this with <code>[up-validate]</code>, but.….</h2>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"/purchases"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">select</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"continent"</span> <span class="hljs-attr">up-validate</span>=<span class="hljs-string">"[name=country]"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">select</span>></span>
<span class="hljs-tag"><<span class="hljs-name">select</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"country"</span> <span class="hljs-attr">up-validate</span>=<span class="hljs-string">"[name=price]"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">select</span>></span>
<span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"weight"</span> <span class="hljs-attr">up-validate</span>=<span class="hljs-string">"[name=price]"</span>></span> kg
<span class="hljs-tag"><<span class="hljs-name">output</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"price"</span>></span>23 €<span class="hljs-tag"></<span class="hljs-name">output</span>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span>></span>Buy stamps<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"></<span class="hljs-name">form</span>></span>
</code></pre>
<p>This form has <b class="negative">race conditions</b> in Unpoly 2:</p>
<ul>
<li>User changes continent</li>
<li>Request 1 targeting <code>[name=country]</code> starts</li>
<li>User changes weight</li>
<li>Request 2 targeting <code>[name=price]</code> starts</li>
<li>User changes continent again</li>
<li>Request 3 targeting <code>[name=country]</code> starts</li>
<li>Three responses arrive and <span class="negative">render in random order <img class="emoji" draggable="false" alt="💥" src="https://cdn.jsdelivr.net/gh/jdecked/[email protected]/assets/svg/1f4a5.svg" data-marp-twemoji=""/></span></li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="28" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="disabling-form-elements-while-loading">Disabling form elements while loading</h2>
<p>Forms with <code>[up-disable]</code> attribute disable all fields and buttons while submitting or validating.<br />
This prevents user input while the form is loading:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">form</span> <span class="hljs-attr">up-submit</span> <span class="hljs-attr">up-disable</span>></span>
<span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span>></span> <span class="hljs-comment"><!-- will be disabled during submission --></span>
<span class="hljs-tag"><<span class="hljs-name">button</span>></span>Submit<span class="hljs-tag"></<span class="hljs-name">button</span>></span> <span class="hljs-comment"><!-- will be disabled during submission --></span>
<span class="hljs-tag"></<span class="hljs-name">form</span>></span>
</code></pre>
<p>You can also only disable the submit button:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">form</span> <span class="hljs-attr">up-submit</span> <span class="hljs-attr">up-disable</span>=<span class="hljs-string">"button"</span>></span>
</code></pre>
<p>Or any given CSS selector:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">form</span> <span class="hljs-attr">up-submit</span> <span class="hljs-attr">up-disable</span>=<span class="hljs-string">"input[name=email]"</span>></span>
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="29" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h3 id="form-before-user-input">Form before user input</h3>
<p>All fields are enabled.</p>
<img src="./images/form-from-hell.svg" alt="A form with many dependent fields" class="picture" style="width: 80%" />
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="30" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h3 id="user-selects-different-country">User selects different country</h3>
<p>All fields disable to prevent concurrent input.</p>
<img src="./images/form-from-hell.disabled.svg" alt="A form is disabled while validating" class="picture" style="width: 80%" />
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="31" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h3 id="validation-has-completed">Validation has completed</h3>
<p>All fields are re-enabled.</p>
<img src="./images/form-from-hell.re-enabled.svg" alt="A form is re-enabled after validation is done" class="picture" style="width: 80%" />
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="32" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="consistency-without-disabling">Consistency without disabling</h1>
<p>Sometimes we don't want to disable forms because of optics (gray fields)<br />
or to not prevent user input.</p>
<p>Unpoly 3 has a <b>second</b> solution for forms with many <code>[up-validate]</code> dependencies<br />
that does not require disabling.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="33" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="eventual-consistency-guarantee-for-codeup-validatecode">Eventual consistency guarantee for <code>[up-validate]</code></h2>
<ul>
<li>Multiple updates from <code>[up-validate]</code> or <code>up.validate()</code><br />
are batched into a single render pass with multiple targets.</li>
<li>Duplicate or nested targets are consolidated.</li>
<li>Unpoly guarantees only one concurrent validation request per form.<br />
Additional validations are queued until the current validation request has loaded.</li>
<li>The form will eventually show a consistent state,<br />
regardless how fast the user clicks or how slow the network is.</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="34" data-class="no-watermark" data-theme="unpoly3-slides" class="no-watermark" style="--class:no-watermark;--theme:unpoly3-slides;"><pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"/purchases"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">select</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"continent"</span> <span class="hljs-attr">up-validate</span>=<span class="hljs-string">"[name=country]"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">select</span>></span>
<span class="hljs-tag"><<span class="hljs-name">select</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"country"</span> <span class="hljs-attr">up-validate</span>=<span class="hljs-string">"[name=price]"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">select</span>></span>
<span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"weight"</span> <span class="hljs-attr">up-validate</span>=<span class="hljs-string">"[name=price]"</span>></span> kg
<span class="hljs-tag"><<span class="hljs-name">output</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"price"</span>></span>23 €<span class="hljs-tag"></<span class="hljs-name">output</span>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span>></span>Buy stamps<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"></<span class="hljs-name">form</span>></span>
</code></pre>
<div class="row" style="font-size: 0.9em">
<div class="col">
<h3 id="unpoly-2-span-classnegativerace-conditionsspan">Unpoly 2: <span class="negative">Race conditions</span></h3>
<ul>
<li>User changes continent</li>
<li>Request 1 for <code>[name=country]</code> starts</li>
<li>User changes weight</li>
<li>Request 2 for <code>[name=price]</code> starts</li>
<li>User changes continent again</li>
<li>Request 3 for <code>[name=country]</code> starts</li>
<li>Responses arrive and render in random order</li>
</ul>
</div>
<div class="col" style="flex-grow: 1.2">
<h3 id="unpoly-3-span-classpositiveeventual-consistencyspan">Unpoly 3: <span class="positive">Eventual consistency</span></h3>
<ul>
<li>User changes continent</li>
<li>Request 1 for <code>[name=country]</code> starts</li>
<li>User changes weight</li>
<li>User changes continent again</li>
<li>Response 1 received and rendered</li>
<li>Request 2 for <code>[name=price], [name=country]</code> starts</li>
<li>Response 2 received and rendered</li>
</ul>
</div>
</div>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="35" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="field-specific-watch-options">Field-specific watch options</h1>
<p>Every form field can configure options that affect both validation (<code>[up-validate]</code><br />
and JavaScript watchers (<code>up.observe()</code>, now <code>up.watch()</code>).</p>
<p>For instance, we can tell a field to validate while the user is typing.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="36" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="example">Example</h2>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"/purchases"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">select</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"continent"</span> <span class="hljs-attr">up-validate</span>=<span class="hljs-string">"[name=country]"</span> <span class="hljs-attr">up-watch-disable</span>=<span class="hljs-string">"[name=country]"</span>></span>
...
<span class="hljs-tag"></<span class="hljs-name">select</span>></span>
<span class="hljs-tag"><<span class="hljs-name">select</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"country"</span> <span class="hljs-attr">up-validate</span>=<span class="hljs-string">"[name=price]"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">select</span>></span>
<span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"weight"</span> <span class="hljs-attr">up-validate</span>=<span class="hljs-string">"[name=price]"</span> <span class="hljs-attr">up-watch-event</span>=<span class="hljs-string">"input"</span>></span> kg
<span class="hljs-tag"><<span class="hljs-name">output</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"price"</span>></span>23 €<span class="hljs-tag"></<span class="hljs-name">output</span>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span>></span>Buy stamps<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"></<span class="hljs-name">form</span>></span>
</code></pre>
<p>The <code>[up-watch-disable]</code> attribute disables the country select while new countries are loading after a continent changes.</p>
<p>The <code>[up-watch-event]</code> attribute updates the price while the user is typing in the weight field (instead of waiting until the field is blurred).</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="37" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<p>Options can be set for a field, the entire form or any container element.</p>
<h4 id="up-watch-event"><code>[up-watch-event]</code></h4>
<p>Which event triggers watch callbacks or validation (e.g. <code>input</code> or <code>change</code>).</p>
<h4 id="up-watch-delay"><code>[up-watch-delay]</code></h4>
<p>How many milliseconds to wait after a change before a watch callback or validation is run.</p>
<h4 id="up-watch-disable"><code>[up-watch-disable]</code></h4>
<p>Whether to disable this form (or any fragment) while an async watch callback or validation is running.</p>
<h4 id="up-watch-feedback"><code>[up-watch-feedback]</code></h4>
<p>Whether to set <code>.up-active</code> and <code>.up-loading</code> classes while an async watch callback or validation is running.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="38" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="handling-disconnects">Handling disconnects</h1>
<p>Unpoly 3 lets you handle connection loss with an <code>{ onOffline }</code> or <code>[up-on-offline]</code> callback:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"..."</span> <span class="hljs-attr">up-on-offline</span>=<span class="hljs-string">"if (confirm('Retry?') event.retry()"</span>></span>Post bid<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
</code></pre>
<p>You may also configure a global handler:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-title function_">on</span>(<span class="hljs-string">'up:fragment:offline'</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params">event</span>) {
<span class="hljs-keyword">if</span> (<span class="hljs-title function_">confirm</span>(<span class="hljs-string">'Retry?'</span>)) event.<span class="hljs-title function_">retry</span>()
})
</code></pre>
<p>You may also do something other than retrying, like substituting content:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-title function_">on</span>(<span class="hljs-string">'up:fragment:offline'</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params">event</span>) {
up.<span class="hljs-title function_">render</span>(event.<span class="hljs-property">renderOptions</span>.<span class="hljs-property">target</span>, { <span class="hljs-attr">content</span>: <span class="hljs-string">"You are offline."</span> })
})
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="39" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="handling-lie-fi">Handling <a href="https://www.urbandictionary.com/define.php?term=lie-fi">"Lie-Fi"</a></h2>
<p>Often our device reports a connection, but we're <em>effectively offline</em>:</p>
<ul>
<li>Smartphone in EDGE cell</li>
<li>Car drives into tunnel</li>
<li>Overcrowded Wi-fi with massive packet loss</li>
</ul>
<p>Unpoly 3 handles Lie-Fi with timeouts:</p>
<ul>
<li>All requests have a default timeout of 90 seconds (<code>up.network.config.timeout</code>)</li>
<li>Timeouts will now trigger <code>onOffline()</code> and use your offline handling</li>
<li>Customize timeouts per-request with <code>{ timeout }</code>, <code>[up-timeout]</code> options</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="40" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="expired-pages-remain-accessible-while-offline">Expired pages remain accessible while offline</h2>
<ul>
<li>Cached content will remain navigatable for 90 minutes</li>
<li>Revalidation will fail, but not change the page and trigger <code>onOffline()</code></li>
<li>Clicking uncached content will not change the page and trigger <code>onOffline()</code></li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="41" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="limitations">Limitations</h2>
<p>While Unpoly 3 lets you handle disconnects, it's not full "offline" support:</p>
<ul>
<li>To fill up the cache the device must be online for the first part of the session (warm start)</li>
<li>The cache is still in-memory and dies with the browser tab</li>
</ul>
<p>For a comprehensive offline experience (cold start) we recommend a <a href="https://web.dev/offline-fallback-page/">service worker</a><br />
or a canned solution like <a href="https://www.talater.com/upup/">UpUp</a> (no relation to Unpoly).</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="42" data-class="topic" data-theme="unpoly3-slides" class="topic" style="--class:topic;--theme:unpoly3-slides;">
<h1 id="quality-of-life">Quality of life</h1>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="43" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="optional-targets">Optional targets</h1>
<p>If you suffix a target selector with <code>:maybe</code> it will only be updated if there is a match in both the current page and server response (like <code>[up-hungry]</code>).</p>
<h2 id="example-1">Example</h2>
<p>The following would require fragments matching <code>.content</code> and <code>.navigation</code>.<br />
If <code>.flashes</code> is missing in either current page or server response, no error
is thrown.</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/post"</span> <span class="hljs-attr">up-target</span>=<span class="hljs-string">".content, .flashes:maybe, .navigation"</span>></span>
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="44" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="uphello-is-now-idempotent"><code>up.hello()</code> is now idempotent</h1>
<p>You can call <code>up.hello()</code> on the same element tree multiple times without the fear of side effects.</p>
<p>Unpoly guarantees that each compiler only ever runs once for a matching elements.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="45" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="late-compiler-registrations">Late compiler registrations</h2>
<p>You can now register compilers after content was rendered.<br />
Compilers registered after booting automatically run on current elements.</p>
<p>This makes it easier to split you compilers into multiple files that are then loaded as-needed.<br />
We plan to work on this further. See <a href="https://github.com/unpoly/unpoly/discussions/446">RFC: Reconciliation of head elements</a>.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="46" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="more-control-over-up-hungry">More control over <code>[up-hungry]</code></h1>
<p>Elements with an <code>[up-hungry]</code> attribute are updated whenever the server sends a matching element, even if the element isn't targeted.</p>
<p>Unpoly 3 lets you control which updates <code>[up-hungry]</code> will piggy-back on.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="47" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="controlling-which-ulayeru-to-watch">Controlling which <u>layer</u> to watch</h2>
<p>By default Unpoly only considers <code>[up-hungry]</code> fragments in the updating layer.</p>
<p>With <code>[up-if-layer=any]</code> a hungry fragment will be considered for updates in <em>any</em> layer.</p>
<h3 id="example-2">Example</h3>
<p>A use case is notification elements in the application layout:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flashes"</span> <span class="hljs-attr">up-hungry</span> <span class="hljs-attr">up-if-layer</span>=<span class="hljs-string">"any"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"unread-messages"</span> <span class="hljs-attr">up-hungry</span> <span class="hljs-attr">up-if-layer</span>=<span class="hljs-string">"any"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="48" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="updating-only-for-history-changes">Updating only for history changes</h2>
<p>By default Unpoly considers <code>[up-hungry]</code> fragments for any update in its layer.</p>
<p>With <code>[up-if-history]</code> a hungry fragment will only be updated when the history changes.</p>
<h3 id="example-3">Example</h3>
<p>A use case is a canonical <code><link></code> element that should only update when we're updating history,
but not when we update smaller fragments:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"canonical"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"..."</span> <span class="hljs-attr">up-hungry</span> <span class="hljs-attr">up-if-history</span> /></span>
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="49" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="attaching-data-with-html5-data-attributes">Attaching data with HTML5 data attributes</h1>
<p>Unpoly always had <a href="https://unpoly.com/up-data"><code>[up-data]</code></a> to attach structured data to an element.</p>
<p>This is verbose when we're attaching simple key/value pairs:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"user"</span> <span class="hljs-attr">up-data</span>=<span class="hljs-string">"<%= { name: @user.name }.to_json %>"</span>></span>
</code></pre>
<p>It would feel more natural to use HTML5 data attributes instead:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"user"</span> <span class="hljs-attr">data-name</span>=<span class="hljs-string">"<%= @user.name %>"</span>></span>
</code></pre>
<p>In Unpoly 3 data can be attached with <strong>both</strong> <code>[up-data]</code> and HTML5 data attributes.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="50" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<p>These three elements produce the same compiler data:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">up-data</span>=<span class="hljs-string">'{ "foo": "one", "bar": "two" }'</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">data-foo</span>=<span class="hljs-string">'one'</span> <span class="hljs-attr">data-bar</span>=<span class="hljs-string">'two'</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">up-data</span>=<span class="hljs-string">'{ "foo": "one" }'</span> <span class="hljs-attr">data-bar</span>=<span class="hljs-string">'bar'</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
</code></pre>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-title function_">compiler</span>(<span class="hljs-string">'div'</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params">element, data</span>) {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(data.<span class="hljs-property">foo</span>) <span class="hljs-comment">// is always "one"</span>
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(data.<span class="hljs-property">bar</span>) <span class="hljs-comment">// is always "two"</span>
})
</code></pre>
<div class="admonition">
<p><strong>Note</strong>: HTML5 data attributes are always flat objects with string values.<br />
If you need to serialize something like an array of numbers, use <code>[up-data]</code>.</p>
</div>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="51" data-class="rails-specific" data-theme="unpoly3-slides" class="rails-specific" style="--class:rails-specific;--theme:unpoly3-slides;">
<p><b>Ruby on Rails users</b> can now use the <code>{ data }</code> option of any element helper<br />
to pass data to compilers:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-ruby">content_tag(<span class="hljs-symbol">:div</span>, <span class="hljs-string">'...'</span>, <span class="hljs-symbol">data:</span> { <span class="hljs-symbol">foo:</span> <span class="hljs-string">'one'</span>, <span class="hljs-symbol">bar:</span> <span class="hljs-string">'two'</span> })
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="52" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="highlighting-the-targeted-fragment">Highlighting the targeted fragment</h1>
<p>Targeted fragments get an <code>.up-loading</code> class.</p>
<p>This lets you highlight the part of the screen that's loading.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="53" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="navigation-feedback-classes">Navigation feedback classes</h2>
<p>A link targeting a fragment <code>.target</code>:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/path"</span> <span class="hljs-attr">up-target</span>=<span class="hljs-string">".target"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"target"</span>></span>old text<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
</code></pre>
<p>While the request is loading the link gets <code>.up-active</code> and the target gets <code>.up-loading</code>:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/path"</span> <span class="hljs-attr">up-target</span>=<span class="hljs-string">".target"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"up-active"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"target"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"up-loading"</span>></span>old text<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
</code></pre>
<p>Once the fragment is updated all feedback classes are feedback removed:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/path"</span> <span class="hljs-attr">up-target</span>=<span class="hljs-string">".target"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"target"</span>></span>new text<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="54" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<img src="./images/form-from-hell.disabled.svg" alt="A form is disabled while validating" class="picture" style="width: 70%" />
<br />
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-css"><span class="hljs-selector-class">.up-loading</span> output {
<span class="hljs-attribute">color</span>: gray;
}
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="55" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="log-separated-by-user-interaction">Log separated by user interaction</h1>
<p>The log has a lot of debug information.<br />
It's often hard to find where the relevant output begins and ends.</p>
<p>The new log shows which user interaction triggered an event chain.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="56" data-class="no-watermark no-padding" data-theme="unpoly3-slides" class="no-watermark no-padding" style="--class:no-watermark no-padding;--theme:unpoly3-slides;"><img src="./images/log-interaction-event.png" alt="New log" class="picture" style="width: 82%; margin: auto;" />
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="57" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="playing-nice-with-foreign-overlays">Playing nice with foreign overlays</h1>
<p>Unpoly 2 sometimes clashes with overlays from other libraries ("foreign overlay")<br />
like Bootstrap or TinyMCE:</p>
<ul>
<li>Clicking a foreign overlay closes an Unpoly overlay</li>
<li>Unpoly steals focus from a foreign overlay</li>
</ul>
<p>This happens when foreign overlays look "on top" visually (<code>z-index: 99999999999</code>), but their elements attach to the <code><body></code>. For Unpoly this looks like content on the root layer.</p>
<p>This could often be fixed by attaching the foreign overlay to the correct Unpoly layer (pseudo-code):</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js"><span class="hljs-title class_">OtherOverlay</span>.<span class="hljs-title function_">open</span>({ <span class="hljs-attr">content</span>: <span class="hljs-string">'foo'</span>, <span class="hljs-title function_">onOpen</span>(<span class="hljs-params">overlay</span>) { up.<span class="hljs-property">layer</span>.<span class="hljs-property">element</span>.<span class="hljs-title function_">attach</span>(overlay) }})
</code></pre>
<p>However, the solution is custom to every library.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="58" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="making-unpoly-aware-of-foreign-overlays">Making Unpoly aware of foreign overlays</h2>
<p>You can push a selector into <code>up.layer.config.foreignOverlaySelectors</code> and Unpoly will no longer have layer-related opinions over that region. You no longer need to re-attach the foreign overlay element.</p>
<p>Example from <code>unpoly-bootstrap5.js</code>:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-property">layer</span>.<span class="hljs-property">config</span>.<span class="hljs-property">foreignOverlaySelectors</span>.<span class="hljs-title function_">push</span>(
<span class="hljs-string">'.modal'</span>,
<span class="hljs-string">'.popover'</span>,
<span class="hljs-string">'.dropdown-menu'</span>
)
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="59" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="more-control-over-the-progress-bar">More control over the progress bar</h1>
<p>Unpoly 2.1 has introduced a progress bar that shows while a request takes too long to load.</p>
<p>This may be unwanted for requests are loading in the background, or have longer load
times in the best of cases (e.g. a large report).</p>
<p>Unpoly 3 gives you more control over if and when the progress bar shows.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="60" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="background-requests">Background requests</h2>
<p>Pass <code>{ background: true }</code> or <code>[up-background]</code> when rendering or making a request</p>
<p>Background requests never trigger the progress bar.<br />
Background requests are also deprioritized.</p>
<h3 id="uses-cases-from-unpoly">Uses cases from Unpoly</h3>
<ul>
<li>Polling requests are background requests automatically</li>
<li>Preload requests are background requests automatically</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="61" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="optimistic-cache-partitioning">Optimistic cache partitioning</h1>
<p>Requests with the same URL and HTTP method, but different header values (e.g. <code>X-Up-Target</code>) now share the entry in Unpoly's <a href="/caching">client-side cache</a>.</p>
<p>If a server <a href="https://unpoly.com/optimizing-responses">optimizes its response</a>, all request headers that influenced the response should be listed in a <code>Vary</code> response header:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-ruby"><span class="hljs-keyword">if</span> request.headers[<span class="hljs-string">'X-Up-Target'</span>] == <span class="hljs-string">'.card'</span>
response.headers[<span class="hljs-string">'Vary'</span>] = <span class="hljs-string">'X-Up-Target'</span>
render <span class="hljs-string">'card'</span>, <span class="hljs-symbol">layout:</span> <span class="hljs-literal">false</span>
<span class="hljs-keyword">else</span>
render <span class="hljs-string">'app'</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>A <code>Vary</code> header tells Unpoly to partition its <a href="/caching">cache</a> for that URL so that each request header value gets a separate cache entries.</p>
<p>You can set a <code>Vary</code> header manually from your server-side code.<br />
You may also be using a library like <a href="https://github.com/unpoly/unpoly-rails">unpoly-rails</a> that sets the <code>Vary</code> header automatically.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="62" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="extensive-render-callbacks">Extensive render callbacks</h1>
<p>You may now pass callback functions to intervene at many points
in the rendering lifecycle.</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-title function_">render</span>({
<span class="hljs-attr">url</span>: <span class="hljs-string">'/path'</span>,
<span class="hljs-title function_">onLoaded</span>(<span class="hljs-params">event</span>) { <span class="hljs-comment">/* Content was loaded from cache or server */</span> },
<span class="hljs-title function_">focus</span>(<span class="hljs-params">fragment, opts</span>) { <span class="hljs-comment">/* Set focus */</span> },
<span class="hljs-title function_">scroll</span>(<span class="hljs-params">fragment, opts</span>) { <span class="hljs-comment">/* Set scroll positions */</span> },
<span class="hljs-title function_">onRendered</span>(<span class="hljs-params">result</span>) { <span class="hljs-comment">/* Fragment was updated */</span> },
<span class="hljs-title function_">onFailRendered</span>(<span class="hljs-params">result</span>) { <span class="hljs-comment">/* Fragment was updated from failed response */</span> },
<span class="hljs-title function_">onRevalidated</span>(<span class="hljs-params">result</span>) { <span class="hljs-comment">/* Stale content was re-rendered */</span> },
<span class="hljs-title function_">onFinished</span>(<span class="hljs-params">result</span>) { <span class="hljs-comment">/* All finished, including animation and revalidation */</span> }
<span class="hljs-title function_">onOffline</span>(<span class="hljs-params">event</span>) { <span class="hljs-comment">/* Disconnection or timeout */</span> },
<span class="hljs-title function_">onError</span>(<span class="hljs-params">error</span>) { <span class="hljs-comment">/* Any error */</span> }
})
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="63" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="strict-target-derivation">Strict target derivation</h1>
<p>Unpoly often needs to guess a target selector that will match an element.<br />
Some features that do this are <code>[up-poll]</code>, <code>up.reload()</code>, <code>[up-hungry]</code>.</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-title function_">reload</span>(element) <span class="hljs-comment">// Produces a target selector from the given element</span>
</code></pre>
<p>To build the selector, Unpoly 2 uses the following element properties in decreasing
order of priority:</p>
<ul>
<li>The element's <code>[up-id]</code> attribute</li>
<li>The element's <code>[id]</code> attribute</li>
<li>The element's <code>[name]</code> attribute</li>
<li>The element's <code>[class]</code> names, ignoring <code>up.fragment.config.badTargetClasses</code>.</li>
<li>The element's tag name</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="64" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="when-target-derivation-goes-wrong">When target derivation goes wrong</h2>
<p>The target derivation in Unpoly 2 sometimes produces a weak selector that won't uniquely identify the element:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"..."</span>></span>
<span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"canonical"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"..."</span> <span class="hljs-attr">up-hungry</span>></span>
</code></pre>
<p>Here the <code>[up-hungry]</code> element would targets <code>link</code>, matching the stylesheet instead. <img class="emoji" draggable="false" alt="💥" src="https://cdn.jsdelivr.net/gh/jdecked/[email protected]/assets/svg/1f4a5.svg" data-marp-twemoji=""/></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="65" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="new-target-derivation-patterns">New target derivation patterns</h2>
<p>Unpoly 3 lets you configure patterns to use for target derivation.<br />
The following patterns are configured by default:</p>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-property">fragment</span>.<span class="hljs-property">config</span>.<span class="hljs-property">targetDerivers</span> = [
<span class="hljs-string">'[up-id]'</span>, <span class="hljs-comment">// [up-id="foo"]</span>
<span class="hljs-string">'[id]'</span>, <span class="hljs-comment">// #foo</span>
<span class="hljs-string">'html'</span>, <span class="hljs-comment">// html</span>
<span class="hljs-string">'head'</span>, <span class="hljs-comment">// head</span>
<span class="hljs-string">'body'</span>, <span class="hljs-comment">// body</span>
<span class="hljs-string">'main'</span>, <span class="hljs-comment">// main</span>
<span class="hljs-string">'[up-main]'</span>, <span class="hljs-comment">// [up-main="root"]</span>
<span class="hljs-string">'link[rel]'</span>, <span class="hljs-comment">// link[rel="canonical"]</span>
<span class="hljs-string">'meta[property]'</span>, <span class="hljs-comment">// link[rel="canonical"]</span>
<span class="hljs-string">'*[name]'</span>, <span class="hljs-comment">// input[name="email"]</span>
<span class="hljs-string">'form[action]'</span>, <span class="hljs-comment">// form[action="/users"]</span>
<span class="hljs-string">'a[href]'</span>, <span class="hljs-comment">// a[href="/users/"]</span>
<span class="hljs-string">'[class]'</span>, <span class="hljs-comment">// .foo (filtered by up.fragment.config.badTargetClasses)</span>
]
</code></pre>
<p>Note that tag names are now only used for unique elements (like <code><body></code> or <code><main></code>).</p>
<p>You can also push a <code>Function(Element): string?</code> if your deriver can't be expressed in a pattern.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="66" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h2 id="derived-target-verification">Derived target verification</h2>
<ul>
<li>Unpoly 3 verifies if a derived targets will actually match the element.</li>
<li>If another element is matched, the next pattern is tried.</li>
<li>If no pattern produces a matching target, an error is thrown.</li>
</ul>
<p>This <em>may</em> throw an <code>up.CannotTarget</code> exception in existing apps with ambiguous selectors.<br />
This means your app is updating the wrong fragments!<br />
You should fix these bugs by setting an <code>[id]</code>, <code>[up-id]</code> or <code>[class]</code> attribute.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="67" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="ie11-removal-%F0%9F%8E%89">IE11 removal <img class="emoji" draggable="false" alt="🎉" src="https://cdn.jsdelivr.net/gh/jdecked/[email protected]/assets/svg/1f389.svg" data-marp-twemoji=""/></h1>
<p>Unpoly 3 will no longer boot on IE11 or <a href="https://en.wikipedia.org/wiki/EdgeHTML">legacy Edge</a>.<br />
If you need to support Internet Explorer 11, use Unpoly 2.</p>
<p>This allowed us to delete a lot of internal code.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="68" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<p>Public functions with a native replacement have been moved to <code>unpoly-migrate.js</code>:</p>
<table>
<thead>
<tr>
<th>Deprecated function</th>
<th>Native replacement</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>up.util.assign()</code></td>
<td><code>Object.assign()</code></td>
</tr>
<tr>
<td><code>up.util.values()</code></td>
<td><code>Object.values()</code></td>
</tr>
<tr>
<td><code>up.element.remove()</code></td>
<td><code>Element#remove()</code></td>
</tr>
<tr>
<td><code>up.element.matches()</code></td>
<td><code>Element#matches()</code></td>
</tr>
<tr>
<td><code>up.element.closest()</code></td>
<td><code>Element#closest()</code></td>
</tr>
<tr>
<td><code>up.element.replace()</code></td>
<td><code>Element#replaceWith()</code></td>
</tr>
<tr>
<td><code>up.element.all()</code></td>
<td><code>document.querySelectorAll()</code></td>
</tr>
<tr>
<td><code>up.element.toggleClass()</code></td>
<td><code>Element#classList.toggle()</code></td>
</tr>
<tr>
<td><code>up.element.isDetached()</code></td>
<td><code>!Element#isConnected</code></td>
</tr>
</tbody>
</table>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="69" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="many-other-features-and-fixes">Many other features and fixes</h1>
<p>See <a href="https://unpoly.com/changes/3.0.0">full CHANGELOG</a>.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="70" data-theme="unpoly3-slides" style="--theme:unpoly3-slides;">
<h1 id="new-documentation-guides">New documentation guides</h1>
<p>In our ongoing efforts to evolve Unpoly's documentation from an API reference to a guide, we have added many new documentation pages:</p>
<div class="row">
<div class="col" style="flex-grow: 1.5">
<ul>
<li><a href="/targeting-fragments">Targeting fragments</a></li>
<li><a href="/target-derivation">Target derivation</a></li>
<li><a href="/skipping-rendering">Skipping unnecessary rendering</a></li>
<li><a href="/render-hooks">Render hooks</a></li>
<li><a href="/aborting-requests">Aborting requests</a></li>
<li><a href="/failed-responses">Handling failed responses</a></li>
<li><a href="/data">Attaching data to elements</a></li>
<li><a href="/optimizing-responses">Optimizing responses</a></li>
<li><a href="/focus">Controlling focus</a></li>
<li><a href="/validation">Validating forms</a></li>
<li><a href="/disabling-forms">Disabling forms while working</a></li>
</ul>
</div>
<div class="col">
<ul>
<li><a href="/dependent-fields">Dependent fields</a></li>
<li><a href="/watch-options">Watch options</a></li>
<li><a href="/caching">Caching</a></li>
<li><a href="/network-issues">Handling network issues</a></li>
<li><a href="/conditional-requests">Conditional requests</a></li>
<li><a href="/loading-indicators">Loading indicators</a></li>
<li><a href="/analytics">Tracking page views</a></li>
<li><a href="/updating-history">Updating history</a></li>
<li><a href="/restoring-history">Restoring history</a></li>
<li><a href="/predefined-animations">Predefined animations</a></li>
<li><a href="/predefined-transitions">Predefined transitions</a></li>
</ul>
</div>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="71" data-class="topic secondary-color" data-theme="unpoly3-slides" class="topic secondary-color" style="--class:topic secondary-color;--theme:unpoly3-slides;">
<h1 id="upgrading">Upgrading</h1>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="72" data-class="secondary-color" data-theme="unpoly3-slides" class="secondary-color" style="--class:secondary-color;--theme:unpoly3-slides;">
<h1 id="expectations-for-upgrade-effort">Expectations for upgrade effort</h1>
<ul>
<li>Upgrade from v2 to v3 will be <em>much</em> smoother than going from v1 to v2.</li>
<li>No changes in HTML or CSS provided by Unpoly.</li>
<li>Almost all are breaking changes are polyfilled by <a href="https://unpoly.com/changes/upgrading"><code>unpoly-migrate.js</code></a>.</li>
<li>Unpoly 3 keeps aliases for deprecated APIs going back to 2016.<br />
You can upgrade from v1 to v3 (without going through v2).</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="73" data-class="secondary-color" data-theme="unpoly3-slides" class="secondary-color" style="--class:secondary-color;--theme:unpoly3-slides;">
<h1 id="recommended-upgrade-workflow">Recommended upgrade workflow</h1>
<ul>
<li>Integrate <a href="https://unpoly.com/changes/upgrading"><code>unpoly-migrate.js</code></a></li>
<li>Run your test suite. Fix all deprecation warnings.</li>
<li>Remove <code>unpoly-migrate.js</code>.</li>
<li>Go through the <a href="https://unpoly.com/changes/3.0.0">CHANGELOG for 3.0.0</a> and manually review all items marked with an <img class="emoji" draggable="false" alt="⚠️" src="https://cdn.jsdelivr.net/gh/jdecked/[email protected]/assets/svg/26a0.svg" data-marp-twemoji=""/> icon.</li>
<li><span class="tag">Optional</span> Remove user code no longer needed with Unpoly 3.</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="74" data-class="secondary-color" data-theme="unpoly3-slides" class="secondary-color" style="--class:secondary-color;--theme:unpoly3-slides;">
<h2 id="code-you-can-probably-remove-in-unpoly-3">Code you can probably remove in Unpoly 3</h2>
<ul>
<li>Workarounds to validate fields on <code>input</code> instead of <code>change</code></li>
<li>Workarounds for <code>[up-hungry]</code> elements in another layer (e.g. notification flashes)</li>
<li>Workarounds to make foreign overlays play nice with Unpoly layers</li>
<li>Workarounds to prevent the display of stale caches</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="75" data-class="no-watermark" data-theme="unpoly3-slides" class="no-watermark" style="--class:no-watermark;--theme:unpoly3-slides;">
<p>Since cached content is now revalidated, you may want to remove any workarounds to prevent the display of stale content:</p>
<h4 id="remove-short-expiry-times">Remove short expiry times</h4>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-property">network</span>.<span class="hljs-property">config</span>.<span class="hljs-property">cacheExpiry</span> = <span class="hljs-number">15_0000</span>
</code></pre>
<h4 id="remove-individual-links-bypassing-the-cache">Remove individual links bypassing the cache</h4>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-html"><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/path"</span> <span class="hljs-attr">up-follow</span> <span class="hljs-attr">up-cache</span>=<span class="hljs-string">"false"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
</code></pre>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-title function_">render</span>({ <span class="hljs-attr">url</span>: <span class="hljs-string">'/path'</span>, <span class="hljs-attr">cache</span>: <span class="hljs-literal">false</span> })
</code></pre>
<h4 id="remove-global-cache-exemptions">Remove global cache exemptions</h4>
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-js">up.<span class="hljs-property">network</span>.<span class="hljs-property">config</span>.<span class="hljs-property">autoCache</span> = <span class="hljs-function">(<span class="hljs-params">request</span>) =></span> !request.<span class="hljs-property">url</span> === <span class="hljs-string">'/dashboard'</span> && ...
</code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="76" data-class="no-watermark" data-theme="unpoly3-slides" class="no-watermark" style="--class:no-watermark;--theme:unpoly3-slides;"><div class="title">
<img src="./images/unpoly3.svg" alt="Unpoly 3" class="title--logo" />
<div class="title--author">
Henning Koch <a href="https://twitter.com/triskweline">@triskweline</a>
</div>
</div>
</section>
<script>!function(){"use strict";const t={h1:{proto:()=>HTMLHeadingElement,attrs:{role:"heading","aria-level":"1"},style:"display: block; font-size: 2em; margin-block-start: 0.67em; margin-block-end: 0.67em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h2:{proto:()=>HTMLHeadingElement,attrs:{role:"heading","aria-level":"2"},style:"display: block; font-size: 1.5em; margin-block-start: 0.83em; margin-block-end: 0.83em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h3:{proto:()=>HTMLHeadingElement,attrs:{role:"heading","aria-level":"3"},style:"display: block; font-size: 1.17em; margin-block-start: 1em; margin-block-end: 1em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h4:{proto:()=>HTMLHeadingElement,attrs:{role:"heading","aria-level":"4"},style:"display: block; margin-block-start: 1.33em; margin-block-end: 1.33em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h5:{proto:()=>HTMLHeadingElement,attrs:{role:"heading","aria-level":"5"},style:"display: block; font-size: 0.83em; margin-block-start: 1.67em; margin-block-end: 1.67em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h6:{proto:()=>HTMLHeadingElement,attrs:{role:"heading","aria-level":"6"},style:"display: block; font-size: 0.67em; margin-block-start: 2.33em; margin-block-end: 2.33em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},span:{proto:()=>HTMLSpanElement},pre:{proto:()=>HTMLElement,style:"display: block; font-family: monospace; white-space: pre; margin: 1em 0; --marp-auto-scaling-white-space: pre;"}},e="data-marp-auto-scaling-wrapper",i="data-marp-auto-scaling-svg",n="data-marp-auto-scaling-container";class s extends HTMLElement{constructor(){super(),this.svgPreserveAspectRatio="xMinYMid meet";const t=t=>([e])=>{const{width:i,height:n}=e.contentRect;this[t]={width:i,height:n},this.updateSVGRect()};this.attachShadow({mode:"open"}),this.containerObserver=new ResizeObserver(t("containerSize")),this.wrapperObserver=new ResizeObserver(((...e)=>{t("wrapperSize")(...e),this.flushSvgDisplay()}))}static get observedAttributes(){return["data-downscale-only"]}connectedCallback(){var t,s,o,r,a;this.shadowRoot.innerHTML=`\n<style>\n svg[${i}] { display: block; width: 100%; height: auto; vertical-align: top; }\n span[${n}] { display: table; white-space: var(--marp-auto-scaling-white-space, nowrap); width: max-content; }\n</style>\n<div ${e}>\n <svg part="svg" ${i}>\n <foreignObject><span ${n}><slot></slot></span></foreignObject>\n </svg>\n</div>\n `.split(/\n\s*/).join(""),this.wrapper=null!==(t=this.shadowRoot.querySelector(`div[${e}]`))&&void 0!==t?t:void 0;const l=this.svg;this.svg=null!==(o=null===(s=this.wrapper)||void 0===s?void 0:s.querySelector(`svg[${i}]`))&&void 0!==o?o:void 0,this.svg!==l&&(this.svgComputedStyle=this.svg?window.getComputedStyle(this.svg):void 0),this.container=null!==(a=null===(r=this.svg)||void 0===r?void 0:r.querySelector(`span[${n}]`))&&void 0!==a?a:void 0,this.observe()}disconnectedCallback(){this.svg=void 0,this.svgComputedStyle=void 0,this.wrapper=void 0,this.container=void 0,this.observe()}attributeChangedCallback(){this.observe()}flushSvgDisplay(){const{svg:t}=this;t&&(t.style.display="inline",requestAnimationFrame((()=>{t.style.display=""})))}observe(){this.containerObserver.disconnect(),this.wrapperObserver.disconnect(),this.wrapper&&this.wrapperObserver.observe(this.wrapper),this.container&&this.containerObserver.observe(this.container),this.svgComputedStyle&&this.observeSVGStyle(this.svgComputedStyle)}observeSVGStyle(t){const e=()=>{const i=(()=>{const e=t.getPropertyValue("--preserve-aspect-ratio");if(e)return e.trim();return`x${(({textAlign:t,direction:e})=>{if(t.endsWith("left"))return"Min";if(t.endsWith("right"))return"Max";if("start"===t||"end"===t){let i="rtl"===e;return"end"===t&&(i=!i),i?"Max":"Min"}return"Mid"})(t)}YMid meet`})();i!==this.svgPreserveAspectRatio&&(this.svgPreserveAspectRatio=i,this.updateSVGRect()),t===this.svgComputedStyle&&requestAnimationFrame(e)};e()}updateSVGRect(){var t,e,i,n,s,o,r;let a=Math.ceil(null!==(e=null===(t=this.containerSize)||void 0===t?void 0:t.width)&&void 0!==e?e:0);const l=Math.ceil(null!==(n=null===(i=this.containerSize)||void 0===i?void 0:i.height)&&void 0!==n?n:0);void 0!==this.dataset.downscaleOnly&&(a=Math.max(a,null!==(o=null===(s=this.wrapperSize)||void 0===s?void 0:s.width)&&void 0!==o?o:0));const c=null===(r=this.svg)||void 0===r?void 0:r.querySelector(":scope > foreignObject");if(null==c||c.setAttribute("width",`${a}`),null==c||c.setAttribute("height",`${l}`),this.svg&&(this.svg.setAttribute("viewBox",`0 0 ${a} ${l}`),this.svg.setAttribute("preserveAspectRatio",this.svgPreserveAspectRatio),this.svg.style.height=a<=0||l<=0?"0":""),this.container){const t=this.svgPreserveAspectRatio.toLowerCase();this.container.style.marginLeft=t.startsWith("xmid")||t.startsWith("xmax")?"auto":"0",this.container.style.marginRight=t.startsWith("xmi")?"auto":"0"}}}const o=(t,{attrs:e={},style:i})=>class extends t{constructor(...t){super(...t);for(const[t,i]of Object.entries(e))this.hasAttribute(t)||this.setAttribute(t,i);this.attachShadow({mode:"open"})}static get observedAttributes(){return["data-auto-scaling"]}connectedCallback(){this._update()}attributeChangedCallback(){this._update()}_update(){const t=i?`<style>:host { ${i} }</style>`:"";let e="<slot></slot>";const{autoScaling:n}=this.dataset;if(void 0!==n){e=`<marp-auto-scaling exportparts="svg:auto-scaling" ${"downscale-only"===n?"data-downscale-only":""}>${e}</marp-auto-scaling>`}this.shadowRoot.innerHTML=t+e}};let r;const a=Symbol();let l;const c="marpitSVGPolyfill:setZoomFactor,",d=Symbol(),g=Symbol();const h=()=>{const t="Apple Computer, Inc."===navigator.vendor,e=t?[u]:[],i={then:e=>(t?(async()=>{if(void 0===l){const t=document.createElement("canvas");t.width=10,t.height=10;const e=t.getContext("2d"),i=new Image(10,10),n=new Promise((t=>{i.addEventListener("load",(()=>t()))}));i.crossOrigin="anonymous",i.src="data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2210%22%20height%3D%2210%22%20viewBox%3D%220%200%201%201%22%3E%3CforeignObject%20width%3D%221%22%20height%3D%221%22%20requiredExtensions%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml%22%3E%3Cdiv%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml%22%20style%3D%22width%3A%201px%3B%20height%3A%201px%3B%20background%3A%20red%3B%20position%3A%20relative%22%3E%3C%2Fdiv%3E%3C%2FforeignObject%3E%3C%2Fsvg%3E",await n,e.drawImage(i,0,0),l=e.getImageData(5,5,1,1).data[3]<128}return l})().then((t=>{null==e||e(t?[u]:[])})):null==e||e([]),i)};return Object.assign(e,i)};let p,m;function u(t){const e="object"==typeof t&&t.target||document,i="object"==typeof t?t.zoom:t;window[g]||(Object.defineProperty(window,g,{configurable:!0,value:!0}),document.body.style.zoom=1.0001,document.body.offsetHeight,document.body.style.zoom=1,window.addEventListener("message",(({data:t,origin:e})=>{if(e===window.origin)try{if(t&&"string"==typeof t&&t.startsWith(c)){const[,e]=t.split(","),i=Number.parseFloat(e);Number.isNaN(i)||(m=i)}}catch(t){console.error(t)}})));let n=!1;Array.from(e.querySelectorAll("svg[data-marpit-svg]"),(t=>{var e,s,o,r;t.style.transform||(t.style.transform="translateZ(0)");const a=i||m||t.currentScale||1;p!==a&&(p=a,n=a);const l=t.getBoundingClientRect(),{length:c}=t.children;for(let i=0;i<c;i+=1){const n=t.children[i];if(n.getScreenCTM){const t=n.getScreenCTM();if(t){const i=null!==(s=null===(e=n.x)||void 0===e?void 0:e.baseVal.value)&&void 0!==s?s:0,c=null!==(r=null===(o=n.y)||void 0===o?void 0:o.baseVal.value)&&void 0!==r?r:0,d=n.children.length;for(let e=0;e<d;e+=1){const s=n.children[e];if("SECTION"===s.tagName){const{style:e}=s;e.transformOrigin||(e.transformOrigin=`${-i}px ${-c}px`),e.transform=`scale(${a}) matrix(${t.a}, ${t.b}, ${t.c}, ${t.d}, ${t.e-l.left}, ${t.f-l.top}) translateZ(0.0001px)`;break}}}}}})),!1!==n&&Array.from(e.querySelectorAll("iframe"),(({contentWindow:t})=>{null==t||t.postMessage(`${c}${n}`,"null"===window.origin?"*":window.origin)}))}function v({once:t=!1,target:e=document}={}){const i=function(t=document){if(t[d])return t[d];let e=!0;const i=()=>{e=!1,delete t[d]};Object.defineProperty(t,d,{configurable:!0,value:i});let n=[],s=!1;(async()=>{try{n=await h()}finally{s=!0}})();const o=()=>{for(const e of n)e({target:t});s&&0===n.length||e&&window.requestAnimationFrame(o)};return o(),i}(e);return t?(i(),()=>{}):i}p=1,m=void 0;const b=Symbol(),w=(e=document)=>{if("undefined"==typeof window)throw new Error("Marp Core's browser script is valid only in browser context.");if(((e=document)=>{const i=window[a];i||customElements.define("marp-auto-scaling",s);for(const n of Object.keys(t)){const s=`marp-${n}`,a=t[n].proto();null!=r||(r=!!document.createElement("div",{is:"marp-auto-scaling"}).outerHTML.startsWith("<div is")),r&&a!==HTMLElement?i||customElements.define(s,o(a,{style:t[n].style}),{extends:n}):(i||customElements.define(s,o(HTMLElement,t[n])),e.querySelectorAll(`${n}[is="${s}"]`).forEach((t=>{t.outerHTML=t.outerHTML.replace(new RegExp(`^<${n}`,"i"),`<${s}`).replace(new RegExp(`</${n}>$`,"i"),`</${s}>`)})))}window[a]=!0})(e),e[b])return e[b];const i=v({target:e}),n=()=>{i(),delete e[b]},l=Object.assign(n,{cleanup:n,update:()=>w(e)});return Object.defineProperty(e,b,{configurable:!0,value:l}),l},y=document.currentScript;w(y?y.getRootNode():document)}();
</script></foreignObject></svg></div><script>/*!! License: https://unpkg.com/@marp-team/[email protected]/lib/bespoke.js.LICENSE.txt */
!function(){"use strict";function e(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var t={from:function(e,t){var n,r=1===(e.parent||e).nodeType?e.parent||e:document.querySelector(e.parent||e),o=[].filter.call("string"==typeof e.slides?r.querySelectorAll(e.slides):e.slides||r.children,(function(e){return"SCRIPT"!==e.nodeName})),i={},a=function(e,t){return(t=t||{}).index=o.indexOf(e),t.slide=e,t},s=function(e,t){i[e]=(i[e]||[]).filter((function(e){return e!==t}))},l=function(e,t){return(i[e]||[]).reduce((function(e,n){return e&&!1!==n(t)}),!0)},c=function(e,t){o[e]&&(n&&l("deactivate",a(n,t)),n=o[e],l("activate",a(n,t)))},d=function(e,t){var r=o.indexOf(n)+e;l(e>0?"next":"prev",a(n,t))&&c(r,t)},u={off:s,on:function(e,t){return(i[e]||(i[e]=[])).push(t),s.bind(null,e,t)},fire:l,slide:function(e,t){if(!arguments.length)return o.indexOf(n);l("slide",a(o[e],t))&&c(e,t)},next:d.bind(null,1),prev:d.bind(null,-1),parent:r,slides:o,destroy:function(e){l("destroy",a(n,e)),i={}}};return(t||[]).forEach((function(e){e(u)})),n||c(0),u}},n=e(t);const r=document.body,o=(...e)=>history.replaceState(...e),i="presenter",a="next",s=["",i,a],l="bespoke-marp-",c=`data-${l}`,d=(e,{protocol:t,host:n,pathname:r,hash:o}=location)=>{const i=e.toString();return`${t}//${n}${r}${i?"?":""}${i}${o}`},u=()=>r.dataset.bespokeView,f=e=>new URLSearchParams(location.search).get(e),m=(e,t={})=>{var n;const r={location,setter:o,...t},i=new URLSearchParams(r.location.search);for(const t of Object.keys(e)){const n=e[t];"string"==typeof n?i.set(t,n):i.delete(t)}try{r.setter({...null!==(n=window.history.state)&&void 0!==n?n:{}},"",d(i,r.location))}catch(e){console.error(e)}},g=(()=>{const e="bespoke-marp";try{return localStorage.setItem(e,e),localStorage.removeItem(e),!0}catch(e){return!1}})(),p=e=>{try{return localStorage.getItem(e)}catch(e){return null}},v=(e,t)=>{try{return localStorage.setItem(e,t),!0}catch(e){return!1}},h=e=>{try{return localStorage.removeItem(e),!0}catch(e){return!1}},y=(e,t)=>{const n="aria-hidden";t?e.setAttribute(n,"true"):e.removeAttribute(n)},b=e=>{e.parent.classList.add(`${l}parent`),e.slides.forEach((e=>e.classList.add(`${l}slide`))),e.on("activate",(t=>{const n=`${l}active`,r=t.slide,o=r.classList,i=!o.contains(n);if(e.slides.forEach((e=>{e.classList.remove(n),y(e,!0)})),o.add(n),y(r,!1),i){const e=`${n}-ready`;o.add(e),document.body.clientHeight,o.remove(e)}}))},w=e=>{let t=0,n=0;Object.defineProperty(e,"fragments",{enumerable:!0,value:e.slides.map((e=>[null,...e.querySelectorAll("[data-marpit-fragment]")]))});const r=r=>void 0!==e.fragments[t][n+r],o=(r,o)=>{t=r,n=o,e.fragments.forEach(((e,t)=>{e.forEach(((e,n)=>{if(null==e)return;const i=t<r||t===r&&n<=o;e.setAttribute(`${c}fragment`,(i?"":"in")+"active");const a=`${c}current-fragment`;t===r&&n===o?e.setAttribute(a,"current"):e.removeAttribute(a)}))})),e.fragmentIndex=o;const i={slide:e.slides[r],index:r,fragments:e.fragments[r],fragmentIndex:o};e.fire("fragment",i)};e.on("next",(({fragment:i=!0})=>{if(i){if(r(1))return o(t,n+1),!1;const i=t+1;e.fragments[i]&&o(i,0)}else{const r=e.fragments[t].length;if(n+1<r)return o(t,r-1),!1;const i=e.fragments[t+1];i&&o(t+1,i.length-1)}})),e.on("prev",(({fragment:i=!0})=>{if(r(-1)&&i)return o(t,n-1),!1;const a=t-1;e.fragments[a]&&o(a,e.fragments[a].length-1)})),e.on("slide",(({index:t,fragment:n})=>{let r=0;if(void 0!==n){const o=e.fragments[t];if(o){const{length:e}=o;r=-1===n?e-1:Math.min(Math.max(n,0),e-1)}}o(t,r)})),o(0,0)},x=document,k=()=>!(!x.fullscreenEnabled&&!x.webkitFullscreenEnabled),$=()=>!(!x.fullscreenElement&&!x.webkitFullscreenElement),E=e=>{e.fullscreen=()=>{k()&&(async()=>{return $()?null===(e=x.exitFullscreen||x.webkitExitFullscreen)||void 0===e?void 0:e.call(x):((e=x.body)=>{var t;return null===(t=e.requestFullscreen||e.webkitRequestFullscreen)||void 0===t?void 0:t.call(e)})();var e})()},document.addEventListener("keydown",(t=>{"f"!==t.key&&"F11"!==t.key||t.altKey||t.ctrlKey||t.metaKey||!k()||(e.fullscreen(),t.preventDefault())}))},L=`${l}inactive`,S=(e=2e3)=>({parent:t,fire:n})=>{const r=t.classList,o=e=>n(`marp-${e?"":"in"}active`);let i;const a=()=>{i&&clearTimeout(i),i=setTimeout((()=>{r.add(L),o()}),e),r.contains(L)&&(r.remove(L),o(!0))};for(const e of["mousedown","mousemove","touchend"])document.addEventListener(e,a);setTimeout(a,0)},P=["AUDIO","BUTTON","INPUT","SELECT","TEXTAREA","VIDEO"],_=e=>{e.parent.addEventListener("keydown",(e=>{if(!e.target)return;const t=e.target;(P.includes(t.nodeName)||"true"===t.contentEditable)&&e.stopPropagation()}))},T=e=>{window.addEventListener("load",(()=>{for(const t of e.slides){const e=t.querySelector("marp-auto-scaling, [data-auto-scaling], [data-marp-fitting]");t.setAttribute(`${c}load`,e?"":"hideable")}}))},I=({interval:e=250}={})=>t=>{document.addEventListener("keydown",(e=>{if(" "===e.key&&e.shiftKey)t.prev();else if("ArrowLeft"===e.key||"ArrowUp"===e.key||"PageUp"===e.key)t.prev({fragment:!e.shiftKey});else if(" "!==e.key||e.shiftKey)if("ArrowRight"===e.key||"ArrowDown"===e.key||"PageDown"===e.key)t.next({fragment:!e.shiftKey});else if("End"===e.key)t.slide(t.slides.length-1,{fragment:-1});else{if("Home"!==e.key)return;t.slide(0)}else t.next();e.preventDefault()}));let n,r,o=0;t.parent.addEventListener("wheel",(i=>{let a=!1;const s=(e,t)=>{e&&(a=a||((e,t)=>((e,t)=>{const n="X"===t?"Width":"Height";return e[`client${n}`]<e[`scroll${n}`]})(e,t)&&((e,t)=>{const{overflow:n}=e,r=e[`overflow${t}`];return"auto"===n||"scroll"===n||"auto"===r||"scroll"===r})(getComputedStyle(e),t))(e,t)),(null==e?void 0:e.parentElement)&&s(e.parentElement,t)};if(0!==i.deltaX&&s(i.target,"X"),0!==i.deltaY&&s(i.target,"Y"),a)return;i.preventDefault();const l=Math.sqrt(i.deltaX**2+i.deltaY**2);if(void 0!==i.wheelDelta){if(void 0===i.webkitForce&&Math.abs(i.wheelDelta)<40)return;if(i.deltaMode===i.DOM_DELTA_PIXEL&&l<4)return}else if(i.deltaMode===i.DOM_DELTA_PIXEL&&l<12)return;r&&clearTimeout(r),r=setTimeout((()=>{n=0}),e);const c=Date.now()-o<e,d=l<=n;if(n=l,c||d)return;let u;(i.deltaX>0||i.deltaY>0)&&(u="next"),(i.deltaX<0||i.deltaY<0)&&(u="prev"),u&&(t[u](),o=Date.now())}))},M=(e=`.${l}osc`)=>{const t=document.querySelector(e);if(!t)return()=>{};const n=(e,n)=>{t.querySelectorAll(`[${c}osc=${JSON.stringify(e)}]`).forEach(n)};return k()||n("fullscreen",(e=>e.style.display="none")),g||n("presenter",(e=>{e.disabled=!0,e.title="Presenter view is disabled due to restricted localStorage."})),e=>{t.addEventListener("click",(t=>{if(t.target instanceof HTMLElement){const{bespokeMarpOsc:n}=t.target.dataset;n&&t.target.blur();const r={fragment:!t.shiftKey};"next"===n?e.next(r):"prev"===n?e.prev(r):"fullscreen"===n?null==e||e.fullscreen():"presenter"===n&&e.openPresenterView()}})),e.parent.appendChild(t),e.on("activate",(({index:t})=>{n("page",(n=>n.textContent=`Page ${t+1} of ${e.slides.length}`))})),e.on("fragment",(({index:t,fragments:r,fragmentIndex:o})=>{n("prev",(e=>e.disabled=0===t&&0===o)),n("next",(n=>n.disabled=t===e.slides.length-1&&o===r.length-1))})),e.on("marp-active",(()=>y(t,!1))),e.on("marp-inactive",(()=>y(t,!0))),k()&&(e=>{for(const t of["","webkit"])x.addEventListener(t+"fullscreenchange",e)})((()=>n("fullscreen",(e=>e.classList.toggle("exit",k()&&$())))))}},O=e=>{window.addEventListener("message",(t=>{if(t.origin!==window.origin)return;const[n,r]=t.data.split(":");if("navigate"===n){const[t,n]=r.split(",");let o=Number.parseInt(t,10),i=Number.parseInt(n,10)+1;i>=e.fragments[o].length&&(o+=1,i=0),e.slide(o,{fragment:i})}}))};var A=["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"];let C=e=>String(e).replace(/[&<>"']/g,(e=>`&${D[e]};`)),D={"&":"amp","<":"lt",">":"gt",'"':"quot","'":"apos"},N="dangerouslySetInnerHTML",B={className:"class",htmlFor:"for"},q={};function K(e,t){let n=[],r="";t=t||{};for(let e=arguments.length;e-- >2;)n.push(arguments[e]);if("function"==typeof e)return t.children=n.reverse(),e(t);if(e){if(r+="<"+e,t)for(let e in t)!1!==t[e]&&null!=t[e]&&e!==N&&(r+=` ${B[e]?B[e]:C(e)}="${C(t[e])}"`);r+=">"}if(-1===A.indexOf(e)){if(t[N])r+=t[N].__html;else for(;n.length;){let e=n.pop();if(e)if(e.pop)for(let t=e.length;t--;)n.push(e[t]);else r+=!0===q[e]?e:C(e)}r+=e?`</${e}>`:""}return q[r]=!0,r}const j=({children:e})=>K(null,null,...e),F=`${l}presenter-`,V={container:`${F}container`,dragbar:`${F}dragbar-container`,next:`${F}next`,nextContainer:`${F}next-container`,noteContainer:`${F}note-container`,noteWrapper:`${F}note-wrapper`,noteButtons:`${F}note-buttons`,infoContainer:`${F}info-container`,infoPage:`${F}info-page`,infoPageText:`${F}info-page-text`,infoPagePrev:`${F}info-page-prev`,infoPageNext:`${F}info-page-next`,noteButtonsBigger:`${F}note-bigger`,noteButtonsSmaller:`${F}note-smaller`,infoTime:`${F}info-time`,infoTimer:`${F}info-timer`},U=e=>{const{title:t}=document;document.title="[Presenter view]"+(t?` - ${t}`:"");const n={},r=e=>(n[e]=n[e]||document.querySelector(`.${e}`),n[e]);document.body.appendChild((e=>{const t=document.createElement("div");return t.className=V.container,t.appendChild(e),t.insertAdjacentHTML("beforeend",K(j,null,K("div",{class:V.nextContainer},K("iframe",{class:V.next,src:"?view=next"})),K("div",{class:V.dragbar}),K("div",{class:V.noteContainer},K("div",{class:V.noteWrapper}),K("div",{class:V.noteButtons},K("button",{class:V.noteButtonsSmaller,tabindex:"-1",title:"Smaller notes font size"},"Smaller notes font size"),K("button",{class:V.noteButtonsBigger,tabindex:"-1",title:"Bigger notes font size"},"Bigger notes font size"))),K("div",{class:V.infoContainer},K("div",{class:V.infoPage},K("button",{class:V.infoPagePrev,tabindex:"-1",title:"Previous"},"Previous"),K("span",{class:V.infoPageText}),K("button",{class:V.infoPageNext,tabindex:"-1",title:"Next"},"Next")),K("time",{class:V.infoTime,title:"Current time"}),K("time",{class:V.infoTimer,title:"Timer"})))),t})(e.parent)),(e=>{let t=!1;r(V.dragbar).addEventListener("mousedown",(()=>{t=!0,r(V.dragbar).classList.add("active")})),window.addEventListener("mouseup",(()=>{t=!1,r(V.dragbar).classList.remove("active")})),window.addEventListener("mousemove",(e=>{if(!t)return;const n=e.clientX/document.documentElement.clientWidth*100;r(V.container).style.setProperty("--bespoke-marp-presenter-split-ratio",`${Math.max(0,Math.min(100,n))}%`)})),r(V.nextContainer).addEventListener("click",(()=>e.next()));const n=r(V.next),o=(i=n,(e,t)=>{var n;return null===(n=i.contentWindow)||void 0===n?void 0:n.postMessage(`navigate:${e},${t}`,"null"===window.origin?"*":window.origin)});var i;n.addEventListener("load",(()=>{r(V.nextContainer).classList.add("active"),o(e.slide(),e.fragmentIndex),e.on("fragment",(({index:e,fragmentIndex:t})=>o(e,t)))}));const a=document.querySelectorAll(".bespoke-marp-note");a.forEach((e=>{e.addEventListener("keydown",(e=>e.stopPropagation())),r(V.noteWrapper).appendChild(e)})),e.on("activate",(()=>a.forEach((t=>t.classList.toggle("active",t.dataset.index==e.slide())))));let s=0;const l=e=>{s=Math.max(-5,s+e),r(V.noteContainer).style.setProperty("--bespoke-marp-note-font-scale",(1.2**s).toFixed(4))},c=()=>l(1),d=()=>l(-1),u=r(V.noteButtonsBigger),f=r(V.noteButtonsSmaller);u.addEventListener("click",(()=>{u.blur(),c()})),f.addEventListener("click",(()=>{f.blur(),d()})),document.addEventListener("keydown",(e=>{"+"===e.key&&c(),"-"===e.key&&d()}),!0),e.on("activate",(({index:t})=>{r(V.infoPageText).textContent=`${t+1} / ${e.slides.length}`}));const m=r(V.infoPagePrev),g=r(V.infoPageNext);m.addEventListener("click",(t=>{m.blur(),e.prev({fragment:!t.shiftKey})})),g.addEventListener("click",(t=>{g.blur(),e.next({fragment:!t.shiftKey})})),e.on("fragment",(({index:t,fragments:n,fragmentIndex:r})=>{m.disabled=0===t&&0===r,g.disabled=t===e.slides.length-1&&r===n.length-1}));let p=new Date;const v=()=>{const e=new Date,t=e=>`${Math.floor(e)}`.padStart(2,"0"),n=e.getTime()-p.getTime(),o=t(n/1e3%60),i=t(n/1e3/60%60),a=t(n/36e5%24);r(V.infoTime).textContent=e.toLocaleTimeString(),r(V.infoTimer).textContent=`${a}:${i}:${o}`};v(),setInterval(v,250),r(V.infoTimer).addEventListener("click",(()=>{p=new Date}))})(e)},X=e=>{if(!(e=>e.syncKey&&"string"==typeof e.syncKey)(e))throw new Error("The current instance of Bespoke.js is invalid for Marp bespoke presenter plugin.");Object.defineProperties(e,{openPresenterView:{enumerable:!0,value:H},presenterUrl:{enumerable:!0,get:R}}),g&&document.addEventListener("keydown",(t=>{"p"!==t.key||t.altKey||t.ctrlKey||t.metaKey||(t.preventDefault(),e.openPresenterView())}))};function H(){const{max:e,floor:t}=Math,n=e(t(.85*window.innerWidth),640),r=e(t(.85*window.innerHeight),360);return window.open(this.presenterUrl,F+this.syncKey,`width=${n},height=${r},menubar=no,toolbar=no`)}function R(){const e=new URLSearchParams(location.search);return e.set("view","presenter"),e.set("sync",this.syncKey),d(e)}const W=e=>{const t=u();return t===a&&e.appendChild(document.createElement("span")),{"":X,[i]:U,[a]:O}[t]},J=e=>{e.on("activate",(t=>{document.querySelectorAll(".bespoke-progress-parent > .bespoke-progress-bar").forEach((n=>{n.style.flexBasis=100*t.index/(e.slides.length-1)+"%"}))}))},Y=e=>{const t=Number.parseInt(e,10);return Number.isNaN(t)?null:t},z=(e={})=>{const t={history:!0,...e};return e=>{let n=!0;const r=e=>{const t=n;try{return n=!0,e()}finally{n=t}},o=(t={fragment:!0})=>{let n=t.fragment?Y(f("f")||""):null;((t,n)=>{const{min:r,max:o}=Math,{fragments:i,slides:a}=e,s=o(0,r(t,a.length-1)),l=o(0,r(n||0,i[s].length-1));s===e.slide()&&l===e.fragmentIndex||e.slide(s,{fragment:l})})((()=>{var t,r;if(location.hash){const[o]=location.hash.slice(1).split(":~:");if(/^\d+$/.test(o))return(null!==(t=Y(o))&&void 0!==t?t:1)-1;const i=document.getElementById(o)||document.querySelector(`a[name="${CSS.escape(o)}"]`);if(i){const{length:t}=e.slides;for(let o=0;o<t;o+=1)if(e.slides[o].contains(i)){const t=null===(r=e.fragments)||void 0===r?void 0:r[o],a=i.closest("[data-marpit-fragment]");if(t&&a){const e=t.indexOf(a);e>=0&&(n=e)}return o}}}return 0})(),n)};e.on("fragment",(({index:e,fragmentIndex:r})=>{n||m({f:0===r||r.toString()},{location:{...location,hash:`#${e+1}`},setter:(...e)=>t.history?history.pushState(...e):history.replaceState(...e)})})),setTimeout((()=>{o(),window.addEventListener("hashchange",(()=>r((()=>{o({fragment:!1}),m({f:void 0})})))),window.addEventListener("popstate",(()=>{n||r((()=>o()))})),n=!1}),0)}},G=(e={})=>{var t;const n=e.key||(null===(t=window.history.state)||void 0===t?void 0:t.marpBespokeSyncKey)||Math.random().toString(36).slice(2),r=`bespoke-marp-sync-${n}`;var i;i={marpBespokeSyncKey:n},m({},{setter:(e,...t)=>o({...e,...i},...t)});const a=()=>{const e=p(r);return e?JSON.parse(e):Object.create(null)},s=e=>{const t=a(),n={...t,...e(t)};return v(r,JSON.stringify(n)),n},l=()=>{window.removeEventListener("pageshow",l),s((e=>({reference:(e.reference||0)+1})))};return e=>{l(),Object.defineProperty(e,"syncKey",{value:n,enumerable:!0});let t=!0;setTimeout((()=>{e.on("fragment",(e=>{t&&s((()=>({index:e.index,fragmentIndex:e.fragmentIndex})))}))}),0),window.addEventListener("storage",(n=>{if(n.key===r&&n.oldValue&&n.newValue){const r=JSON.parse(n.oldValue),o=JSON.parse(n.newValue);if(r.index!==o.index||r.fragmentIndex!==o.fragmentIndex)try{t=!1,e.slide(o.index,{fragment:o.fragmentIndex,forSync:!0})}finally{t=!0}}}));const o=()=>{const{reference:e}=a();void 0===e||e<=1?h(r):s((()=>({reference:e-1})))};window.addEventListener("pagehide",(e=>{e.persisted&&window.addEventListener("pageshow",l),o()})),e.on("destroy",o)}},{PI:Q,abs:Z,sqrt:ee,atan2:te}=Math,ne={passive:!0},re=({slope:e=-.7,swipeThreshold:t=30}={})=>n=>{let r;const o=n.parent,i=e=>{const t=o.getBoundingClientRect();return{x:e.pageX-(t.left+t.right)/2,y:e.pageY-(t.top+t.bottom)/2}};o.addEventListener("touchstart",(({touches:e})=>{r=1===e.length?i(e[0]):void 0}),ne),o.addEventListener("touchmove",(e=>{if(r)if(1===e.touches.length){e.preventDefault();const t=i(e.touches[0]),n=t.x-r.x,o=t.y-r.y;r.delta=ee(Z(n)**2+Z(o)**2),r.radian=te(n,o)}else r=void 0})),o.addEventListener("touchend",(o=>{if(r){if(r.delta&&r.delta>=t&&r.radian){const t=(r.radian-e+Q)%(2*Q)-Q;n[t<0?"next":"prev"](),o.stopPropagation()}r=void 0}}),ne)},oe=new Map;oe.clear(),oe.set("none",{backward:{both:void 0,incoming:void 0,outgoing:void 0},forward:{both:void 0,incoming:void 0,outgoing:void 0}});const ie={both:"",outgoing:"outgoing-",incoming:"incoming-"},ae={forward:"",backward:"-backward"},se=e=>`--marp-bespoke-transition-animation-${e}`,le=e=>`--marp-transition-${e}`,ce=se("name"),de=se("duration"),ue=e=>new Promise((t=>{const n={},r=document.createElement("div"),o=e=>{r.remove(),t(e)};r.addEventListener("animationstart",(()=>o(n))),Object.assign(r.style,{animationName:e,animationDuration:"1s",animationFillMode:"both",animationPlayState:"paused",position:"absolute",pointerEvents:"none"}),document.body.appendChild(r);const i=getComputedStyle(r).getPropertyValue(le("duration"));i&&(n.defaultDuration=i),((e,t)=>{requestAnimationFrame((()=>{e.style.animationPlayState="running",requestAnimationFrame((()=>t(void 0)))}))})(r,o)})),fe=async e=>oe.has(e)?oe.get(e):(e=>{const t={},n=[];for(const[r,o]of Object.entries(ie))for(const[i,a]of Object.entries(ae)){const s=`marp-${o}transition${a}-${e}`;n.push(ue(s).then((e=>{t[i]=t[i]||{},t[i][r]=e?{...e,name:s}:void 0})))}return Promise.all(n).then((()=>t))})(e).then((t=>(oe.set(e,t),t))),me=e=>Object.values(e).flatMap(Object.values).every((e=>!e)),ge=(e,{type:t,backward:n})=>{const r=e[n?"backward":"forward"],o=(()=>{const e=r[t],n=e=>({[ce]:e.name});if(e)return n(e);if(r.both){const e=n(r.both);return"incoming"===t&&(e[se("direction")]="reverse"),e}})();return!o&&n?ge(e,{type:t,backward:!1}):o||{[ce]:"__bespoke_marp_transition_no_animation__"}},pe=e=>{if(e)try{const t=JSON.parse(e);if((e=>{if("object"!=typeof e)return!1;const t=e;return"string"==typeof t.name&&(void 0===t.duration||"string"==typeof t.duration)})(t))return t}catch(e){}},ve="_tSId",he="_tA",ye="bespoke-marp-transition-warming-up",be=window.matchMedia("(prefers-reduced-motion: reduce)"),we="__bespoke_marp_transition_reduced_outgoing__",xe="__bespoke_marp_transition_reduced_incoming__",ke={forward:{both:void 0,incoming:{name:xe},outgoing:{name:we}},backward:{both:void 0,incoming:{name:xe},outgoing:{name:we}}},$e=e=>{if(!document.startViewTransition)return;const t=t=>(void 0!==t&&(e._tD=t),e._tD);let n;t(!1),((...e)=>{const t=[...new Set(e).values()];return Promise.all(t.map((e=>fe(e)))).then()})(...Array.from(document.querySelectorAll("section[data-transition], section[data-transition-back]")).flatMap((e=>[e.dataset.transition,e.dataset.transitionBack].flatMap((e=>{const t=pe(e);return[null==t?void 0:t.name,(null==t?void 0:t.builtinFallback)?`__builtin__${t.name}`:void 0]})).filter((e=>!!e))))).then((()=>{document.querySelectorAll("style").forEach((e=>{e.innerHTML=e.innerHTML.replace(/--marp-transition-duration:[^;}]*[;}]/g,(e=>e.slice(0,-1)+"!important"+e.slice(-1)))}))}));const r=(n,{back:r,cond:o})=>i=>{var a;const s=t();if(s)return!!i[he]||!("object"!=typeof s||(s.skipTransition(),!i.forSync));if(!o(i))return!0;const l=e.slides[e.slide()],c=()=>{var e;return null!==(e=i.back)&&void 0!==e?e:r},d="data-transition"+(c()?"-back":""),u=l.querySelector(`section[${d}]`);if(!u)return!0;const f=pe(null!==(a=u.getAttribute(d))&&void 0!==a?a:void 0);return!f||((async(e,{builtinFallback:t=!0}={})=>{let n=await fe(e);if(me(n)){if(!t)return;return n=await fe(`__builtin__${e}`),me(n)?void 0:n}return n})(f.name,{builtinFallback:f.builtinFallback}).then((e=>{if(!e){t(!0);try{n(i)}finally{t(!1)}return}let r=e;be.matches&&(console.warn("Use a constant animation to transition because preferring reduced motion by viewer has detected."),r=ke);const o=document.getElementById(ve);o&&o.remove();const a=document.createElement("style");a.id=ve,document.head.appendChild(a),((e,t)=>{const n=[`:root{${le("direction")}:${t.backward?-1:1};}`,":root:has(.bespoke-marp-inactive){cursor:none;}"],r=t=>{var n,o,i;const a=(null===(n=e[t].both)||void 0===n?void 0:n.defaultDuration)||(null===(o=e[t].outgoing)||void 0===o?void 0:o.defaultDuration)||(null===(i=e[t].incoming)||void 0===i?void 0:i.defaultDuration);return"forward"===t?a:a||r("forward")},o=t.duration||r(t.backward?"backward":"forward");void 0!==o&&n.push(`::view-transition-group(*){${de}:${o};}`);const i=e=>Object.entries(e).map((([e,t])=>`${e}:${t};`)).join("");return n.push(`::view-transition-old(root){${i(ge(e,{...t,type:"outgoing"}))}}`,`::view-transition-new(root){${i(ge(e,{...t,type:"incoming"}))}}`),n})(r,{backward:c(),duration:f.duration}).forEach((e=>{var t;return null===(t=a.sheet)||void 0===t?void 0:t.insertRule(e)}));const s=document.documentElement.classList;s.add(ye);let l=!1;const d=()=>{l||(n(i),l=!0,s.remove(ye))},u=()=>{t(!1),a.remove(),s.remove(ye)};try{t(!0);const e=document.startViewTransition(d);t(e),e.finished.finally(u)}catch(e){console.error(e),d(),u()}})),!1)};e.on("prev",r((t=>e.prev({...t,[he]:!0})),{back:!0,cond:e=>{var t;return e.index>0&&!((null===(t=e.fragment)||void 0===t||t)&&n.fragmentIndex>0)}})),e.on("next",r((t=>e.next({...t,[he]:!0})),{cond:t=>t.index+1<e.slides.length&&!(n.fragmentIndex+1<n.fragments.length)})),setTimeout((()=>{e.on("slide",r((t=>e.slide(t.index,{...t,[he]:!0})),{cond:t=>{const n=e.slide();return t.index!==n&&(t.back=t.index<n,!0)}}))}),0),e.on("fragment",(e=>{n=e}))};let Ee;const Le=()=>(void 0===Ee&&(Ee="wakeLock"in navigator&&navigator.wakeLock),Ee),Se=async()=>{const e=Le();if(e)try{return await e.request("screen")}catch(e){console.warn(e)}return null},Pe=async()=>{if(!Le())return;let e;const t=()=>{e&&"visible"===document.visibilityState&&Se()};for(const e of["visibilitychange","fullscreenchange"])document.addEventListener(e,t);return e=await Se(),e};((e=document.getElementById(":$p"))=>{(()=>{const e=f("view");r.dataset.bespokeView=e===a||e===i?e:""})();const t=(e=>{const t=f(e);return m({[e]:void 0}),t})("sync")||void 0;n.from(e,((...e)=>{const t=s.findIndex((e=>u()===e));return e.map((([e,n])=>e[t]&&n)).filter((e=>e))})([[1,1,0],G({key:t})],[[1,1,1],W(e)],[[1,1,0],_],[[1,1,1],b],[[1,0,0],S()],[[1,1,1],T],[[1,1,1],z({history:!1})],[[1,1,0],I()],[[1,1,0],E],[[1,0,0],J],[[1,1,0],re()],[[1,0,0],M()],[[1,0,0],$e],[[1,1,1],w],[[1,1,0],Pe]))})()}();</script></body></html>