-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
2399 lines (2357 loc) · 238 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
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html><html lang="en-US"><head><title>Unpoly 2</title><meta property="og:title" content="Unpoly 2"><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{-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-tap-highlight-color:transparent;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{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{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{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){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+PC9kZWZzPjxyZWN0IGNsYXNzPSJhIiB4PSIxMCIgeT0iMjAiIHdpZHRoPSI4MCIgaGVpZ2h0PSI2MCIgcng9IjUuNjciLz48cGF0aCBjbGFzcz0iYSIgZD0iTTQwIDcwSDIwVjUwbTIwIDBMMjAgNzBtNDAtNDBoMjB2MjBtLTIwIDAgMjAtMjAiLz48L3N2Zz4=") 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+PC9kZWZzPjxyZWN0IGNsYXNzPSJhIiB4PSIxMCIgeT0iMjAiIHdpZHRoPSI4MCIgaGVpZ2h0PSI2MCIgcng9IjUuNjciLz48cGF0aCBjbGFzcz0iYSIgZD0iTTIwIDUwaDIwdjIwbS0yMCAwIDIwLTIwbTQwIDBINjBWMzBtMjAgMEw2MCA1MCIvPjwvc3ZnPg==")}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,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48ZGVmcz48c3R5bGU+LmF7ZmlsbDpub25lO3N0cm9rZTojZmZmO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS13aWR0aDo1cHh9PC9zdHlsZT48L2RlZnM+PHBhdGggY2xhc3M9ImEiIGQ9Ik0yMCA2MGgtNWE1IDUgMCAwIDEtNS01VjIwYTUgNSAwIDAgMSA1LTVoNjBhNSA1IDAgMCAxIDUgNXY1TTMwIDg1aDYwIi8+PHJlY3QgeD0iMzAiIHk9IjM1IiB3aWR0aD0iNjAiIGhlaWdodD0iNDAiIHJ4PSI1IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiLz48cmVjdCBjbGFzcz0iYSIgeD0iMzAiIHk9IjM1IiB3aWR0aD0iNjAiIGhlaWdodD0iNDAiIHJ4PSI1Ii8+PHBhdGggY2xhc3M9ImEiIGQ9Ik00MCA1MGg0ME00MCA2MGgzMCIvPjwvc3ZnPg==") no-repeat 50%;background-size:contain;overflow:hidden;text-indent:100%;white-space:nowrap}}.bespoke-marp-note,.bespoke-marp-osc,.bespoke-progress-parent{display:none;transition:none}@media screen{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.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 *{-webkit-animation-name:__bespoke_marp__!important;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}}[data-bespoke-marp-fragment=inactive]{visibility:hidden}body[data-bespoke-view=""] .bespoke-marp-parent,body[data-bespoke-view=next] .bespoke-marp-parent{bottom:0;left:0;position:absolute;right:0;top:0}body[data-bespoke-view=""] .bespoke-marp-parent>.bespoke-marp-osc,body[data-bespoke-view=next] .bespoke-marp-parent>.bespoke-marp-osc{background:rgba(0,0,0,.65);border-radius:7px;bottom:50px;color:#fff;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;-moz-user-select:none;-ms-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-areas:"current next" "current note" "info note";grid-template-columns:2fr 1fr;grid-template-rows:minmax(140px,1fr) 2fr 3em;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;-moz-user-select:none;-ms-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-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;-moz-user-select:none;-ms-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}body[data-bespoke-view=presenter] .bespoke-marp-presenter-container .bespoke-marp-presenter-note-container .bespoke-marp-note{word-wrap:break-word;scrollbar-width:thin;scrollbar-color:hsla(0,0%,93.3%,.5) transparent;box-sizing:border-box;font-size:1.1em;height:calc(100% - 40px);margin:20px;overflow:auto;padding-right:3px;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.3%,.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;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}}@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,300;1,400;1,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 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 4:3 960px 720px
*/div#p>svg>foreignObject>section .octicon{fill:currentColor;display:inline-block;vertical-align:text-bottom}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 h1 .octicon-link,div#p>svg>foreignObject>section h2 .octicon-link,div#p>svg>foreignObject>section h3 .octicon-link,div#p>svg>foreignObject>section h4 .octicon-link,div#p>svg>foreignObject>section h5 .octicon-link,div#p>svg>foreignObject>section h6 .octicon-link{color:#1b1f23;vertical-align:middle;visibility:hidden}div#p>svg>foreignObject>section h1:hover .anchor,div#p>svg>foreignObject>section h2:hover .anchor,div#p>svg>foreignObject>section h3:hover .anchor,div#p>svg>foreignObject>section h4:hover .anchor,div#p>svg>foreignObject>section h5:hover .anchor,div#p>svg>foreignObject>section h6:hover .anchor{text-decoration:none}div#p>svg>foreignObject>section h1:hover .anchor .octicon-link,div#p>svg>foreignObject>section h2:hover .anchor .octicon-link,div#p>svg>foreignObject>section h3:hover .anchor .octicon-link,div#p>svg>foreignObject>section h4:hover .anchor .octicon-link,div#p>svg>foreignObject>section h5:hover .anchor .octicon-link,div#p>svg>foreignObject>section h6:hover .anchor .octicon-link{visibility:visible}div#p>svg>foreignObject>section h1:hover .anchor .octicon-link:before,div#p>svg>foreignObject>section h2:hover .anchor .octicon-link:before,div#p>svg>foreignObject>section h3:hover .anchor .octicon-link:before,div#p>svg>foreignObject>section h4:hover .anchor .octicon-link:before,div#p>svg>foreignObject>section h5:hover .anchor .octicon-link:before,div#p>svg>foreignObject>section h6:hover .anchor .octicon-link:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' aria-hidden='true'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z'/%3E%3C/svg%3E");content:" ";display:inline-block;height:16px;width:16px}div#p>svg>foreignObject>section{word-wrap:break-word;color:#24292e;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:16px;line-height:1.5;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}div#p>svg>foreignObject>section{--marpit-root-font-size:16px}div#p>svg>foreignObject>section details{display:block}div#p>svg>foreignObject>section summary{display:list-item}div#p>svg>foreignObject>section a{background-color:initial}div#p>svg>foreignObject>section a:active,div#p>svg>foreignObject>section a:hover{outline-width:0}div#p>svg>foreignObject>section strong{font-weight:inherit;font-weight:bolder}div#p>svg>foreignObject>section h1{margin:.67em 0}div#p>svg>foreignObject>section img{border-style:none}div#p>svg>foreignObject>section code,div#p>svg>foreignObject>section kbd,div#p>svg>foreignObject>section pre{font-family:monospace,monospace;font-size:1em}div#p>svg>foreignObject>section hr{box-sizing:initial;overflow:visible}div#p>svg>foreignObject>section input{font:inherit;margin:0;overflow:visible}div#p>svg>foreignObject>section [type=checkbox]{padding:0}div#p>svg>foreignObject>section *,div#p>svg>foreignObject>section [type=checkbox]{box-sizing:border-box}div#p>svg>foreignObject>section input{font-family:inherit;font-size:inherit;line-height:inherit}div#p>svg>foreignObject>section a{color:#0366d6;text-decoration:none}div#p>svg>foreignObject>section a:hover{text-decoration:underline}div#p>svg>foreignObject>section strong{font-weight:600}div#p>svg>foreignObject>section hr{background:transparent;border-bottom:1px solid #dfe2e5;height:0;margin:15px 0;overflow:hidden}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}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 h1,div#p>svg>foreignObject>section h2,div#p>svg>foreignObject>section h3,div#p>svg>foreignObject>section h4,div#p>svg>foreignObject>section h5,div#p>svg>foreignObject>section h6{margin-bottom:0;margin-top:0}div#p>svg>foreignObject>section h1{font-size:32px}div#p>svg>foreignObject>section h1,div#p>svg>foreignObject>section h2{font-weight:600}div#p>svg>foreignObject>section h2{font-size:24px}div#p>svg>foreignObject>section h3{font-size:20px}div#p>svg>foreignObject>section h3,div#p>svg>foreignObject>section h4{font-weight:600}div#p>svg>foreignObject>section h4{font-size:16px}div#p>svg>foreignObject>section h5{font-size:14px}div#p>svg>foreignObject>section h5,div#p>svg>foreignObject>section h6{font-weight:600}div#p>svg>foreignObject>section h6{font-size:12px}div#p>svg>foreignObject>section p{margin-bottom:10px;margin-top:0}div#p>svg>foreignObject>section blockquote{margin:0}div#p>svg>foreignObject>section ol,div#p>svg>foreignObject>section ul{margin-bottom:0;margin-top:0;padding-left:0}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 code,div#p>svg>foreignObject>section pre{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:12px}div#p>svg>foreignObject>section pre{margin-bottom:0;margin-top:0}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 :checked+.radio-label{border-color:#0366d6;position:relative;z-index:1}div#p>svg>foreignObject>section .border{border:1px solid #e1e4e8!important}div#p>svg>foreignObject>section .border-0{border:0!important}div#p>svg>foreignObject>section .border-bottom{border-bottom:1px solid #e1e4e8!important}div#p>svg>foreignObject>section .rounded-1{border-radius:3px!important}div#p>svg>foreignObject>section .bg-white{background-color:#fff!important}div#p>svg>foreignObject>section .bg-gray-light{background-color:#fafbfc!important}div#p>svg>foreignObject>section .text-gray-light{color:#6a737d!important}div#p>svg>foreignObject>section .pl-3,div#p>svg>foreignObject>section .px-3{padding-left:16px!important}div#p>svg>foreignObject>section .px-3{padding-right:16px!important}div#p>svg>foreignObject>section .f6{font-size:12px!important}div#p>svg>foreignObject>section div#p>svg>foreignObject>section section.f6{--marpit-root-font-size:12px!important}div#p>svg>foreignObject>section .lh-condensed{line-height:1.25!important}div#p>svg>foreignObject>section .text-bold{font-weight:600!important}div#p>svg>foreignObject>section .pl-c{color:#6a737d}div#p>svg>foreignObject>section .pl-c1,div#p>svg>foreignObject>section .pl-s .pl-v{color:#005cc5}div#p>svg>foreignObject>section .pl-e,div#p>svg>foreignObject>section .pl-en{color:#6f42c1}div#p>svg>foreignObject>section .pl-s .pl-s1,div#p>svg>foreignObject>section .pl-smi{color:#24292e}div#p>svg>foreignObject>section .pl-ent{color:#22863a}div#p>svg>foreignObject>section .pl-k{color:#d73a49}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:#032f62}div#p>svg>foreignObject>section .pl-smw,div#p>svg>foreignObject>section .pl-v{color:#e36209}div#p>svg>foreignObject>section .pl-bu{color:#b31d28}div#p>svg>foreignObject>section .pl-ii{background-color:#b31d28;color:#fafbfc}div#p>svg>foreignObject>section .pl-c2{background-color:#d73a49;color:#fafbfc}div#p>svg>foreignObject>section .pl-c2:before{content:"^M"}div#p>svg>foreignObject>section .pl-sr .pl-cce{color:#22863a;font-weight:700}div#p>svg>foreignObject>section .pl-ml{color:#735c0f}div#p>svg>foreignObject>section .pl-mh,div#p>svg>foreignObject>section .pl-mh .pl-en,div#p>svg>foreignObject>section .pl-ms{color:#005cc5;font-weight:700}div#p>svg>foreignObject>section .pl-mi{color:#24292e;font-style:italic}div#p>svg>foreignObject>section .pl-mb{color:#24292e;font-weight:700}div#p>svg>foreignObject>section .pl-md{background-color:#ffeef0;color:#b31d28}div#p>svg>foreignObject>section .pl-mi1{background-color:#f0fff4;color:#22863a}div#p>svg>foreignObject>section .pl-mc{background-color:#ffebda;color:#e36209}div#p>svg>foreignObject>section .pl-mi2{background-color:#005cc5;color:#f6f8fa}div#p>svg>foreignObject>section .pl-mdr{color:#6f42c1;font-weight:700}div#p>svg>foreignObject>section .pl-ba{color:#586069}div#p>svg>foreignObject>section .pl-sg{color:#959da5}div#p>svg>foreignObject>section .pl-corl{color:#032f62;text-decoration:underline}div#p>svg>foreignObject>section .mb-0{margin-bottom:0!important}div#p>svg>foreignObject>section .my-2{margin-bottom:8px!important;margin-top:8px!important}div#p>svg>foreignObject>section .pl-0{padding-left:0!important}div#p>svg>foreignObject>section .py-0{padding-bottom:0!important;padding-top:0!important}div#p>svg>foreignObject>section .pl-1{padding-left:4px!important}div#p>svg>foreignObject>section .pl-2{padding-left:8px!important}div#p>svg>foreignObject>section .py-2{padding-bottom:8px!important;padding-top:8px!important}div#p>svg>foreignObject>section .pl-3{padding-left:16px!important}div#p>svg>foreignObject>section .pl-4{padding-left:24px!important}div#p>svg>foreignObject>section .pl-5{padding-left:32px!important}div#p>svg>foreignObject>section .pl-6{padding-left:40px!important}div#p>svg>foreignObject>section .pl-7{padding-left:48px!important}div#p>svg>foreignObject>section .pl-8{padding-left:64px!important}div#p>svg>foreignObject>section .pl-9{padding-left:80px!important}div#p>svg>foreignObject>section .pl-10{padding-left:96px!important}div#p>svg>foreignObject>section .pl-11{padding-left:112px!important}div#p>svg>foreignObject>section .pl-12{padding-left:128px!important}div#p>svg>foreignObject>section hr{border-bottom-color:#eee}div#p>svg>foreignObject>section kbd{background-color:#fafbfc;border:1px solid #d1d5da;border-radius:3px;box-shadow:inset 0 -1px 0 #d1d5da;color:#444d56;display:inline-block;font:11px SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;line-height:10px;padding:3px 5px;vertical-align:middle}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 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 pre,div#p>svg>foreignObject>section table,div#p>svg>foreignObject>section ul{margin-bottom:16px;margin-top:0}div#p>svg>foreignObject>section hr{background-color:#e1e4e8;border:0;height:.25em;margin:24px 0;padding:0}div#p>svg>foreignObject>section blockquote{border-left:.25em solid #dfe2e5;color:#6a737d;padding:0 1em}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 h1,div#p>svg>foreignObject>section h2,div#p>svg>foreignObject>section h3,div#p>svg>foreignObject>section h4,div#p>svg>foreignObject>section h5,div#p>svg>foreignObject>section h6{font-weight:600;line-height:1.25;margin-bottom:16px;margin-top:24px}div#p>svg>foreignObject>section h1{font-size:2em}div#p>svg>foreignObject>section h1,div#p>svg>foreignObject>section h2{border-bottom:1px solid #eaecef;padding-bottom:.3em}div#p>svg>foreignObject>section h2{font-size:1.5em}div#p>svg>foreignObject>section h3{font-size:1.25em}div#p>svg>foreignObject>section h4{font-size:1em}div#p>svg>foreignObject>section h5{font-size:.875em}div#p>svg>foreignObject>section h6{color:#6a737d;font-size:.85em}div#p>svg>foreignObject>section ol,div#p>svg>foreignObject>section ul{padding-left:2em}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{word-wrap:break-all}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: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{display:block;overflow:auto;width:100%}div#p>svg>foreignObject>section table th{font-weight:600}div#p>svg>foreignObject>section table td,div#p>svg>foreignObject>section table th{border:1px solid #dfe2e5;padding:6px 13px}div#p>svg>foreignObject>section table tr{background-color:#fff;border-top:1px solid #c6cbd1}div#p>svg>foreignObject>section table tr:nth-child(2n){background-color:#f6f8fa}div#p>svg>foreignObject>section img{background-color:#fff;box-sizing:initial;max-width:100%}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 code{background-color:rgba(27,31,35,.05);border-radius:3px;font-size:85%;margin:0;padding:.2em .4em}div#p>svg>foreignObject>section pre{word-wrap:normal}div#p>svg>foreignObject>section pre>code{background:transparent;border:0;font-size:100%;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 pre{margin-bottom:0;word-break:normal}div#p>svg>foreignObject>section pre{background-color:#f6f8fa;border-radius:3px;font-size:85%;line-height:1.45;overflow:auto;padding:16px}div#p>svg>foreignObject>section pre code{word-wrap:normal;background-color:initial;border:0;display:inline;line-height:inherit;margin:0;max-width:auto;overflow:visible;padding:0}div#p>svg>foreignObject>section .commit-tease-sha{color:#444d56;display:inline-block;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:90%}div#p>svg>foreignObject>section div#p>svg>foreignObject>section section.commit-tease-sha{--marpit-root-font-size:90%}div#p>svg>foreignObject>section .full-commit .btn-outline:not(:disabled):hover{border-color:#005cc5;color:#005cc5}div#p>svg>foreignObject>section .blob-wrapper{overflow-x:auto;overflow-y:hidden}div#p>svg>foreignObject>section .blob-wrapper-embedded{max-height:240px;overflow-y:auto}div#p>svg>foreignObject>section .blob-num{color:rgba(27,31,35,.3);cursor:pointer;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:12px;line-height:20px;min-width:50px;padding-left:10px;padding-right:10px;text-align:right;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:top;white-space:nowrap;width:1%}div#p>svg>foreignObject>section div#p>svg>foreignObject>section section.blob-num{--marpit-root-font-size:12px}div#p>svg>foreignObject>section .blob-num:hover{color:rgba(27,31,35,.6)}div#p>svg>foreignObject>section .blob-num:before{content:attr(data-line-number)}div#p>svg>foreignObject>section .blob-code{line-height:20px;padding-left:10px;padding-right:10px;position:relative;vertical-align:top}div#p>svg>foreignObject>section .blob-code-inner{word-wrap:normal;color:#24292e;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:12px;overflow:visible;white-space:pre}div#p>svg>foreignObject>section div#p>svg>foreignObject>section section.blob-code-inner{--marpit-root-font-size:12px}div#p>svg>foreignObject>section .pl-token.active,div#p>svg>foreignObject>section .pl-token:hover{background:#ffea7f;cursor:pointer}div#p>svg>foreignObject>section .tab-size[data-tab-size="1"]{-moz-tab-size:1;-o-tab-size:1;tab-size:1}div#p>svg>foreignObject>section .tab-size[data-tab-size="2"]{-moz-tab-size:2;-o-tab-size:2;tab-size:2}div#p>svg>foreignObject>section .tab-size[data-tab-size="3"]{-moz-tab-size:3;-o-tab-size:3;tab-size:3}div#p>svg>foreignObject>section .tab-size[data-tab-size="4"]{-moz-tab-size:4;-o-tab-size:4;tab-size:4}div#p>svg>foreignObject>section .tab-size[data-tab-size="5"]{-moz-tab-size:5;-o-tab-size:5;tab-size:5}div#p>svg>foreignObject>section .tab-size[data-tab-size="6"]{-moz-tab-size:6;-o-tab-size:6;tab-size:6}div#p>svg>foreignObject>section .tab-size[data-tab-size="7"]{-moz-tab-size:7;-o-tab-size:7;tab-size:7}div#p>svg>foreignObject>section .tab-size[data-tab-size="8"]{-moz-tab-size:8;-o-tab-size:8;tab-size:8}div#p>svg>foreignObject>section .tab-size[data-tab-size="9"]{-moz-tab-size:9;-o-tab-size:9;tab-size:9}div#p>svg>foreignObject>section .tab-size[data-tab-size="10"]{-moz-tab-size:10;-o-tab-size:10;tab-size:10}div#p>svg>foreignObject>section .tab-size[data-tab-size="11"]{-moz-tab-size:11;-o-tab-size:11;tab-size:11}div#p>svg>foreignObject>section .tab-size[data-tab-size="12"]{-moz-tab-size:12;-o-tab-size:12;tab-size:12}div#p>svg>foreignObject>section .task-list-item{list-style-type:none}div#p>svg>foreignObject>section .task-list-item+.task-list-item{margin-top:3px}div#p>svg>foreignObject>section .task-list-item input{margin:0 .2em .25em -1.6em;vertical-align:middle}div#p>svg>foreignObject>section .hljs{background:#fff;color:#333;display:block;overflow-x:auto;padding:.5em}div#p>svg>foreignObject>section .hljs-comment,div#p>svg>foreignObject>section .hljs-meta{color:#969896}div#p>svg>foreignObject>section .hljs-emphasis,div#p>svg>foreignObject>section .hljs-quote,div#p>svg>foreignObject>section .hljs-strong,div#p>svg>foreignObject>section .hljs-template-variable,div#p>svg>foreignObject>section .hljs-variable{color:#df5000}div#p>svg>foreignObject>section .hljs-keyword,div#p>svg>foreignObject>section .hljs-selector-tag,div#p>svg>foreignObject>section .hljs-type{color:#d73a49}div#p>svg>foreignObject>section .hljs-attribute,div#p>svg>foreignObject>section .hljs-bullet,div#p>svg>foreignObject>section .hljs-literal,div#p>svg>foreignObject>section .hljs-symbol{color:#0086b3}div#p>svg>foreignObject>section .hljs-name,div#p>svg>foreignObject>section .hljs-section{color:#63a35c}div#p>svg>foreignObject>section .hljs-tag{color:#333}div#p>svg>foreignObject>section .hljs-attr,div#p>svg>foreignObject>section .hljs-selector-attr,div#p>svg>foreignObject>section .hljs-selector-class,div#p>svg>foreignObject>section .hljs-selector-id,div#p>svg>foreignObject>section .hljs-selector-pseudo,div#p>svg>foreignObject>section .hljs-title{color:#6f42c1}div#p>svg>foreignObject>section .hljs-addition{background-color:#eaffea;color:#55a532}div#p>svg>foreignObject>section .hljs-deletion{background-color:#ffecec;color:#bd2c00}div#p>svg>foreignObject>section .hljs-link{text-decoration:underline}div#p>svg>foreignObject>section .hljs-number{color:#005cc5}div#p>svg>foreignObject>section .hljs-string{color:#032f62}div#p>svg>foreignObject>section svg[data-marp-fitting=svg]{max-height:563px}div#p>svg>foreignObject>section h1{color:#246;font-size:1.6em}div#p>svg>foreignObject>section h1,div#p>svg>foreignObject>section h2{border-bottom:none}div#p>svg>foreignObject>section h2{font-size:1.3em}div#p>svg>foreignObject>section h3{font-size:1.1em}div#p>svg>foreignObject>section h4{font-size:1.05em}div#p>svg>foreignObject>section h5{font-size:1em}div#p>svg>foreignObject>section h6{font-size:.9em}div#p>svg>foreignObject>section h1 strong,div#p>svg>foreignObject>section h2 strong,div#p>svg>foreignObject>section h3 strong,div#p>svg>foreignObject>section h4 strong,div#p>svg>foreignObject>section h5 strong,div#p>svg>foreignObject>section h6 strong{color:#48c;font-weight:inherit}div#p>svg>foreignObject>section hr{height:0;padding-top:.25em}div#p>svg>foreignObject>section pre{border:1px solid #999;line-height:1.15;overflow:visible}div#p>svg>foreignObject>section pre code svg[data-marp-fitting=svg]{max-height:529px}div#p>svg>foreignObject>section footer,div#p>svg>foreignObject>section header{color:hsla(0,0%,40%,.75);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{align-items:stretch;background:#fff;display:flex;flex-direction:column;flex-wrap: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>: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:#777;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.invert{background-color:#222;color:#e6eaf0}div#p>svg>foreignObject>section.invert:after{color:#999}div#p>svg>foreignObject>section.invert img{background-color:transparent}div#p>svg>foreignObject>section.invert a{color:#50b3ff}div#p>svg>foreignObject>section.invert h1{color:#a3c5e7}div#p>svg>foreignObject>section.invert h2,div#p>svg>foreignObject>section.invert h3,div#p>svg>foreignObject>section.invert h4,div#p>svg>foreignObject>section.invert h5{color:#ebeff5}div#p>svg>foreignObject>section.invert blockquote,div#p>svg>foreignObject>section.invert h6{border-color:#3d3f43;color:#939699}div#p>svg>foreignObject>section.invert h1 strong,div#p>svg>foreignObject>section.invert h2 strong,div#p>svg>foreignObject>section.invert h3 strong,div#p>svg>foreignObject>section.invert h4 strong,div#p>svg>foreignObject>section.invert h5 strong,div#p>svg>foreignObject>section.invert h6 strong{color:#7bf}div#p>svg>foreignObject>section.invert hr{background-color:#3d3f43}div#p>svg>foreignObject>section.invert footer,div#p>svg>foreignObject>section.invert header{color:hsla(0,0%,60%,.75)}div#p>svg>foreignObject>section.invert code,div#p>svg>foreignObject>section.invert kbd{background-color:#111}div#p>svg>foreignObject>section.invert kbd{border-color:#666;box-shadow:inset 0 -1px 0 #555;color:#e6eaf0}div#p>svg>foreignObject>section.invert table tr{background-color:#12181d;border-color:#60657b}div#p>svg>foreignObject>section.invert table tr:nth-child(2n){background-color:#1b2024}div#p>svg>foreignObject>section.invert table td,div#p>svg>foreignObject>section.invert table th{border-color:#5b5e61}div#p>svg>foreignObject>section.invert pre{background-color:#0a0e12;border-color:#777}div#p>svg>foreignObject>section.invert pre code{background-color:transparent}div#p>svg>foreignObject>section[data-color] h1,div#p>svg>foreignObject>section[data-color] h2,div#p>svg>foreignObject>section[data-color] h3,div#p>svg>foreignObject>section[data-color] h4,div#p>svg>foreignObject>section[data-color] h5,div#p>svg>foreignObject>section[data-color] h6{color:currentColor}div#p>svg>foreignObject>section svg[data-marp-fitting=svg]{display:block;height:auto;width:100%}@supports (-ms-ime-align:auto){div#p>svg>foreignObject>section svg[data-marp-fitting=svg]{position:static}}div#p>svg>foreignObject>section svg[data-marp-fitting=svg].__reflow__{content:""}@supports (-ms-ime-align:auto){div#p>svg>foreignObject>section svg[data-marp-fitting=svg].__reflow__{position:relative}}div#p>svg>foreignObject>section [data-marp-fitting-svg-content]{display:table;white-space:nowrap;width:-webkit-max-content;width:-moz-max-content;width:max-content}div#p>svg>foreignObject>section [data-marp-fitting-svg-content-wrap]{white-space:pre}div#p>svg>foreignObject>section img[data-marp-twemoji]{background:transparent;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em;width:1em}
/* @theme unpoly2-slides */div#p>svg>foreignObject>section html{}div#p>svg>foreignObject>section{
/* --highlightColor-color: #dd4232; */font-family:Roboto,sans-serif;font-size:25px;line-height:1.35;--highlightColor:#ff4433;--textColor:#24292e}div#p>svg>foreignObject>section{--marpit-root-font-size:25px}div#p>svg>foreignObject>section:not(.no-watermark):not(.title){background-image:url('./images/unpoly2.svg');background-position:top 40px right 40px;background-size:250px;background-repeat:no-repeat}div#p>svg>foreignObject>section .muted{opacity:0.5}
/*
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 h1{color:var(--highlightColor);position:relative}
/*
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 .row{display:flex}div#p>svg>foreignObject>section .col{flex:1 1 0}div#p>svg>foreignObject>section .col+.col{margin-left:2em}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 h1{font-size:3em;font-family:Orbitron,sans-serif;text-transform:uppercase}div#p>svg>foreignObject>section.new:before,div#p>svg>foreignObject>section.pro:before{content:'JAN';background-color:white;position:absolute;left:-110px;top:-60px;text-align:center;padding:90px 90px 10px;color:white;background-color:var(--textColor);font-size:28px;font-weight:bold;transform:rotate(-45deg)
/* 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:28px}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 .positive{color:#37c04e}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}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[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="unpoly2-slides" class="no-watermark" style="--class:no-watermark;--theme:unpoly2-slides;"><div class="title">
<img src="./images/unpoly2.svg" alt="Unpoly 2" 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="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>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 1 users<br />
who want to learn about the major changes in Unpoly 2.</strong></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="3" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Unpoly 2?</h1>
<p>Unpoly 2 is the end of a long-term project.</p>
<p>It resulted from many discussions with my colleagues at makandra,
and the limits they ran into when using Unpoly for non-trivial interactions.</p>
<p>I also looked through a lot of code on our Gitlab to see how Unpoly was used in the wild.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="4" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Unpoly 2 objectives</h1>
<div class="row">
<div class="col">
<h3>Kill boilerplate configuration</h3>
<p>Repetitive configuration code is now the default.
New, unopionated Bootstrap integration.</p>
<h3>Layer API</h3>
<p>A new layer API replaces modals and popups.<br />
Layers are isolated and can be stacked infinitely.</p>
<h3>Sub-interactions</h3>
<p>Branch off sub-interaction into an overlay.<br />
Overlay results are propagated back to parent layer, where the story continues.</p>
</div>
<div class="col">
<h3>Navigation intent</h3>
<p>Not every fragment update means a user navigation.
Switching screens need other defaults than updating a box.</p>
<h3>Accessibility</h3>
<p>All Unpoly features carefully manage focus.
Keyboard navigation is supported everywhere.</p>
<h3>Quality of live improvements</h3>
<p>Extensive improvements for almost all APIs.</p>
</div>
</div>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="5" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Changes ahead, but don't panic!</h1>
<p>I know that many of you are maintaining large apps with Unpoly 0.62.</p>
<p>You will see some major changes in these slides, but <strong>don't panic</strong>!<br />
Unpoly 2 keeps aliases for deprecated APIs going back to 2016.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="6" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<div class="row">
<div class="col">
<h3>Renamed events are aliased</h3>
<p><code>up.on('up:proxy:load')</code> will bind to<br />
<code>up:request:load</code>.</p>
<h3>Renamed functions are aliased</h3>
<p><code>up.modal.close()</code> will call<br />
<code>up.layer.dismiss()</code></p>
<h3>Renamed options are aliased</h3>
<p><code>{ reveal: false }</code> will be renamed to<br />
<code>{ scroll: false }</code></p>
</div>
<div class="col">
<h3>Renamed packages are aliased</h3>
<p><code>up.proxy.config</code> will return<br />
<code>up.network.config</code>.</p>
<h3>Renamed HTML attributes are aliased</h3>
<p><code><a up-close></code> will translate to<br />
<code><a up-dismiss></code>.</p>
</div>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="7" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<p>Calls to old APIs will be forwarded to the new version and log a deprecation notice with a trace.
</p>
<img src="./images/log-deprecation.png" alt="New log" class="picture" style="display: block; margin: 0.6em 0 1.1em 0" />
<p>This way you upgrade Unpoly, revive your application with few changes,<br />
then replace deprecated API calls under green tests.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="8" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Removing aliases from your build</h2>
<p>All aliases are shipped as a separate file <code>unpoly-migrate.js</code>.</p>
<p>If you prefer errors to warnings, you can set <code>up.migrate.config.logLevel = 'error'</code>.</p>
<p>Once you have fixed deprecated usages you can remove this from your build.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="9" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Kill boilerplate configuration</h1>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="10" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<p><strong><img class="emoji" draggable="false" alt="💡" src="https://twemoji.maxcdn.com/2/svg/1f4a1.svg" data-marp-twemoji=""/> Our projects need too much code to configure Unpoly.</strong></p>
<p>None of our projects use Unpoly as it comes out of the box.
Instead we customize Unpoly with a long list of custom settings, Rails helpers
and macros that we copy from project to project.</p>
<p>Some of that should be a better default by the framework.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="11" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Delete your link helpers</h1>
<p>All our projects have helpers like <code>content_link</code> and <code>modal_link</code> to configure defaults:</p>
<ul>
<li>Make a link followable through Unpoly</li>
<li>Accelerate clicks with <code>[up-preload]</code> and <code>[up-instant]</code></li>
<li>Set a default target selector</li>
<li>Set a transition (sometimes)</li>
</ul>
<p><strong>In Unpoly 2 these helpers (and their macros) are no longer needed.</strong></p>
<p>You can configure Unpoly 2 to handle standard <code><a href></code> links without any <code>[up-...]</code> attributes.</p>
<p>Rails users can now use the standard <code>link_to</code> helper without extra options.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="12" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Following all links by default</h1>
<p><strong><img class="emoji" draggable="false" alt="💡" src="https://twemoji.maxcdn.com/2/svg/1f4a1.svg" data-marp-twemoji=""/> Most apps handle all links and forms through Unpoly.</strong></p>
<p>Unpoly 1 forced apps to manually opt-in every link and form.</p>
<p>You can tell Unpoly 2 to handle <strong>all</strong> links and forms:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.link.config.followSelectors.push(<span class="hljs-string">'a[href]'</span>)
up.form.config.submitSelectors.push(<span class="hljs-string">'form'</span>)
</span></span></foreignObject></svg></code></pre>
<p>Links will now be followed through Unpoly <strong>without</strong> an <code>[up-target]</code> or <code>[up-follow]</code> attribute:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/backend"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="13" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Making exceptions</h2>
<p>You may still opt out individual links or forms by setting <code>[up-follow=false]</code>:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/backend"</span> <span class="hljs-attr">up-follow</span>=<span class="hljs-string">"false"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
</span></span></foreignObject></svg></code></pre>
<p>Unpoly comes pre-configured to <em>never</em> follow:</p>
<ul>
<li>Links with a cross-origin <code>[href]</code>.</li>
<li>Links with a <code>[target]</code> attribute (to target an iframe or open new browser tab).</li>
<li>Links with a <code>[rel=download]</code> attribute.</li>
<li>Links with an <code>[href]</code> attribute starting with <code>javascript:</code>.</li>
<li>Links with an <code>[href="#"]</code> attribute that don't also have local HTML
in an <code>[up-document]</code>, <code>[up-fragment]</code> or <code>[up-content]</code> attribute.</li>
<li>Links matching <code>up.link.config.noFollowSelectors</code></li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="14" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Accelerating links by default</h1>
<p><strong><img class="emoji" draggable="false" alt="💡" src="https://twemoji.maxcdn.com/2/svg/1f4a1.svg" data-marp-twemoji=""/> Most links should activate on <code>mousedown</code> and be preloaded.</strong></p>
<p>If you want to default to <code>[up-instant]</code> and <code>[up-preload]</code>:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.link.config.instantSelectors.push(<span class="hljs-string">'a[href]'</span>)
up.link.config.preloadSelectors.push(<span class="hljs-string">'a[href]'</span>)
</span></span></foreignObject></svg></code></pre>
<p>All your links now activate on <code>mousedown</code> and (with a <code>GET</code> method) preload on <code>mouseover</code>:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/backend"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="15" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Making exceptions</h2>
<p>Instant clicks feel wrong for buttons. To cover that, configure a CSS selector that excludes buttons:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.link.config.instant.push(<span class="hljs-string">'a[href]:not(.button)'</span>)
</span></span></foreignObject></svg></code></pre>
<p>Individual links may opt out by setting <code>[up-instant=false]</code> or <code>[up-preload=false]</code>:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/backend"</span> <span class="hljs-attr">up-instant</span>=<span class="hljs-string">"false"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/expensive-path"</span> <span class="hljs-attr">up-preload</span>=<span class="hljs-string">"false"</span>></span>
</span></span></foreignObject></svg></code></pre>
<p>You may also configure global exceptions in <code>up.link.config.noInstantSelectors</code> and <code>up.link.config.noPreloadSelectors</code>.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="16" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Main targets</h1>
<p><strong><img class="emoji" draggable="false" alt="💡" src="https://twemoji.maxcdn.com/2/svg/1f4a1.svg" data-marp-twemoji=""/> Many links simply replace the main content element.</strong></p>
<p>Unpoly 1 required you to pass a target selector with every fragment update, although it would often be the same selector.</p>
<p>Unpoly 2 lets you mark elements as default targets using the <code>[up-main]</code> attribute:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">body</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"layout"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"layout--side"</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">"layout--content"</span> <span class="hljs-attr">up-main</span>></span>
...
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">body</span>></span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="17" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Links work without target</h2>
<p>Once a main target is configured, you no longer need <code>[up-target]</code> in a link.<br />
Use <code>[up-follow]</code> and the <code>[up-main]</code> element will be replaced:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/foo"</span> <span class="hljs-attr">up-follow</span>></span>...<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
</span></span></foreignObject></svg></code></pre>
<p>If you want to update something more specific, you can still use <code>[up-target]</code>:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/foo"</span> <span class="hljs-attr">up-target</span>=<span class="hljs-string">".profile"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
</span></span></foreignObject></svg></code></pre>
<p>Instead of assigning <code>[up-main]</code> you may also configure an existing selector:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.fragment.config.mainTargets.push(<span class="hljs-string">'.layout--content'</span>)
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="18" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Overlays can use different main targets</h2>
<p><strong><img class="emoji" draggable="false" alt="💡" src="https://twemoji.maxcdn.com/2/svg/1f4a1.svg" data-marp-twemoji=""/> Overlays often use a different default selector, e.g. to exclude a navigation bar.</strong></p>
<p>Unpoly 2 lets you configure different main targets for different layer modes:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">body</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"layout"</span> <span class="hljs-attr">up-main</span>=<span class="hljs-string">"root"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"layout--side"</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">"layout--content"</span> <span class="hljs-attr">up-main</span>=<span class="hljs-string">"overlay"</span>></span>
...
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">body</span>></span>
</span></span></foreignObject></svg></code></pre>
<p>You may also configure overlay targets unobtrusively:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.layer.config.popup.mainTargets.push(<span class="hljs-string">'.menu'</span>) <span class="hljs-comment">// for popup overlays</span>
up.layer.config.drawer.mainTargets.push(<span class="hljs-string">'.menu'</span>) <span class="hljs-comment">// for drawer overlays</span>
up.layer.config.overlay.mainTargets.push(<span class="hljs-string">'.layout--content'</span>) <span class="hljs-comment">// for all overlay modes</span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="19" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Setting a default transition</h1>
<p>I'm not a big fan of animating <em>every</em> fragment update.</p>
<p><strong>But since some of you do this</strong>, here is how to set a default transition:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.fragment.config.navigateOptions.transition = <span class="hljs-string">'cross-fade'</span>
</span></span></foreignObject></svg></code></pre>
<p>We're going to learn more about <code>up.fragment.config.navigateOptions</code> in a minute.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="20" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Reworked Bootstrap integration</h1>
<p><strong><img class="emoji" draggable="false" alt="💡" src="https://twemoji.maxcdn.com/2/svg/1f4a1.svg" data-marp-twemoji=""/> Many projects didn't use built-in Bootstrap integration, because it was too opinionated.</strong></p>
<p>For example, Unpoly tried to re-use the Bootstrap modal styles, but most projects simply wanted the white box from the Unpoly default. Projects ended up using their own configuration, which was much more minimal.</p>
<p>Unpoly 2 now ships with a <strong>unopinionated Bootstrap integration</strong>.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="21" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<p>This is all of <code>unpoly-bootstrap4.js</code>:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-comment">// Bootstrap expects the class .active instead of .up-active</span>
up.feedback.config.currentClasses.push(<span class="hljs-string">'active'</span>)
<span class="hljs-comment">// Set .up-current classes in Bootstrap navigation components</span>
up.feedback.config.navs.push(<span class="hljs-string">'.nav'</span>, <span class="hljs-string">'.navbar'</span>)
<span class="hljs-comment">// When validating, update the closest form group with the results</span>
up.form.config.validateTargets.unshift(<span class="hljs-string">'.form-group:has(&)'</span>)
<span class="hljs-comment">// When revealing, scroll far enough so content is not covered by</span>
<span class="hljs-comment">// fixed Bootstrap bars</span>
up.viewport.config.fixedTop.push(<span class="hljs-string">'.navbar.fixed-top'</span>)
up.viewport.config.fixedBottom.push(<span class="hljs-string">'.navbar.fixed-bottom'</span>)
up.viewport.config.anchoredRight.push(<span class="hljs-string">'.navbar.fixed-top'</span>, <span class="hljs-string">'.navbar.fixed-bottom'</span>)
<span class="hljs-comment">/// Don't use common utility classes to build selectors</span>
up.fragment.config.badTargetClasses.push(
<span class="hljs-string">'row'</span>,
<span class="hljs-regexp">/^col(-xs|-sm|-md|-lg|-xl)?(-\d+)?$/</span>,
<span class="hljs-regexp">/^[mp][tblrxy]?-\d+$/</span>
)
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="22" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Support for Bootstrap 3, 4 and 5</h2>
<p>Unpoly 2 now supports the three major Bootstrap versions we're using:</p>
<ul>
<li><code>unpoly-bootstrap3.js</code></li>
<li><code>unpoly-bootstrap3.css</code></li>
<li><code>unpoly-bootstrap4.js</code></li>
<li><code>unpoly-bootstrap4.css</code></li>
<li><code>unpoly-bootstrap5.js</code></li>
<li><code>unpoly-bootstrap5.css</code></li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="23" data-class="topic" data-theme="unpoly2-slides" class="topic" style="--class:topic;--theme:unpoly2-slides;">
<h1>Layer API</h1>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="24" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Demo</h1>
<p><img class="emoji" draggable="false" alt="🎥" src="https://twemoji.maxcdn.com/2/svg/1f3a5.svg" data-marp-twemoji=""/> <em>Show in the demo app</em>:</p>
<ul>
<li>
<p><em>Infinite stacking: Company / Project / Budget</em></p>
</li>
<li>
<p><em>Returning with value: Create budget from project details</em></p>
</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="25" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>New layer terminology</h1>
<p>The root page, modals and popups have been consolidated into a single term <em>layer</em>.</p>
<p>There are different layer <em>modes</em>, e.g. <code>modal</code> or <code>popup</code>.</p>
<p>An <em>overlay</em> is any layer that is not the root layer.</p>
<br />
<table>
<thead>
<tr>
<th>Mode</th>
<th>Description</th>
<th>Overlay?</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>root</code></td>
<td>The root page</td>
<td>no</td>
</tr>
<tr>
<td><code>modal</code></td>
<td>A modal dialog box</td>
<td>yes</td>
</tr>
<tr>
<td><code>drawer</code></td>
<td>A drawer sliding in from the side</td>
<td>yes</td>
</tr>
<tr>
<td><code>popup</code></td>
<td>A popup menu anchored to a link</td>
<td>yes</td>
</tr>
<tr>
<td><code>cover</code> <img class="emoji" draggable="false" alt="✨" src="https://twemoji.maxcdn.com/2/svg/2728.svg" data-marp-twemoji=""/></td>
<td>Covers entire screen</td>
<td>yes</td>
</tr>
</tbody>
</table>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="26" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Layers can be stacked infinitely</h1>
<p><strong><img class="emoji" draggable="false" alt="💡" src="https://twemoji.maxcdn.com/2/svg/1f4a1.svg" data-marp-twemoji=""/> In Unpoly 1 you could only open a single modal.
This limited its practical applications.</strong></p>
<p>Example from a real application:</p>
<ul>
<li>An index page on the root layer shows a list of records</li>
<li>Clicking a record opens a record in a modal overlay. This is useful since the user retains the scroll position of the list in the background.</li>
<li>The details screen cannot open another modal overlay, since one is already open.</li>
</ul>
<p><strong>Unpoly 2 lets you stack an arbitrary number of layers.</strong></p>
<p><img class="emoji" draggable="false" alt="🎥" src="https://twemoji.maxcdn.com/2/svg/1f3a5.svg" data-marp-twemoji=""/> <em>Show demo</em></p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="27" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Layers are fully isolated</h1>
<p><strong><img class="emoji" draggable="false" alt="💡" src="https://twemoji.maxcdn.com/2/svg/1f4a1.svg" data-marp-twemoji=""/> In Unpoly 1 you could accidentally update another layer.</strong></p>
<p>In Unpoly 2 layers are fully isolated. You cannot accidentally target an element in another layer:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><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">".foo"</span>></span> <span class="hljs-comment"><!-- will only match in current layer --></span>
</span></span></foreignObject></svg></code></pre>
<p>If you want to do <em>anything</em> in another layer, use an <code>[up-layer]</code> attribute:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><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">".foo"</span> <span class="hljs-attr">up-layer</span>=<span class="hljs-string">"parent"</span>></span> <span class="hljs-comment"><!-- will only match in parent layer --></span>
<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">".foo"</span> <span class="hljs-attr">up-layer</span>=<span class="hljs-string">"root"</span>></span> <span class="hljs-comment"><!-- will only match in root layer --></span>
<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">".foo"</span> <span class="hljs-attr">up-layer</span>=<span class="hljs-string">"new"</span>></span> <span class="hljs-comment"><!-- opens a new modal overlay --></span>
</span></span></foreignObject></svg></code></pre>
<p>You can always look at <code>[up-layer]</code> to know what layer is going to be updated.<br />
If there is no <code>[up-layer]</code> attribute, you are going to update the current layer.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="28" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>CSS selectors are matched in the current layer</h2>
<p>JavaScript functions that take a <strong>CSS selector</strong> will only look in the current layer:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.submit(<span class="hljs-string">'.user-form'</span>) <span class="hljs-comment">// will only find in the current layer</span>
up.fragment.get(<span class="hljs-string">'.foo'</span>) <span class="hljs-comment">// will only find in the current layer</span>
</span></span></foreignObject></svg></code></pre>
<p>If you want to match a selector in another layer, use a <code>{ layer }</code> option:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.fragment.get(<span class="hljs-string">'.first'</span>, { <span class="hljs-attr">layer</span>: <span class="hljs-string">'any'</span> }) <span class="hljs-comment">// will find in any layer</span>
up.fragment.get(<span class="hljs-string">'.first'</span>, { <span class="hljs-attr">layer</span>: <span class="hljs-string">'parent'</span> }) <span class="hljs-comment">// will find in parent layer</span>
</span></span></foreignObject></svg></code></pre>
<p>You don't need a <code>{ layer }</code> option when you pass DOM elements instead of selectors:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.submit(form) <span class="hljs-comment">// will submit the form in the form's layer</span>
up.fragment.get(element, <span class="hljs-string">'.child'</span>) <span class="hljs-comment">// will find .child in element's descendants</span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="29" data-class="no-watermark" data-theme="unpoly2-slides" class="no-watermark" style="--class:no-watermark;--theme:unpoly2-slides;"><h2 class="center">Referring to layers</h2>
<div class="row">
<div class="col">
<table>
<thead>
<tr>
<th>Layer name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>current</code></td>
<td>The current layer</td>
</tr>
<tr>
<td><code>any</code></td>
<td>Any layer, preferring the current</td>
</tr>
<tr>
<td><code>parent</code></td>
<td>The layer that opened the current layer</td>
</tr>
<tr>
<td><code>closest</code></td>
<td>The current layer or any ancestor, preferring closer layers</td>
</tr>
<tr>
<td><code>root</code></td>
<td>The root layer</td>
</tr>
<tr>
<td><code>overlay</code></td>
<td>Any overlay</td>
</tr>
<tr>
<td><code>origin</code></td>
<td>The layer of the element that triggered the current action</td>
</tr>
<tr>
<td><code><Number></code></td>
<td>The layer with this index</td>
</tr>
</tbody>
</table>
</div>
<div class="col">
<table>
<thead>
<tr>
<th>Layer name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ancestor</code></td>
<td>Any ancestor layer of the current layer</td>
</tr>
<tr>
<td><code>child</code></td>
<td>The child layer of the current layer</td>
</tr>
<tr>
<td><code>descendant</code></td>
<td>Any descendant of the current layer</td>
</tr>
<tr>
<td><code><Element></code></td>
<td>The given element's layer</td>
</tr>
<tr>
<td><code>current root</code></td>
<td>Space-separated alternatives</td>
</tr>
<tr>
<td><code>front</code></td>
<td>The frontmost layer (which may not be the current layer)</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="30" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Most events are associated with a layer</h1>
<p><strong><img class="emoji" draggable="false" alt="💡" src="https://twemoji.maxcdn.com/2/svg/1f4a1.svg" data-marp-twemoji=""/> Layers are rarely interested in events of other layers.</strong></p>
<p>Where possible Unpoly 2 will emit its events on associated layers instead of <code>document</code>.<br />
This way you can listen to events on one layer without receiving events from other layers.</p>
<p>Events resulting from user navigation (like <code>up:link:follow</code>, <code>up:request:load</code>) are associated with the layer of the activated element.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="31" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Listening to your layer</h2>
<p>Unpoly 2 provides convenience functions <code>up.layer.on()</code> and <code>up.layer.emit()</code><br />
to listen / emit on the current layer:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.layer.on(<span class="hljs-string">'up:request:load'</span>, callback) <span class="hljs-comment">// only listen to events from the current layer</span>
up.layer.emit(<span class="hljs-string">'my:event'</span>) <span class="hljs-comment">// emit my:event on the current layer's element</span>
</span></span></foreignObject></svg></code></pre>
<p>The current layer is the layer in which you are compiling or navigating.</p>
<h2>Listening to everything</h2>
<p>Layer events will still bubble up to the <code>document</code>,<br />
so you can still register
a listener for events from any layer:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.on(<span class="hljs-string">'up:request:load'</span>, callback) <span class="hljs-comment">// listen to events from all layers</span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="32" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Remembering the current layer</h1>
<p>Like most functions, <code>up.layer.dismiss()</code> will affect the "current" layer,<br />
so it's an alias for <code>up.layer.current.dismiss()</code>.</p>
<p><code>up.layer.current</code> is set to the right layer in compilers and most events, even if that layer is not the "front" layer. E.g. if you're compiling a fragment for a background layer, <code>up.layer.current</code> will be
the background layer during compilation.</p>
<p>If you have async code, the current layer may change when your callback is called.<br />
You may also retrieve the current layer for later reference:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">dismissCurrentLayerIn</span>(<span class="hljs-params">seconds</span>) </span>{
<span class="hljs-keyword">let</span> savedLayer = up.layer.current <span class="hljs-comment">// returns an up.Layer object</span>
<span class="hljs-keyword">let</span> dismiss = <span class="hljs-function">() =></span> savedLayer.dismiss()
<span class="hljs-built_in">setTimeout</span>(dismiss, seconds * <span class="hljs-number">1000</span>)
}
dismissCurrentLayerIn(<span class="hljs-number">10</span>) <span class="hljs-comment">// </span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="33" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Inspecting the layer from the server</h1>
<p>The server now knows if the request is targeting an overlay.<br />
The following is Ruby code (<code>unpoly-rails</code> gem):</p>
<pre><code class="language-ruby"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.layer.overlay? <span class="hljs-comment"># true</span>
up.layer.root? <span class="hljs-comment"># false</span>
up.layer.mode <span class="hljs-comment"># 'drawer'</span>
</span></span></foreignObject></svg></code></pre>
<p>Note that fragment updates may target different layers for successful (HTTP status <code>200 OK</code>) and failed (status <code>4xx</code> or <code>5xx</code>) responses. Use <code>up.fail_target</code> to learn about the
layer targeted for a failed update:</p>
<pre><code class="language-ruby"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.fail_layer.overlay? <span class="hljs-comment"># false</span>
up.fail_layer.root? <span class="hljs-comment"># true</span>
up.fail_layer.mode <span class="hljs-comment"># 'root'</span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="34" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Layer context</h1>
<p>The web platform gives you several tools to persist state across requests,<br />
like cookies or session storage.</p>
<p>But with overlays you need to store state <em>per layer</em>.</p>
<p>Unpoly adds <em>layer context</em>, a key/value store that exists for
the lifetime of a layer:</p>
<table>
<thead>
<tr>
<th>Store</th>
<th>Scope</th>
<th>Persistence</th>
<th>Values</th>
<th>Client-manageable</th>
<th>Server-manageable</th>
</tr>
</thead>
<tbody>
<tr>
<td>Local storage</td>
<td>Domain</td>
<td>Permanentish</td>
<td>String</td>
<td>Yes</td>
<td>–</td>
</tr>
<tr>
<td>Cookies</td>
<td>Domain</td>
<td>Configurable</td>
<td>String</td>
<td>Configurable</td>
<td>Yes</td>
</tr>
<tr>
<td>Session storage</td>
<td>Tab</td>
<td>Session</td>
<td>String</td>
<td>Yes</td>
<td>–</td>
</tr>
<tr>
<td>Layer context <img class="emoji" draggable="false" alt="🆕" src="https://twemoji.maxcdn.com/2/svg/1f195.svg" data-marp-twemoji=""/></td>
<td>Layer</td>
<td>Session</td>
<td>Object</td>
<td>Yes</td>
<td>Yes</td>
</tr>
</tbody>
</table>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="35" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Initializing the context object</h2>
<p>The default context is an empty object (<code>{}</code>).</p>
<p>You may initialize the context object when opening a layer:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.layer.open({ <span class="hljs-attr">url</span>: <span class="hljs-string">'/games/new'</span>, <span class="hljs-attr">context</span>: { <span class="hljs-attr">lives</span>: <span class="hljs-number">3</span> } })
</span></span></foreignObject></svg></code></pre>
<p>Or from HTML:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">'/games/new'</span> <span class="hljs-attr">up-layer</span>=<span class="hljs-string">'new'</span> <span class="hljs-attr">up-context</span>=<span class="hljs-string">'{ "lives": 3 }'</span>></span>
Start a new game
<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="36" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Working with the context object</h2>
<p>You may read and change the context from your client-side JavaScript:</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.layer.on(<span class="hljs-string">'heart:collected'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
up.context.lives++
})
</span></span></foreignObject></svg></code></pre>
<p>You may read and change the context from the server:</p>
<pre><code class="language-ruby"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GamesController</span> < ApplicationController</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">restart</span></span>
up.context[<span class="hljs-symbol">:lives</span>] = <span class="hljs-number">3</span>
render <span class="hljs-string">'stage1'</span>
<span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="37" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Use case: Re-using interactions in an overlay, but with a variation</h2>
<p>Context is useful when you want to re-use an existing interaction in an overlay, but make a slight variation.</p>
<ul>
<li>Assume you want to re-use your existing contacts index for a contact picker widget.</li>
<li>The contact picker opens the context index in an overlay where the user can choose a contact.</li>
<li>In this case the contact index should show an additional message "Pick a contact for project Foo", replacing <code>Foo</code> with the actual name of the project.</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="38" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<p>We can implement such an contact picker with this ERB template:</p>
<pre><code class="language-erb"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="xml"><%</span><span class="ruby"> form_for <span class="hljs-variable">@project</span> <span class="hljs-keyword">do</span> <span class="hljs-params">|form|</span> </span><span class="xml">%>
Contact: <%=</span><span class="ruby"> form.object.contact </span><span class="xml">%>
<span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">'/contacts'</span>
<span class="hljs-attr">up-layer</span>=<span class="hljs-string">'new'</span>
<span class="hljs-attr">up-accept-location</span>=<span class="hljs-string">'/contacts/*'</span>
<span class="hljs-attr">up-context</span>=<span class="hljs-string">'<%=</span></span></span><span class="ruby"> { <span class="hljs-symbol">project:</span> <span class="hljs-variable">@project</span>.name }.to_json </span><span class="xml"><span class="hljs-tag"><span class="hljs-string">%>'</span>></span>
Pick a contact
<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
...
<%</span><span class="ruby"> <span class="hljs-keyword">end</span> </span><span class="xml">%>
</span></span></span></foreignObject></svg></code></pre>
<p>Our effective contact object would now be something like <code>{ project: 'Hosting 2021' }</code>.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="39" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<p>The server can inspect the context in <code>/contacts/index.erb</code>:</p>
<pre><code class="language-erb"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="xml"><%</span><span class="ruby"> <span class="hljs-keyword">if</span> up.context[<span class="hljs-symbol">:project</span>] </span><span class="xml">%>
Pick a contact for <%=</span><span class="ruby"> up.context[<span class="hljs-symbol">:project</span>] </span><span class="xml">%>:
<%</span><span class="ruby"> <span class="hljs-keyword">else</span> </span><span class="xml">%>
List of contacts
<%</span><span class="ruby"> <span class="hljs-keyword">end</span> </span><span class="xml">%>
<%</span><span class="ruby"> <span class="hljs-variable">@contacts</span>.each <span class="hljs-keyword">do</span> <span class="hljs-params">|contact|</span> </span><span class="xml">%>
<span class="hljs-tag"><<span class="hljs-name">li</span>></span>...<span class="hljs-tag"></<span class="hljs-name">li</span>></span>
<%</span><span class="ruby"> <span class="hljs-keyword">end</span> </span><span class="xml">%>
</span></span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="40" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>New overlay HTML structure</h1>
<p>Unpoly 2 uses a more compact HTML markup for its overlays.<br />
If you have customized your modals and popup with CSS, this is a breaking change for you.</p>
<p>Luckily the new HTML structure is very similiar:</p>
<div class="row">
<div class="col">
<h2>Old modal HTML</h2>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"up-modal"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"up-modal-viewport"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"up-modal-dialog"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"up-modal-content"</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">"up-modal-dismiss"</span>></span>×<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
</span></span></foreignObject></svg></code></pre>
</div>
<div class="col">
<h2>New modal HTML</h2>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">up-modal</span>></span>
<span class="hljs-tag"><<span class="hljs-name">up-modal-viewport</span>></span>
<span class="hljs-tag"><<span class="hljs-name">up-modal-box</span>></span>
<span class="hljs-tag"><<span class="hljs-name">up-modal-content</span>></span>...<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">up-modal-dismiss</span>></span>×<span class="hljs-tag"></<span class="hljs-name">up-modal-dismiss</span>></span>
<span class="hljs-tag"></<span class="hljs-name">up-modal-box</span>></span>
<span class="hljs-tag"></<span class="hljs-name">up-modal-viewport</span>></span>
<span class="hljs-tag"></<span class="hljs-name">up-modal</span>></span>
</span></span></foreignObject></svg></code></pre>
</div>
</div>
<p>The HTML of other overlay modes was changed in the same way (e.g. <code><up-popup></code>).</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="41" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Customizing overlays with CSS</h1>
<p>If you have modified the appearance with CSS, you need to update your selectors.</p>
<h3>Old CSS</h3>
<pre><code class="language-css"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-selector-class">.up-popup-content</span> {
<span class="hljs-attribute">background-color</span>: <span class="hljs-number">#eeeeee</span>;
}
</span></span></foreignObject></svg></code></pre>
<h3>New CSS</h3>
<pre><code class="language-css"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up-popup-<span class="hljs-attribute">content</span> {
<span class="hljs-attribute">background-color</span>: <span class="hljs-number">#eeeeee</span>;
}
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="42" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Overlay classes</h1>
<p>You can assign a class to the overlay you're opening.</p>
<div class="row">
<div class="col">
<h3>JavaScript</h3>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.layer.open({
<span class="hljs-attr">url</span>: <span class="hljs-string">'/confirm-erase'</span>,
<span class="hljs-attr">method</span>: <span class="hljs-string">'delete'</span>,
<span class="hljs-attr">class</span>: <span class="hljs-string">'warning'</span>
})
</span></span></foreignObject></svg></code></pre>
</div>
<div class="col">
<h3>HTML</h3>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/confirm-erase"</span>
<span class="hljs-attr">up-method</span>=<span class="hljs-string">"delete"</span>
<span class="hljs-attr">up-layer</span>=<span class="hljs-string">"new"</span>
<span class="hljs-attr">up-class</span>=<span class="hljs-string">"warning"</span>></span>
Erase disk
<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
</span></span></foreignObject></svg></code></pre>
</div>
</div>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="43" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<p>The class will be assigned to the layer element:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">up-modal</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"warning"</span>></span>
...
<span class="hljs-tag"></<span class="hljs-name">up-modal</span>></span>
</span></span></foreignObject></svg></code></pre>
<p>You can now style "warning modals" in your CSS:</p>
<pre><code class="language-css"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up-modal<span class="hljs-selector-class">.warning</span> up-modal-box {
<span class="hljs-attribute">background-color</span>: yellow
}
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="44" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Overlay sizes</h1>
<p><strong><img class="emoji" draggable="false" alt="💡" src="https://twemoji.maxcdn.com/2/svg/1f4a1.svg" data-marp-twemoji=""/> In Unpoly 1, overlays grew with the size of the content.
This was impractical because<br />
a single long line of text would stretch the overlay to its maximum width.</strong></p>
<p>Because of this most projects have configured modals to have a fixed size.<br />
Many projects also have hacks to open modals with different sizes.</p>
<p>In Unpoly 2 all overlays have a given <strong>size</strong> that sets a maximum width:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><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-layer</span>=<span class="hljs-string">"new"</span> <span class="hljs-attr">up-size</span>=<span class="hljs-string">"small"</span>></span>open small modal<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
<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-layer</span>=<span class="hljs-string">"new"</span> <span class="hljs-attr">up-size</span>=<span class="hljs-string">"medium"</span>></span>open medium modal<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
<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-layer</span>=<span class="hljs-string">"new"</span> <span class="hljs-attr">up-size</span>=<span class="hljs-string">"large"</span>></span>open large modal<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
<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-layer</span>=<span class="hljs-string">"new"</span> <span class="hljs-attr">up-size</span>=<span class="hljs-string">"auto"</span>></span>open growing modal<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="45" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Default sizes</h2>
<table>
<thead>
<tr>
<th style="text-align:right">Mode</th>
<th style="text-align:right">small</th>
<th style="text-align:right">medium</th>
<th style="text-align:right">large</th>
<th>grow</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:right"><code>modal</code></td>
<td style="text-align:right"><code>350px</code></td>
<td style="text-align:right"><code>650px</code></td>
<td style="text-align:right"><code>1000px</code></td>
<td>grow with content</td>
</tr>
<tr>
<td style="text-align:right"><code>popup</code></td>
<td style="text-align:right"><code>180px</code></td>
<td style="text-align:right"><code>300px</code></td>
<td style="text-align:right"><code>550px</code></td>
<td>grow with content</td>
</tr>
<tr>
<td style="text-align:right"><code>drawer</code></td>
<td style="text-align:right"><code>150px</code></td>
<td style="text-align:right"><code>340px</code></td>
<td style="text-align:right"><code>600px</code></td>
<td>grow with content</td>
</tr>
<tr>
<td style="text-align:right"><code>cover</code></td>
<td style="text-align:right"><code>100%</code></td>
<td style="text-align:right"><code>100%</code></td>
<td style="text-align:right"><code>100%</code></td>
<td><code>100%</code></td>
</tr>
</tbody>
</table>
<br />
<p>These are generally wider than Bootstrap's counterparts.</p>
<p>Regardless of size, overlays never grow wider than the screen width.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="46" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Customizing overlay sizes</h2>
<p>You can customize sizes with CSS:</p>
<pre><code class="language-css"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up-modal<span class="hljs-selector-attr">[size=medium]</span> up-modal-box {
<span class="hljs-attribute">width</span>: <span class="hljs-number">500px</span>;
}
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="47" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Customizing overlay HTML</h1>
<p>The HTML markup for a given overlay mode is now static.<br />
There is no <code>up.modal.config.template</code> anymore.</p>
<p>Many former use cases for <code>up.modal.config.template</code> are covered by assigning a class or size.</p>
<p>If you do need to customize the overlay HTML, you may use the <code>up:layer:opened</code> event to modify the layer as it becomes visible. The event is emitted
before the opening animation starts.</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.on(<span class="hljs-string">'up:layer:opened'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">event</span>) </span>{
<span class="hljs-keyword">if</span> (isChristmas()) {
up.element.affix(event.layer.element, <span class="hljs-string">'.santa-hat'</span>, <span class="hljs-attr">text</span>: <span class="hljs-string">'Merry Christmas!'</span>)
}
})
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="48" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Drawers and modal "flavors"</h1>
<p>Drawer overlays used to be a modal "flavor" in Unpoly 1.<br />
They shared their HTML markup with
standard modal overlays.</p>
<p>There is no <code>up.modal.flavors</code> in Unpoly 2 anymore.</p>
<p>Drawers are now a first-class overlay
mode with their own <code><up-drawer></code> element.</p>
<p>There is currently no API to define a custom overlay mode.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="49" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>New layer mode: Cover</h1>
<p>Unpoly 2 ships with a new layer mode called <code>cover</code>.</p>
<p>It overlays the <em>entire</em> page, including application layout.<br />
It brings its own scrollbar.</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><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-layer</span>=<span class="hljs-string">"new cover"</span>></span>
</span></span></foreignObject></svg></code></pre>
<p>You often see cover overlays in mobile apps, e.g. on settings screens.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="50" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Dismissability can be fine-tuned</h1>
<p>In Unpoly 1 you could prevent a user from closing a layer with the <code>{ closable: false }</code> option.</p>
<p>In Unpoly 2 you may choose which closing methods are available to the user:</p>
<table>
<thead>
<tr>
<th>Option</th>
<th>Effect</th>
<th>Dismiss value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>{ keyDismissable }</code></td>
<td>Enables dimissing with Escape key</td>
<td><code>:key</code></td>
</tr>
<tr>
<td><code>{ outsideDismissable }</code></td>
<td>Enables dismissing by clicking on the background</td>
<td><code>:outside</code></td>
</tr>
<tr>
<td><code>{ buttonDismissable }</code></td>
<td>Adds an "X" button to the layer</td>
<td><code>:button</code></td>
</tr>
</tbody>
</table>
<p>You may also enable or disable <em>all</em> closing methods together with the <code>{ dismissable }</code> option.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="51" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Overlays without history</h1>
<p><strong><img class="emoji" draggable="false" alt="💡" src="https://twemoji.maxcdn.com/2/svg/1f4a1.svg" data-marp-twemoji=""/> Modals without history required too much code in Unpoly 1.</strong></p>
<p>In Unpoly 1 you could use <code>{ history: false }</code> to open an overlay without updating the browser history. However, every user navigation within that overlay <em>would</em> affect history, unless you had <code>[up-history=false]</code> on <em>every</em> link. This is impractical, since a link should not need to know whether it is used within an overlay.</p>
<p>Unpoly 2 reworks that behavior to what you would expect:</p>
<ul>
<li>When an overlay is opened without history, no contained link or form will ever update history.</li>
<li>When an overlay without history opens another overlay, that other overlay will never update history.</li>
<li>Layers without history know their current location URL (<code>up.layer.location</code>).</li>
<li>Layers without history support the <code>.up-current</code> class.</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="52" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Sub-interactions</h1>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="53" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Why we need sub-interactions</h1>
<p><img class="emoji" draggable="false" alt="🎥" src="https://twemoji.maxcdn.com/2/svg/1f3a5.svg" data-marp-twemoji=""/> <em>Show scenario in the demo app</em></p>
<p>This story is the base use case for a sub-interaction:</p>
<ul>
<li>User starts filling out the form for a new project</li>
<li>To create a project, the user must select a company. But the desired company does not yet exist.</li>
<li>The user may open a new overlay to create the missing company.<br />
The unfinished project form remains open in the background.</li>
<li>When the company was created in the overlay, the overlay should close.<br />
The project form should now have the newly created company selected.</li>
</ul>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="54" data-class="no-watermark no-padding" data-theme="unpoly2-slides" class="no-watermark no-padding" style="--class:no-watermark no-padding;--theme:unpoly2-slides;"><img src="images/subinteraction-flow.svg" height="100%" />
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="55" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Unpoly 1 modals didn't work well for this</h1>
<p><strong><img class="emoji" draggable="false" alt="💡" src="https://twemoji.maxcdn.com/2/svg/1f4a1.svg" data-marp-twemoji=""/> Unpoly 1 hade no way to communicate the "result" of an overlay back to the parent layer.</strong></p>
<p>Overlay content needed to update fragments in the parent layer to continue the story there.
This required the overlay to know the parent layer's state, <em>coupling</em> the sub-interaction to the parent interaction.</p>
<p>In Unpoly 2 overlays no longer need to know about the parent layer's state.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="56" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Acceptance conditions in Unpoly 2</h1>
<p><strong>When opening an overlay in Unpoly 2, you may define a <em>condition</em> when the overlay interaction ends.</strong></p>
<p>When the condition occurs, the overlay is automatically closed and a callback is run.</p>
<p>This completely <em>decouples</em> the outer interaction from sub-interactions.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="57" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Overlay result values</h1>
<p>Overlays in Unpoly 2 may have a <em>result value</em>.</p>
<p>E.g. if the user selects a value, creates a record or confirms an action,
we consider the overlay to be <strong>accepted with that value</strong>.</p>
<pre><code class="language-js"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap>up.layer.open({
<span class="hljs-attr">url</span>: <span class="hljs-string">'/select-user'</span>,
<span class="hljs-attr">onAccepted</span>: <span class="hljs-function">(<span class="hljs-params">event</span>) =></span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Got user '</span>, event.value)
})
</span></span></foreignObject></svg></code></pre>
<p>The following slides examine how an overlay can be <em>accepted</em>.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="58" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h1>Accepting when a location is reached</h1>
<p>The following will open an overlay that closes once a URL like <code>/companies/123</code> is reached:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/companies/new"</span>
<span class="hljs-attr">up-layer</span>=<span class="hljs-string">"new"</span>
<span class="hljs-attr">up-accept-location</span>=<span class="hljs-string">"/companies/$id"</span>
<span class="hljs-attr">up-on-accepted</span>=<span class="hljs-string">"alert('New company with ID ' + value.id)"</span>></span>
New company
<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
</span></span></foreignObject></svg></code></pre>
<p>Placeholders in the URL pattern (<code>$id</code>) become the overlay's <em>acceptance value</em>.</p>
<p>The <code>[up-on-accepted]</code> callback is called with an acceptance value.</p>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="59" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Reloading on acceptance</h2>
<p>A <strong>common callback</strong> is to reload an element in the parent layer:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/companies/new"</span>
<span class="hljs-attr">up-layer</span>=<span class="hljs-string">"new"</span>
<span class="hljs-attr">up-accept-location</span>=<span class="hljs-string">"/companies/$id"</span>
<span class="hljs-attr">up-on-accepted</span>=<span class="hljs-string">"up.reload('.company-list')"</span>></span>
New company
<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"company-list"</span>></span>
...
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="60" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Adding options to an existing select</h2>
<p>Another common callback reloads <code><select></code> options and selects the new foreign key:</p>
<pre><code class="language-html"><svg data-marp-fitting="svg" data-marp-fitting-code><foreignObject><span data-marp-fitting-svg-content><span data-marp-fitting-svg-content-wrap><span class="hljs-tag"><<span class="hljs-name">select</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"company"</span>></span>...<span class="hljs-tag"></<span class="hljs-name">select</span>></span>
<span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/companies/new"</span>
<span class="hljs-attr">up-layer</span>=<span class="hljs-string">"new"</span>
<span class="hljs-attr">up-accept-location</span>=<span class="hljs-string">"/companies/$id"</span>
<span class="hljs-attr">up-on-accepted</span>=<span class="hljs-string">"up.validate('select', { params: { company: value.id } })"</span>></span>
New company
<span class="hljs-tag"></<span class="hljs-name">a</span>></span>
</span></span></foreignObject></svg></code></pre>
</section>
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="61" data-theme="unpoly2-slides" style="--theme:unpoly2-slides;">
<h2>Why this is useful</h2>
<ul>