diff --git a/404.html b/404.html new file mode 100644 index 0000000..8f1622c --- /dev/null +++ b/404.html @@ -0,0 +1,28 @@ +Page not found - Node.js Design Patterns Third Edition

đŸ˜±
404 Error
Page not found!

The page you were looking for could not be found

Man reading Node.js Design Patterns book
\ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..c35b88c --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +www.nodejsdesignpatterns.com \ No newline at end of file diff --git a/ahrefs_54973d5b1e8ffe5dbee78a2e55e391c1fb00e20d517572f9b01f6aca183a696e b/ahrefs_54973d5b1e8ffe5dbee78a2e55e391c1fb00e20d517572f9b01f6aca183a696e new file mode 100644 index 0000000..12a2538 --- /dev/null +++ b/ahrefs_54973d5b1e8ffe5dbee78a2e55e391c1fb00e20d517572f9b01f6aca183a696e @@ -0,0 +1 @@ +ahrefs-site-verification_54973d5b1e8ffe5dbee78a2e55e391c1fb00e20d517572f9b01f6aca183a696e \ No newline at end of file diff --git a/android-chrome-192x192.png b/android-chrome-192x192.png new file mode 100755 index 0000000..daae6e8 Binary files /dev/null and b/android-chrome-192x192.png differ diff --git a/android-chrome-512x512.png b/android-chrome-512x512.png new file mode 100755 index 0000000..592dd0b Binary files /dev/null and b/android-chrome-512x512.png differ diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100755 index 0000000..7d481d4 Binary files /dev/null and b/apple-touch-icon.png differ diff --git a/assets/0768f4fb0e6afbc52000.svg b/assets/0768f4fb0e6afbc52000.svg new file mode 100644 index 0000000..1bcaba7 --- /dev/null +++ b/assets/0768f4fb0e6afbc52000.svg @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_400i.svg"; \ No newline at end of file diff --git a/assets/09512a65c0f7628f2205.eot b/assets/09512a65c0f7628f2205.eot new file mode 100644 index 0000000..d3b89e9 --- /dev/null +++ b/assets/09512a65c0f7628f2205.eot @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_400.eot"; \ No newline at end of file diff --git a/assets/128aef13e266ee58a02b.svg b/assets/128aef13e266ee58a02b.svg new file mode 100644 index 0000000..25420de --- /dev/null +++ b/assets/128aef13e266ee58a02b.svg @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Playfair_Display_600.svg"; \ No newline at end of file diff --git a/assets/26c33577d1a552938896.eot b/assets/26c33577d1a552938896.eot new file mode 100644 index 0000000..6989b11 --- /dev/null +++ b/assets/26c33577d1a552938896.eot @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_700i.eot"; \ No newline at end of file diff --git a/assets/346ec5009af8fc247876.eot b/assets/346ec5009af8fc247876.eot new file mode 100644 index 0000000..746355b --- /dev/null +++ b/assets/346ec5009af8fc247876.eot @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Coustard_400.eot"; \ No newline at end of file diff --git a/assets/376202fa33a55b4e7b57.woff2 b/assets/376202fa33a55b4e7b57.woff2 new file mode 100644 index 0000000..03e3de0 --- /dev/null +++ b/assets/376202fa33a55b4e7b57.woff2 @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Coustard_400.woff2"; \ No newline at end of file diff --git a/assets/3a434a4352510ae626c5.woff b/assets/3a434a4352510ae626c5.woff new file mode 100644 index 0000000..9256c5c --- /dev/null +++ b/assets/3a434a4352510ae626c5.woff @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Coustard_400.woff"; \ No newline at end of file diff --git a/assets/3e1003e006aa95f46f10.ttf b/assets/3e1003e006aa95f46f10.ttf new file mode 100644 index 0000000..842ffcb --- /dev/null +++ b/assets/3e1003e006aa95f46f10.ttf @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Coustard_400.ttf"; \ No newline at end of file diff --git a/assets/4cd2bdad5da83fe79b0f.woff b/assets/4cd2bdad5da83fe79b0f.woff new file mode 100644 index 0000000..5a2bd33 --- /dev/null +++ b/assets/4cd2bdad5da83fe79b0f.woff @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_700i.woff"; \ No newline at end of file diff --git a/assets/538cd47e5b984f6cd2d5.woff2 b/assets/538cd47e5b984f6cd2d5.woff2 new file mode 100644 index 0000000..822d8a2 --- /dev/null +++ b/assets/538cd47e5b984f6cd2d5.woff2 @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_700i.woff2"; \ No newline at end of file diff --git a/assets/54da969939e02da4ef8a.woff b/assets/54da969939e02da4ef8a.woff new file mode 100644 index 0000000..985b113 --- /dev/null +++ b/assets/54da969939e02da4ef8a.woff @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Playfair_Display_600.woff"; \ No newline at end of file diff --git a/assets/5ad1fe7e8556951d2118.eot b/assets/5ad1fe7e8556951d2118.eot new file mode 100644 index 0000000..e9c4d19 --- /dev/null +++ b/assets/5ad1fe7e8556951d2118.eot @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Playfair_Display_600.eot"; \ No newline at end of file diff --git a/assets/5dc4202504b4f981d4bd.woff b/assets/5dc4202504b4f981d4bd.woff new file mode 100644 index 0000000..cc92a03 --- /dev/null +++ b/assets/5dc4202504b4f981d4bd.woff @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_400.woff"; \ No newline at end of file diff --git a/assets/73346027d4fde332f075.woff2 b/assets/73346027d4fde332f075.woff2 new file mode 100644 index 0000000..8c1654a --- /dev/null +++ b/assets/73346027d4fde332f075.woff2 @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_400.woff2"; \ No newline at end of file diff --git a/assets/77883d091aa094551645.svg b/assets/77883d091aa094551645.svg new file mode 100644 index 0000000..d2a6963 --- /dev/null +++ b/assets/77883d091aa094551645.svg @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_400.svg"; \ No newline at end of file diff --git a/assets/906c03462ad64f540d89.ttf b/assets/906c03462ad64f540d89.ttf new file mode 100644 index 0000000..79ea5e2 --- /dev/null +++ b/assets/906c03462ad64f540d89.ttf @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_700i.ttf"; \ No newline at end of file diff --git a/assets/a16393948d50476b3f93.woff2 b/assets/a16393948d50476b3f93.woff2 new file mode 100644 index 0000000..6d95628 --- /dev/null +++ b/assets/a16393948d50476b3f93.woff2 @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Playfair_Display_600.woff2"; \ No newline at end of file diff --git a/assets/a297162908dc185b3c12.woff b/assets/a297162908dc185b3c12.woff new file mode 100644 index 0000000..01c9d5d --- /dev/null +++ b/assets/a297162908dc185b3c12.woff @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_400i.woff"; \ No newline at end of file diff --git a/assets/b92844af222958865654.ttf b/assets/b92844af222958865654.ttf new file mode 100644 index 0000000..3b5f4e1 --- /dev/null +++ b/assets/b92844af222958865654.ttf @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Playfair_Display_600.ttf"; \ No newline at end of file diff --git a/assets/bfa596a50f5557f7023c.eot b/assets/bfa596a50f5557f7023c.eot new file mode 100644 index 0000000..4eb6e3f --- /dev/null +++ b/assets/bfa596a50f5557f7023c.eot @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_700.eot"; \ No newline at end of file diff --git a/assets/c0de456eacace5e47c36.ttf b/assets/c0de456eacace5e47c36.ttf new file mode 100644 index 0000000..67e3043 --- /dev/null +++ b/assets/c0de456eacace5e47c36.ttf @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_400i.ttf"; \ No newline at end of file diff --git a/assets/c3a6c890c794f3d5ada1.woff b/assets/c3a6c890c794f3d5ada1.woff new file mode 100644 index 0000000..c363412 --- /dev/null +++ b/assets/c3a6c890c794f3d5ada1.woff @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_700.woff"; \ No newline at end of file diff --git a/assets/c45d3e9755c98a4a74a1.woff2 b/assets/c45d3e9755c98a4a74a1.woff2 new file mode 100644 index 0000000..e74da74 --- /dev/null +++ b/assets/c45d3e9755c98a4a74a1.woff2 @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_400i.woff2"; \ No newline at end of file diff --git a/assets/c53f2ac2814955806ace.woff2 b/assets/c53f2ac2814955806ace.woff2 new file mode 100644 index 0000000..4e4c73e --- /dev/null +++ b/assets/c53f2ac2814955806ace.woff2 @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_700.woff2"; \ No newline at end of file diff --git a/assets/c5f9d77b067d6de089f8.ttf b/assets/c5f9d77b067d6de089f8.ttf new file mode 100644 index 0000000..42b0982 --- /dev/null +++ b/assets/c5f9d77b067d6de089f8.ttf @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_400.ttf"; \ No newline at end of file diff --git a/assets/d7407b25c9bdf3ccfb08.svg b/assets/d7407b25c9bdf3ccfb08.svg new file mode 100644 index 0000000..ef428ae --- /dev/null +++ b/assets/d7407b25c9bdf3ccfb08.svg @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_700.svg"; \ No newline at end of file diff --git a/assets/d7ca58e9f57c24a103e8.svg b/assets/d7ca58e9f57c24a103e8.svg new file mode 100644 index 0000000..3f8cebe --- /dev/null +++ b/assets/d7ca58e9f57c24a103e8.svg @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Coustard_400.svg"; \ No newline at end of file diff --git a/assets/d914f6425a5885c87306.ttf b/assets/d914f6425a5885c87306.ttf new file mode 100644 index 0000000..3bb7eed --- /dev/null +++ b/assets/d914f6425a5885c87306.ttf @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_700.ttf"; \ No newline at end of file diff --git a/assets/dd8640498a6a77b6cded.svg b/assets/dd8640498a6a77b6cded.svg new file mode 100644 index 0000000..0907c7a --- /dev/null +++ b/assets/dd8640498a6a77b6cded.svg @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_700i.svg"; \ No newline at end of file diff --git a/assets/e93354fee5a146339fd7.eot b/assets/e93354fee5a146339fd7.eot new file mode 100644 index 0000000..da30222 --- /dev/null +++ b/assets/e93354fee5a146339fd7.eot @@ -0,0 +1 @@ +export default __webpack_public_path__ + "/fonts/Open_Sans_400i.eot"; \ No newline at end of file diff --git a/assets/fonts/Coustard_400.eot b/assets/fonts/Coustard_400.eot new file mode 100644 index 0000000..61bfb90 Binary files /dev/null and b/assets/fonts/Coustard_400.eot differ diff --git a/assets/fonts/Coustard_400.svg b/assets/fonts/Coustard_400.svg new file mode 100644 index 0000000..1e76cc6 --- /dev/null +++ b/assets/fonts/Coustard_400.svg @@ -0,0 +1,611 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Coustard_400.ttf b/assets/fonts/Coustard_400.ttf new file mode 100644 index 0000000..3cc701c Binary files /dev/null and b/assets/fonts/Coustard_400.ttf differ diff --git a/assets/fonts/Coustard_400.woff b/assets/fonts/Coustard_400.woff new file mode 100644 index 0000000..c3940ab Binary files /dev/null and b/assets/fonts/Coustard_400.woff differ diff --git a/assets/fonts/Coustard_400.woff2 b/assets/fonts/Coustard_400.woff2 new file mode 100644 index 0000000..2b3e1da Binary files /dev/null and b/assets/fonts/Coustard_400.woff2 differ diff --git a/assets/fonts/Open_Sans_400.eot b/assets/fonts/Open_Sans_400.eot new file mode 100644 index 0000000..8f3becf Binary files /dev/null and b/assets/fonts/Open_Sans_400.eot differ diff --git a/assets/fonts/Open_Sans_400.svg b/assets/fonts/Open_Sans_400.svg new file mode 100644 index 0000000..78eb653 --- /dev/null +++ b/assets/fonts/Open_Sans_400.svg @@ -0,0 +1,336 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Open_Sans_400.ttf b/assets/fonts/Open_Sans_400.ttf new file mode 100644 index 0000000..fb23764 Binary files /dev/null and b/assets/fonts/Open_Sans_400.ttf differ diff --git a/assets/fonts/Open_Sans_400.woff b/assets/fonts/Open_Sans_400.woff new file mode 100644 index 0000000..39e88ed Binary files /dev/null and b/assets/fonts/Open_Sans_400.woff differ diff --git a/assets/fonts/Open_Sans_400.woff2 b/assets/fonts/Open_Sans_400.woff2 new file mode 100644 index 0000000..e9f58b7 Binary files /dev/null and b/assets/fonts/Open_Sans_400.woff2 differ diff --git a/assets/fonts/Open_Sans_400i.eot b/assets/fonts/Open_Sans_400i.eot new file mode 100644 index 0000000..3837009 Binary files /dev/null and b/assets/fonts/Open_Sans_400i.eot differ diff --git a/assets/fonts/Open_Sans_400i.svg b/assets/fonts/Open_Sans_400i.svg new file mode 100644 index 0000000..e6a951f --- /dev/null +++ b/assets/fonts/Open_Sans_400i.svg @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Open_Sans_400i.ttf b/assets/fonts/Open_Sans_400i.ttf new file mode 100644 index 0000000..7a092e4 Binary files /dev/null and b/assets/fonts/Open_Sans_400i.ttf differ diff --git a/assets/fonts/Open_Sans_400i.woff b/assets/fonts/Open_Sans_400i.woff new file mode 100644 index 0000000..2bfa582 Binary files /dev/null and b/assets/fonts/Open_Sans_400i.woff differ diff --git a/assets/fonts/Open_Sans_400i.woff2 b/assets/fonts/Open_Sans_400i.woff2 new file mode 100644 index 0000000..10031c0 Binary files /dev/null and b/assets/fonts/Open_Sans_400i.woff2 differ diff --git a/assets/fonts/Open_Sans_700.eot b/assets/fonts/Open_Sans_700.eot new file mode 100644 index 0000000..5b9ddb4 Binary files /dev/null and b/assets/fonts/Open_Sans_700.eot differ diff --git a/assets/fonts/Open_Sans_700.svg b/assets/fonts/Open_Sans_700.svg new file mode 100644 index 0000000..8e6b61a --- /dev/null +++ b/assets/fonts/Open_Sans_700.svg @@ -0,0 +1,334 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Open_Sans_700.ttf b/assets/fonts/Open_Sans_700.ttf new file mode 100644 index 0000000..c9a3c7a Binary files /dev/null and b/assets/fonts/Open_Sans_700.ttf differ diff --git a/assets/fonts/Open_Sans_700.woff b/assets/fonts/Open_Sans_700.woff new file mode 100644 index 0000000..b8b46d0 Binary files /dev/null and b/assets/fonts/Open_Sans_700.woff differ diff --git a/assets/fonts/Open_Sans_700.woff2 b/assets/fonts/Open_Sans_700.woff2 new file mode 100644 index 0000000..3a38286 Binary files /dev/null and b/assets/fonts/Open_Sans_700.woff2 differ diff --git a/assets/fonts/Open_Sans_700i.eot b/assets/fonts/Open_Sans_700i.eot new file mode 100644 index 0000000..ea3c763 Binary files /dev/null and b/assets/fonts/Open_Sans_700i.eot differ diff --git a/assets/fonts/Open_Sans_700i.svg b/assets/fonts/Open_Sans_700i.svg new file mode 100644 index 0000000..80b5635 --- /dev/null +++ b/assets/fonts/Open_Sans_700i.svg @@ -0,0 +1,342 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Open_Sans_700i.ttf b/assets/fonts/Open_Sans_700i.ttf new file mode 100644 index 0000000..ae7db67 Binary files /dev/null and b/assets/fonts/Open_Sans_700i.ttf differ diff --git a/assets/fonts/Open_Sans_700i.woff b/assets/fonts/Open_Sans_700i.woff new file mode 100644 index 0000000..2fb93a3 Binary files /dev/null and b/assets/fonts/Open_Sans_700i.woff differ diff --git a/assets/fonts/Open_Sans_700i.woff2 b/assets/fonts/Open_Sans_700i.woff2 new file mode 100644 index 0000000..5153813 Binary files /dev/null and b/assets/fonts/Open_Sans_700i.woff2 differ diff --git a/assets/fonts/Playfair_Display_600.eot b/assets/fonts/Playfair_Display_600.eot new file mode 100644 index 0000000..c8c4480 Binary files /dev/null and b/assets/fonts/Playfair_Display_600.eot differ diff --git a/assets/fonts/Playfair_Display_600.svg b/assets/fonts/Playfair_Display_600.svg new file mode 100644 index 0000000..d044514 --- /dev/null +++ b/assets/fonts/Playfair_Display_600.svg @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Playfair_Display_600.ttf b/assets/fonts/Playfair_Display_600.ttf new file mode 100644 index 0000000..8c2a173 Binary files /dev/null and b/assets/fonts/Playfair_Display_600.ttf differ diff --git a/assets/fonts/Playfair_Display_600.woff b/assets/fonts/Playfair_Display_600.woff new file mode 100644 index 0000000..9caffde Binary files /dev/null and b/assets/fonts/Playfair_Display_600.woff differ diff --git a/assets/fonts/Playfair_Display_600.woff2 b/assets/fonts/Playfair_Display_600.woff2 new file mode 100644 index 0000000..d96b6bf Binary files /dev/null and b/assets/fonts/Playfair_Display_600.woff2 differ diff --git a/assets/main.2099d5a76e3050e6dea6.js b/assets/main.2099d5a76e3050e6dea6.js new file mode 100644 index 0000000..a135969 --- /dev/null +++ b/assets/main.2099d5a76e3050e6dea6.js @@ -0,0 +1,2 @@ +(()=>{"use strict";function t(e){return t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},t(e)}function e(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),r.push.apply(r,n)}return r}function r(r){for(var n=1;n1&&void 0!==arguments[1]&&arguments[1],r="true"===t.getAttribute("aria-expanded"),n=t.getAttribute("data-faq-id"),i=t.getAttribute("aria-controls"),o=document.getElementById(i);e&&window.location.hash.length>0&&n===window.location.hash.substr(1)?window.dataLayer&&window.dataLayer.push({event:"faq_open_".concat(n)}):r?(t.setAttribute("aria-expanded","false"),o.style.maxHeight=0,o.style.padding="0 0 0 3.2rem"):(window.dataLayer&&window.dataLayer.push({event:"faq_open_".concat(n)}),t.setAttribute("aria-expanded","true"),o.style.maxHeight="1000px",o.style.padding="1.5rem 0 1.5rem 3.2rem")}document.querySelectorAll("dl.faq button").forEach((function(e){e.addEventListener("click",(function(e){e.preventDefault(),t(e.currentTarget)})),t(e,!0)}))}()}))})(); +//# sourceMappingURL=main.2099d5a76e3050e6dea6.js.map \ No newline at end of file diff --git a/assets/main.2099d5a76e3050e6dea6.js.map b/assets/main.2099d5a76e3050e6dea6.js.map new file mode 100644 index 0000000..05c12d3 --- /dev/null +++ b/assets/main.2099d5a76e3050e6dea6.js.map @@ -0,0 +1 @@ +{"version":3,"file":"main.2099d5a76e3050e6dea6.js","mappings":"uqCAIAA,SAASC,iBAAiB,oBAAoB,YCJ/B,WACb,IAAMC,EAAOF,SAASG,qBAAqB,QAAQ,GAC7CC,EAASJ,SAASK,eAAe,UACjCC,EAAoB,CAAC,wBACrBC,EAAsB,CAAC,eAAgB,cAEzCC,EAAc,EACdC,EAAQ,CACVC,kBAAkB,EAClBC,kBAAkB,GAGpB,SAASC,EAAaC,GACpB,IAAMC,EAAQC,EAAAA,EAAA,GAAQN,GAAUI,GAC5BG,KAAKC,UAAUH,KAAcE,KAAKC,UAAUR,KAC9CA,EAAQK,EAKZ,WACE,GAAIL,EAAMC,kBAAoBD,EAAME,iBAAkB,CACpD,IAAK,IAALO,EAAA,EAAAC,EAAwBb,EAAiBY,EAAAC,EAAAC,OAAAF,IAAE,CAAtC,IAAMG,EAASF,EAAAD,GAClBhB,EAAKoB,UAAUC,IAAIF,EACrB,CACA,IAAK,IAALG,EAAA,EAAAC,EAAwBlB,EAAmBiB,EAAAC,EAAAL,OAAAI,IAAE,CAAxC,IAAMH,EAASI,EAAAD,GAClBpB,EAAOkB,UAAUC,IAAIF,EACvB,CACF,KAAO,CACL,IAAK,IAALK,EAAA,EAAAC,EAAwBrB,EAAiBoB,EAAAC,EAAAP,OAAAM,IAAE,CAAtC,IAAML,EAASM,EAAAD,GAClBxB,EAAKoB,UAAUM,OAAOP,EACxB,CACA,IAAK,IAALQ,EAAA,EAAAC,EAAwBvB,EAAmBsB,EAAAC,EAAAV,OAAAS,IAAE,CAAxC,IAAMR,EAASS,EAAAD,GAClBzB,EAAOkB,UAAUM,OAAOP,EAC1B,CACF,CACF,CApBIU,GAEJ,CAoBiBC,OAAOC,WAAW,iCAC1BhC,iBAAiB,UAAU,SAACiC,GACnCtB,EAAY,CAAEF,iBAAkBwB,EAAEC,SACpC,IAEAH,OAAO/B,iBAAiB,UAAU,SAACiC,GAEjCtB,EAAY,CAAED,iBADQqB,OAAOI,QAAU5B,IAEvCA,EAAcwB,OAAOI,OACvB,GACF,ED3CEhC,GELa,WAGb,SAASiC,EAAQC,GAA2B,IAAtBC,EAAYC,UAAApB,OAAA,QAAAqB,IAAAD,UAAA,IAAAA,UAAA,GAC1BE,EAAmD,SAAtCJ,EAAIK,aAAa,iBAC9BC,EAAQN,EAAIK,aAAa,eACzBE,EAAWP,EAAIK,aAAa,iBAC5BG,EAAS9C,SAASK,eAAewC,GAEnCN,GAAgBP,OAAOe,SAASC,KAAK5B,OAAS,GAAKwB,IAAUZ,OAAOe,SAASC,KAAKC,OAAO,GACvFjB,OAAOkB,WACTlB,OAAOkB,UAAUC,KAAK,CAAEC,MAAO,YAAFC,OAAcT,KAK3CF,GACFJ,EAAIgB,aAAa,gBAAiB,SAClCR,EAAOS,MAAMC,UAAY,EACzBV,EAAOS,MAAME,QAAU,iBAEnBzB,OAAOkB,WACTlB,OAAOkB,UAAUC,KAAK,CAAEC,MAAO,YAAFC,OAAcT,KAE7CN,EAAIgB,aAAa,gBAAiB,QAClCR,EAAOS,MAAMC,UAAY,SACzBV,EAAOS,MAAME,QAAU,yBAE3B,CA3BgBzD,SAAS0D,iBAAiB,iBA6BlCC,SAAQ,SAACC,GACfA,EAAO3D,iBAAiB,SAAS,SAACiC,GAChCA,EAAE2B,iBACFxB,EAAOH,EAAE4B,cACX,IACAzB,EAAOuB,GAAQ,EACjB,GACF,CF/BEG,EACF,G","sources":["webpack://nodejs-design-patterns-book/./src/js/index.js","webpack://nodejs-design-patterns-book/./src/js/navbar.js","webpack://nodejs-design-patterns-book/./src/js/faqs.js"],"sourcesContent":["import '../scss/style.scss'\nimport navbar from './navbar.js'\nimport faqs from './faqs.js'\n\ndocument.addEventListener('DOMContentLoaded', () => {\n navbar() // init navbar\n faqs() // init faqs\n})\n","export default function navbar () {\n const html = document.getElementsByTagName('html')[0]\n const navbar = document.getElementById('navbar')\n const htmlStickyClasses = ['has-navbar-fixed-top']\n const navbarStickyClasses = ['is-fixed-top', 'has-shadow']\n\n let lastScrollY = 0\n let state = {\n stickyBarEnabled: true,\n stickyBarVisible: true\n }\n\n function updateState (stateChanges) {\n const newState = { ...state, ...stateChanges }\n if (JSON.stringify(newState) !== JSON.stringify(state)) {\n state = newState\n update()\n }\n }\n\n function update () {\n if (state.stickyBarEnabled && state.stickyBarVisible) {\n for (const className of htmlStickyClasses) {\n html.classList.add(className)\n }\n for (const className of navbarStickyClasses) {\n navbar.classList.add(className)\n }\n } else {\n for (const className of htmlStickyClasses) {\n html.classList.remove(className)\n }\n for (const className of navbarStickyClasses) {\n navbar.classList.remove(className)\n }\n }\n }\n\n const mobileMq = window.matchMedia('screen and (max-width: 768px)')\n mobileMq.addEventListener('change', (e) => {\n updateState({ stickyBarEnabled: e.matches })\n })\n\n window.addEventListener('scroll', (e) => {\n const isScrollingUp = window.scrollY < lastScrollY\n updateState({ stickyBarVisible: isScrollingUp })\n lastScrollY = window.scrollY\n })\n}\n","export default function faqs () {\n const buttons = document.querySelectorAll('dl.faq button')\n\n function toggle (btn, skipIfAnchor = false) {\n const isExpanded = btn.getAttribute('aria-expanded') === 'true'\n const faqId = btn.getAttribute('data-faq-id')\n const targetId = btn.getAttribute('aria-controls')\n const target = document.getElementById(targetId)\n\n if (skipIfAnchor && window.location.hash.length > 0 && faqId === window.location.hash.substr(1)) {\n if (window.dataLayer) {\n window.dataLayer.push({ event: `faq_open_${faqId}` })\n }\n return\n }\n\n if (isExpanded) {\n btn.setAttribute('aria-expanded', 'false')\n target.style.maxHeight = 0\n target.style.padding = '0 0 0 3.2rem'\n } else {\n if (window.dataLayer) {\n window.dataLayer.push({ event: `faq_open_${faqId}` })\n }\n btn.setAttribute('aria-expanded', 'true')\n target.style.maxHeight = '1000px'\n target.style.padding = '1.5rem 0 1.5rem 3.2rem'\n }\n }\n\n buttons.forEach((button) => {\n button.addEventListener('click', (e) => {\n e.preventDefault()\n toggle(e.currentTarget)\n })\n toggle(button, true) // once the page loads collapse all tha faqs\n })\n}\n"],"names":["document","addEventListener","html","getElementsByTagName","navbar","getElementById","htmlStickyClasses","navbarStickyClasses","lastScrollY","state","stickyBarEnabled","stickyBarVisible","updateState","stateChanges","newState","_objectSpread","JSON","stringify","_i","_htmlStickyClasses","length","className","classList","add","_i2","_navbarStickyClasses","_i3","_htmlStickyClasses2","remove","_i4","_navbarStickyClasses2","update","window","matchMedia","e","matches","scrollY","toggle","btn","skipIfAnchor","arguments","undefined","isExpanded","getAttribute","faqId","targetId","target","location","hash","substr","dataLayer","push","event","concat","setAttribute","style","maxHeight","padding","querySelectorAll","forEach","button","preventDefault","currentTarget","faqs"],"sourceRoot":""} \ No newline at end of file diff --git a/assets/manifest.json b/assets/manifest.json new file mode 100644 index 0000000..4ba22b7 --- /dev/null +++ b/assets/manifest.json @@ -0,0 +1,66 @@ +{ + "main.css": "/style.2099d5a76e3050e6dea6.css", + "main.js": "/main.2099d5a76e3050e6dea6.js", + "/fonts/Coustard_400.svg": "//fonts/Coustard_400.svg", + "/fonts/Playfair_Display_600.svg": "//fonts/Playfair_Display_600.svg", + "/fonts/Coustard_400.ttf": "//fonts/Coustard_400.ttf", + "/fonts/Playfair_Display_600.ttf": "//fonts/Playfair_Display_600.ttf", + "/fonts/Open_Sans_400i.svg": "//fonts/Open_Sans_400i.svg", + "/fonts/Open_Sans_700i.svg": "//fonts/Open_Sans_700i.svg", + "/fonts/Open_Sans_400.svg": "//fonts/Open_Sans_400.svg", + "/fonts/Open_Sans_700.svg": "//fonts/Open_Sans_700.svg", + "/fonts/Playfair_Display_600.woff": "//fonts/Playfair_Display_600.woff", + "/fonts/Playfair_Display_600.eot": "//fonts/Playfair_Display_600.eot", + "/fonts/Coustard_400.woff": "//fonts/Coustard_400.woff", + "/fonts/Coustard_400.eot": "//fonts/Coustard_400.eot", + "/fonts/Open_Sans_700.ttf": "//fonts/Open_Sans_700.ttf", + "/fonts/Playfair_Display_600.woff2": "//fonts/Playfair_Display_600.woff2", + "/fonts/Open_Sans_400.ttf": "//fonts/Open_Sans_400.ttf", + "/fonts/Coustard_400.woff2": "//fonts/Coustard_400.woff2", + "/fonts/Open_Sans_700i.ttf": "//fonts/Open_Sans_700i.ttf", + "/fonts/Open_Sans_400i.ttf": "//fonts/Open_Sans_400i.ttf", + "/fonts/Open_Sans_700.woff": "//fonts/Open_Sans_700.woff", + "/fonts/Open_Sans_400.woff": "//fonts/Open_Sans_400.woff", + "/fonts/Open_Sans_700i.woff": "//fonts/Open_Sans_700i.woff", + "/fonts/Open_Sans_400i.woff": "//fonts/Open_Sans_400i.woff", + "/fonts/Open_Sans_700.eot": "//fonts/Open_Sans_700.eot", + "/fonts/Open_Sans_400.eot": "//fonts/Open_Sans_400.eot", + "/fonts/Open_Sans_700.woff2": "//fonts/Open_Sans_700.woff2", + "/fonts/Open_Sans_400i.eot": "//fonts/Open_Sans_400i.eot", + "/fonts/Open_Sans_700i.eot": "//fonts/Open_Sans_700i.eot", + "/fonts/Open_Sans_400.woff2": "//fonts/Open_Sans_400.woff2", + "/fonts/Open_Sans_700i.woff2": "//fonts/Open_Sans_700i.woff2", + "/fonts/Open_Sans_400i.woff2": "//fonts/Open_Sans_400i.woff2", + "Playfair_Display_600.woff2": "/a16393948d50476b3f93.woff2", + "Playfair_Display_600.woff": "/54da969939e02da4ef8a.woff", + "Playfair_Display_600.svg": "/128aef13e266ee58a02b.svg", + "Playfair_Display_600.eot": "/5ad1fe7e8556951d2118.eot", + "Playfair_Display_600.ttf": "/b92844af222958865654.ttf", + "Open_Sans_700i.woff2": "/538cd47e5b984f6cd2d5.woff2", + "Open_Sans_400i.woff2": "/c45d3e9755c98a4a74a1.woff2", + "Open_Sans_700i.woff": "/4cd2bdad5da83fe79b0f.woff", + "Open_Sans_400.woff2": "/73346027d4fde332f075.woff2", + "Open_Sans_400i.woff": "/a297162908dc185b3c12.woff", + "Open_Sans_700.woff2": "/c53f2ac2814955806ace.woff2", + "Open_Sans_400i.svg": "/0768f4fb0e6afbc52000.svg", + "Open_Sans_700i.eot": "/26c33577d1a552938896.eot", + "Coustard_400.woff2": "/376202fa33a55b4e7b57.woff2", + "Open_Sans_400.woff": "/5dc4202504b4f981d4bd.woff", + "Open_Sans_700i.ttf": "/906c03462ad64f540d89.ttf", + "Open_Sans_400i.ttf": "/c0de456eacace5e47c36.ttf", + "Open_Sans_700.woff": "/c3a6c890c794f3d5ada1.woff", + "Open_Sans_700i.svg": "/dd8640498a6a77b6cded.svg", + "Open_Sans_400i.eot": "/e93354fee5a146339fd7.eot", + "Open_Sans_400.eot": "/09512a65c0f7628f2205.eot", + "Coustard_400.woff": "/3a434a4352510ae626c5.woff", + "Open_Sans_400.svg": "/77883d091aa094551645.svg", + "Open_Sans_700.eot": "/bfa596a50f5557f7023c.eot", + "Open_Sans_400.ttf": "/c5f9d77b067d6de089f8.ttf", + "Open_Sans_700.svg": "/d7407b25c9bdf3ccfb08.svg", + "Open_Sans_700.ttf": "/d914f6425a5885c87306.ttf", + "Coustard_400.eot": "/346ec5009af8fc247876.eot", + "Coustard_400.ttf": "/3e1003e006aa95f46f10.ttf", + "Coustard_400.svg": "/d7ca58e9f57c24a103e8.svg", + "style.css.map": "/style.2099d5a76e3050e6dea6.css.map", + "main.js.map": "/main.2099d5a76e3050e6dea6.js.map" +} \ No newline at end of file diff --git a/assets/style.2099d5a76e3050e6dea6.css b/assets/style.2099d5a76e3050e6dea6.css new file mode 100644 index 0000000..2bb96a7 --- /dev/null +++ b/assets/style.2099d5a76e3050e6dea6.css @@ -0,0 +1,3 @@ +@font-face{font-family:"Coustard";font-style:normal;font-weight:400;src:url(/assets/346ec5009af8fc247876.eot);src:local("Coustard Regular"),local("Coustard-Regular"),url(/assets/346ec5009af8fc247876.eot?#iefix) format("embedded-opentype"),url(/assets/376202fa33a55b4e7b57.woff2) format("woff2"),url(/assets/3a434a4352510ae626c5.woff) format("woff"),url(/assets/3e1003e006aa95f46f10.ttf) format("truetype"),url(/assets/d7ca58e9f57c24a103e8.svg#Coustard) format("svg")}@font-face{font-family:"Open Sans";font-style:normal;font-weight:400;src:url(/assets/09512a65c0f7628f2205.eot);src:local("Open Sans Regular"),local("OpenSans-Regular"),url(/assets/09512a65c0f7628f2205.eot?#iefix) format("embedded-opentype"),url(/assets/73346027d4fde332f075.woff2) format("woff2"),url(/assets/5dc4202504b4f981d4bd.woff) format("woff"),url(/assets/c5f9d77b067d6de089f8.ttf) format("truetype"),url(/assets/77883d091aa094551645.svg#OpenSans) format("svg")}@font-face{font-family:"Open Sans";font-style:italic;font-weight:400;src:url(/assets/e93354fee5a146339fd7.eot);src:local("Open Sans Italic"),local("OpenSans-Italic"),url(/assets/e93354fee5a146339fd7.eot?#iefix) format("embedded-opentype"),url(/assets/c45d3e9755c98a4a74a1.woff2) format("woff2"),url(/assets/a297162908dc185b3c12.woff) format("woff"),url(/assets/c0de456eacace5e47c36.ttf) format("truetype"),url(/assets/0768f4fb0e6afbc52000.svg#OpenSans) format("svg")}@font-face{font-family:"Open Sans";font-style:normal;font-weight:700;src:url(/assets/bfa596a50f5557f7023c.eot);src:local("Open Sans Bold"),local("OpenSans-Bold"),url(/assets/bfa596a50f5557f7023c.eot?#iefix) format("embedded-opentype"),url(/assets/c53f2ac2814955806ace.woff2) format("woff2"),url(/assets/c3a6c890c794f3d5ada1.woff) format("woff"),url(/assets/d914f6425a5885c87306.ttf) format("truetype"),url(/assets/d7407b25c9bdf3ccfb08.svg#OpenSans) format("svg")}@font-face{font-family:"Open Sans";font-style:italic;font-weight:700;src:url(/assets/26c33577d1a552938896.eot);src:local("Open Sans Bold Italic"),local("OpenSans-BoldItalic"),url(/assets/26c33577d1a552938896.eot?#iefix) format("embedded-opentype"),url(/assets/538cd47e5b984f6cd2d5.woff2) format("woff2"),url(/assets/4cd2bdad5da83fe79b0f.woff) format("woff"),url(/assets/906c03462ad64f540d89.ttf) format("truetype"),url(/assets/dd8640498a6a77b6cded.svg#OpenSans) format("svg")}@font-face{font-family:"Playfair Display";font-style:normal;font-weight:600;src:url(/assets/5ad1fe7e8556951d2118.eot);src:local(""),url(/assets/5ad1fe7e8556951d2118.eot?#iefix) format("embedded-opentype"),url(/assets/a16393948d50476b3f93.woff2) format("woff2"),url(/assets/54da969939e02da4ef8a.woff) format("woff"),url(/assets/b92844af222958865654.ttf) format("truetype"),url(/assets/128aef13e266ee58a02b.svg#PlayfairDisplay) format("svg")}.file-cta,.file-name,.select select,.textarea,.input,.button,.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:3px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 3px);padding-left:calc(0.75em - 3px);padding-right:calc(0.75em - 3px);padding-top:calc(0.5em - 3px);position:relative;vertical-align:top}.file-cta:focus,.file-name:focus,.select select:focus,.textarea:focus,.input:focus,.button:focus,.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus,.pagination-ellipsis:focus,.is-focused.file-cta,.is-focused.file-name,.select select.is-focused,.is-focused.textarea,.is-focused.input,.is-focused.button,.is-focused.pagination-previous,.is-focused.pagination-next,.is-focused.pagination-link,.is-focused.pagination-ellipsis,.file-cta:active,.file-name:active,.select select:active,.textarea:active,.input:active,.button:active,.pagination-previous:active,.pagination-next:active,.pagination-link:active,.pagination-ellipsis:active,.is-active.file-cta,.is-active.file-name,.select select.is-active,.is-active.textarea,.is-active.input,.is-active.button,.is-active.pagination-previous,.is-active.pagination-next,.is-active.pagination-link,.is-active.pagination-ellipsis{outline:none}[disabled].file-cta,[disabled].file-name,.select select[disabled],[disabled].textarea,[disabled].input,[disabled].button,[disabled].pagination-previous,[disabled].pagination-next,[disabled].pagination-link,[disabled].pagination-ellipsis,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input,fieldset[disabled] .button,fieldset[disabled] .pagination-previous,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-ellipsis{cursor:not-allowed}.is-unselectable,.file,.button,.tabs,.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.breadcrumb{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select:not(.is-multiple):not(.is-loading)::after,.navbar-link:not(.is-arrowless)::after{border:3px solid transparent;border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:.625em}.block:not(:last-child),.title:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.progress:not(:last-child),.notification:not(:last-child),.content:not(:last-child),.box:not(:last-child),.tabs:not(:last-child),.pagination:not(:last-child),.message:not(:last-child),.level:not(:last-child),.breadcrumb:not(:last-child){margin-bottom:1.5rem}.delete,.modal-close{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(50,50,50,.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}.delete::before,.modal-close::before,.delete::after,.modal-close::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.delete::before,.modal-close::before{height:2px;width:50%}.delete::after,.modal-close::after{height:50%;width:2px}.delete:hover,.modal-close:hover,.delete:focus,.modal-close:focus{background-color:rgba(50,50,50,.3)}.delete:active,.modal-close:active{background-color:rgba(50,50,50,.4)}.is-small.delete,.is-small.modal-close{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.delete,.is-medium.modal-close{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.delete,.is-large.modal-close{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.control.is-loading::after,.select.is-loading::after,.loader,.button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdbdb;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.is-overlay,.image.is-square img,.image.is-square .has-ratio,.image.is-1by1 img,.image.is-1by1 .has-ratio,.image.is-5by4 img,.image.is-5by4 .has-ratio,.image.is-4by3 img,.image.is-4by3 .has-ratio,.image.is-3by2 img,.image.is-3by2 .has-ratio,.image.is-5by3 img,.image.is-5by3 .has-ratio,.image.is-16by9 img,.image.is-16by9 .has-ratio,.image.is-2by1 img,.image.is-2by1 .has-ratio,.image.is-3by1 img,.image.is-3by1 .has-ratio,.image.is-4by5 img,.image.is-4by5 .has-ratio,.image.is-3by4 img,.image.is-3by4 .has-ratio,.image.is-2by3 img,.image.is-2by3 .has-ratio,.image.is-3by5 img,.image.is-3by5 .has-ratio,.image.is-9by16 img,.image.is-9by16 .has-ratio,.image.is-1by2 img,.image.is-1by2 .has-ratio,.image.is-1by3 img,.image.is-1by3 .has-ratio,.modal-background,.modal{bottom:0;left:0;position:absolute;right:0;top:0}.navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,optgroup,select,textarea{font-family:"Open Sans",sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:monospace}body{color:#999;font-size:1em;font-weight:400;line-height:1.5}a{color:#da8f4c;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:#f5f5f5;color:#da1039;font-size:.875em;font-weight:normal;padding:.25em .5em .25em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type=checkbox],input[type=radio]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#363636;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#999;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#363636}@keyframes spinAround{from{transform:rotate(0deg)}to{transform:rotate(359deg)}}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#da8f4c;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#363636;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#fff;content:"/"}.breadcrumb ul,.breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"→"}.breadcrumb.has-bullet-separator li+li::before{content:"‱"}.breadcrumb.has-dot-separator li+li::before{content:"·"}.breadcrumb.has-succeeds-separator li+li::before{content:"≻"}.card{background-color:#fff;border-radius:.25rem;box-shadow:0 .5em 1em -0.125em rgba(50,50,50,.1),0 0px 0 1px rgba(50,50,50,.02);color:#999;max-width:100%;position:relative}.card-footer:first-child,.card-content:first-child,.card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-footer:last-child,.card-content:last-child,.card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-header{background-color:transparent;align-items:stretch;box-shadow:0 .125em .25em rgba(50,50,50,.1);display:flex}.card-header-title{align-items:center;color:#363636;display:flex;flex-grow:1;font-weight:700;padding:.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:.75rem 1rem}.card-image{display:block;position:relative}.card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-content{background-color:transparent;padding:1.5rem}.card-footer{background-color:transparent;border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:0 .5em 1em -0.125em rgba(50,50,50,.1),0 0px 0 1px rgba(50,50,50,.02);padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#999;display:block;font-size:.875rem;line-height:1.5;padding:.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#323232}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#da8f4c;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .title,.level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#999;display:block;padding:.5em .75em}.menu-list a:hover{background-color:#f5f5f5;color:#363636}.menu-list a.is-active{background-color:#da8f4c;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#7a7a7a;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#323232}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#323232;color:#fff}.message.is-black .message-body{border-color:#323232}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark{background-color:#fafafa}.message.is-dark .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body{border-color:#363636}.message.is-primary{background-color:#fcf4ee}.message.is-primary .message-header{background-color:#da8f4c;color:#fff}.message.is-primary .message-body{border-color:#da8f4c;color:#94561f}.message.is-link{background-color:#fcf4ee}.message.is-link .message-header{background-color:#da8f4c;color:#fff}.message.is-link .message-body{border-color:#da8f4c;color:#94561f}.message.is-info{background-color:#eff5fb}.message.is-info .message-header{background-color:#3e8ed0;color:#fff}.message.is-info .message-body{border-color:#3e8ed0;color:#296fa8}.message.is-success{background-color:#effbf1}.message.is-success .message-header{background-color:#b9eec5;color:rgba(0,0,0,.7)}.message.is-success .message-body{border-color:#b9eec5;color:#1d7731}.message.is-warning{background-color:#fffaeb}.message.is-warning .message-header{background-color:#ffe08a;color:rgba(0,0,0,.7)}.message.is-warning .message-body{border-color:#ffe08a;color:#946c00}.message.is-danger{background-color:#feecf0}.message.is-danger .message-header{background-color:#f14668;color:#fff}.message.is-danger .message-body{border-color:#f14668;color:#cc0f35}.message-header{align-items:center;background-color:#999;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#999;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:transparent}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(50,50,50,.86)}.modal-content,.modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){.modal-content,.modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-head,.modal-card-foot{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#363636;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#323232}.navbar.is-white .navbar-brand>.navbar-item,.navbar.is-white .navbar-brand .navbar-link{color:#323232}.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#323232}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#323232}.navbar.is-white .navbar-burger{color:#323232}@media screen and (min-width: 1024px){.navbar.is-white .navbar-start>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-end .navbar-link{color:#323232}.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#323232}.navbar.is-white .navbar-start .navbar-link::after,.navbar.is-white .navbar-end .navbar-link::after{border-color:#323232}.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#323232}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#323232}}.navbar.is-black{background-color:#323232;color:#fff}.navbar.is-black .navbar-brand>.navbar-item,.navbar.is-black .navbar-brand .navbar-link{color:#fff}.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#252525;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-black .navbar-start>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-end .navbar-link{color:#fff}.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end .navbar-link.is-active{background-color:#252525;color:#fff}.navbar.is-black .navbar-start .navbar-link::after,.navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#252525;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#323232;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand>.navbar-item,.navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width: 1024px){.navbar.is-light .navbar-start>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-start .navbar-link::after,.navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,.7)}}.navbar.is-dark{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand>.navbar-item,.navbar.is-dark .navbar-brand .navbar-link{color:#fff}.navbar.is-dark .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-dark .navbar-start>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.navbar.is-dark .navbar-end .navbar-link{color:#fff}.navbar.is-dark .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-start .navbar-link::after,.navbar.is-dark .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary{background-color:#da8f4c;color:#fff}.navbar.is-primary .navbar-brand>.navbar-item,.navbar.is-primary .navbar-brand .navbar-link{color:#fff}.navbar.is-primary .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand .navbar-link.is-active{background-color:#d68237;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-primary .navbar-start>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.navbar.is-primary .navbar-end .navbar-link{color:#fff}.navbar.is-primary .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end .navbar-link.is-active{background-color:#d68237;color:#fff}.navbar.is-primary .navbar-start .navbar-link::after,.navbar.is-primary .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link{background-color:#d68237;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active{background-color:#da8f4c;color:#fff}}.navbar.is-link{background-color:#da8f4c;color:#fff}.navbar.is-link .navbar-brand>.navbar-item,.navbar.is-link .navbar-brand .navbar-link{color:#fff}.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#d68237;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-link .navbar-start>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-end .navbar-link{color:#fff}.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end .navbar-link.is-active{background-color:#d68237;color:#fff}.navbar.is-link .navbar-start .navbar-link::after,.navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#d68237;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#da8f4c;color:#fff}}.navbar.is-info{background-color:#3e8ed0;color:#fff}.navbar.is-info .navbar-brand>.navbar-item,.navbar.is-info .navbar-brand .navbar-link{color:#fff}.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-info .navbar-start>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-end .navbar-link{color:#fff}.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end .navbar-link.is-active{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-start .navbar-link::after,.navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3e8ed0;color:#fff}}.navbar.is-success{background-color:#b9eec5;color:rgba(0,0,0,.7)}.navbar.is-success .navbar-brand>.navbar-item,.navbar.is-success .navbar-brand .navbar-link{color:rgba(0,0,0,.7)}.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#a4e9b4;color:rgba(0,0,0,.7)}.navbar.is-success .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-success .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width: 1024px){.navbar.is-success .navbar-start>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-end .navbar-link{color:rgba(0,0,0,.7)}.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end .navbar-link.is-active{background-color:#a4e9b4;color:rgba(0,0,0,.7)}.navbar.is-success .navbar-start .navbar-link::after,.navbar.is-success .navbar-end .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#a4e9b4;color:rgba(0,0,0,.7)}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#b9eec5;color:rgba(0,0,0,.7)}}.navbar.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand>.navbar-item,.navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width: 1024px){.navbar.is-warning .navbar-start>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-start .navbar-link::after,.navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ffe08a;color:rgba(0,0,0,.7)}}.navbar.is-danger{background-color:#f14668;color:#fff}.navbar.is-danger .navbar-brand>.navbar-item,.navbar.is-danger .navbar-brand .navbar-link{color:#fff}.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-danger .navbar-start>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-end .navbar-link{color:#fff}.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-start .navbar-link::after,.navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#f14668;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}html.has-navbar-fixed-top,body.has-navbar-fixed-top{padding-top:3.25rem}html.has-navbar-fixed-bottom,body.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#999;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color,opacity,transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:nth-child(1){top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,.05)}.navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#999;display:block;line-height:1.5;padding:.5rem .75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}a.navbar-item,.navbar-link{cursor:pointer}a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover,a.navbar-item.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,.navbar-link.is-active{background-color:#fafafa;color:#da8f4c}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(0.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:transparent;border-bottom-color:#da8f4c}.navbar-item.is-tab.is-active{background-color:transparent;border-bottom-color:#da8f4c;border-bottom-style:solid;border-bottom-width:3px;color:#da8f4c;padding-bottom:calc(0.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#da8f4c;margin-top:-0.375em;right:1.125em}.navbar-dropdown{font-size:.875rem;padding-bottom:.5rem;padding-top:.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:.5rem 0}@media screen and (max-width: 1023px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(50,50,50,.1);padding:.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(50,50,50,.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}html.has-navbar-fixed-top-touch,body.has-navbar-fixed-top-touch{padding-top:3.25rem}html.has-navbar-fixed-bottom-touch,body.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width: 1024px){.navbar,.navbar-menu,.navbar-start,.navbar-end{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-start,.navbar.is-spaced .navbar-end{align-items:center}.navbar.is-spaced a.navbar-item,.navbar.is-spaced .navbar-link{border-radius:4px}.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#323232}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#da8f4c}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(50,50,50,.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(50,50,50,.1);display:none;font-size:.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#323232}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#da8f4c}.navbar.is-spaced .navbar-dropdown,.navbar-dropdown.is-boxed{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(50,50,50,.1),0 0 0 1px rgba(50,50,50,.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity,transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.navbar>.container .navbar-brand,.container>.navbar .navbar-brand{margin-left:-0.75rem}.navbar>.container .navbar-menu,.container>.navbar .navbar-menu{margin-right:-0.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(50,50,50,.1)}.navbar.is-fixed-top-desktop{top:0}html.has-navbar-fixed-top-desktop,body.has-navbar-fixed-top-desktop{padding-top:3.25rem}html.has-navbar-fixed-bottom-desktop,body.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}html.has-spaced-navbar-fixed-top,body.has-spaced-navbar-fixed-top{padding-top:5.25rem}html.has-spaced-navbar-fixed-bottom,body.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}a.navbar-item.is-active,.navbar-link.is-active{color:#323232}a.navbar-item.is-active:not(:focus):not(:hover),.navbar-link.is-active:not(:focus):not(:hover){background-color:transparent}.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link,.navbar-item.has-dropdown.is-active .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-0.25rem}.pagination.is-small{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-previous,.pagination.is-rounded .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}.pagination.is-rounded .pagination-link{border-radius:9999px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-previous,.pagination-next,.pagination-link{border-color:#dbdbdb;color:#363636;min-width:2.5em}.pagination-previous:hover,.pagination-next:hover,.pagination-link:hover{border-color:#fff;color:#363636}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus{border-color:#485fc7}.pagination-previous:active,.pagination-next:active,.pagination-link:active{box-shadow:inset 0 1px 2px rgba(50,50,50,.2)}.pagination-previous[disabled],.pagination-previous.is-disabled,.pagination-next[disabled],.pagination-next.is-disabled,.pagination-link[disabled],.pagination-link.is-disabled{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#7a7a7a;opacity:.5}.pagination-previous,.pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#da8f4c;border-color:#da8f4c;color:#fff}.pagination-ellipsis{color:#fff;pointer-events:none}.pagination-list{flex-wrap:wrap}.pagination-list li{list-style:none}@media screen and (max-width: 768px){.pagination{flex-wrap:wrap}.pagination-previous,.pagination-next{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{margin-bottom:0;margin-top:0}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between;margin-bottom:0;margin-top:0}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:0 .5em 1em -0.125em rgba(50,50,50,.1),0 0px 0 1px rgba(50,50,50,.02);font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#323232}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#323232;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#323232}.panel.is-black .panel-block.is-active .panel-icon{color:#323232}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading{background-color:#da8f4c;color:#fff}.panel.is-primary .panel-tabs a.is-active{border-bottom-color:#da8f4c}.panel.is-primary .panel-block.is-active .panel-icon{color:#da8f4c}.panel.is-link .panel-heading{background-color:#da8f4c;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#da8f4c}.panel.is-link .panel-block.is-active .panel-icon{color:#da8f4c}.panel.is-info .panel-heading{background-color:#3e8ed0;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#3e8ed0}.panel.is-info .panel-block.is-active .panel-icon{color:#3e8ed0}.panel.is-success .panel-heading{background-color:#b9eec5;color:rgba(0,0,0,.7)}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#b9eec5}.panel.is-success .panel-block.is-active .panel-icon{color:#b9eec5}.panel.is-warning .panel-heading{background-color:#ffe08a;color:rgba(0,0,0,.7)}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ffe08a}.panel.is-warning .panel-block.is-active .panel-icon{color:#ffe08a}.panel.is-danger .panel-heading{background-color:#f14668;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#f14668}.panel.is-danger .panel-block.is-active .panel-icon{color:#f14668}.panel-tabs:not(:last-child),.panel-block:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#363636;font-size:1.25em;font-weight:700;line-height:1.25;padding:.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:.5em}.panel-tabs a.is-active{border-bottom-color:#999;color:#363636}.panel-list a{color:#999}.panel-list a:hover{color:#da8f4c}.panel-block{align-items:center;color:#363636;display:flex;justify-content:flex-start;padding:.5em .75em}.panel-block input[type=checkbox]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#da8f4c;color:#363636}.panel-block.is-active .panel-icon{color:#da8f4c}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#7a7a7a;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#999;display:flex;justify-content:center;margin-bottom:-1px;padding:.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#363636;color:#363636}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#da8f4c;color:#da8f4c}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:.75em;padding-right:.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:transparent !important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#fff;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#da8f4c;border-color:#da8f4c;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}.tabs.is-small{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.box{background-color:#fff;border-radius:6px;box-shadow:0 .5em 1em -0.125em rgba(50,50,50,.1),0 0px 0 1px rgba(50,50,50,.02);color:#999;display:block;padding:1.25rem}a.box:hover,a.box:focus{box-shadow:0 .5em 1em -0.125em rgba(50,50,50,.1),0 0 0 1px #da8f4c}a.box:active{box-shadow:inset 0 1px 2px rgba(50,50,50,.2),0 0 0 1px #da8f4c}.button{background-color:#fff;border-color:#dbdbdb;border-width:3px;color:#363636;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 3px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 3px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-small,.button .icon.is-medium,.button .icon.is-large{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 3px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 3px)}.button .icon:first-child:last-child{margin-left:calc(-0.5em - 3px);margin-right:calc(-0.5em - 3px)}.button:hover,.button.is-hovered{border-color:#da8f4c;color:#363636}.button:focus,.button.is-focused{border-color:#754418;color:#363636}.button:focus:not(:active),.button.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(218,143,76,.25)}.button:active,.button.is-active{border-color:#da8f4c;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#999;text-decoration:underline}.button.is-text:hover,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text.is-focused{background-color:#f5f5f5;color:#363636}.button.is-text:active,.button.is-text.is-active{background-color:#e8e8e8;color:#363636}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-ghost{background:none;border-color:transparent;color:#da8f4c;text-decoration:none}.button.is-ghost:hover,.button.is-ghost.is-hovered{color:#da8f4c;text-decoration:underline}.button.is-white{background-color:#fff;border-color:transparent;color:#323232}.button.is-white:hover,.button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#323232}.button.is-white:focus,.button.is-white.is-focused{border-color:transparent;color:#323232}.button.is-white:focus:not(:active),.button.is-white.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.button.is-white:active,.button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#323232}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}.button.is-white.is-inverted{background-color:#323232;color:#fff}.button.is-white.is-inverted:hover,.button.is-white.is-inverted.is-hovered{background-color:#252525}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#323232;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #323232 #323232 !important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined:hover,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#323232}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-outlined.is-loading:hover::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #323232 #323232 !important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#323232;color:#323232}.button.is-white.is-inverted.is-outlined:hover,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined.is-focused{background-color:#323232;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading:hover::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#323232;box-shadow:none;color:#323232}.button.is-black{background-color:#323232;border-color:transparent;color:#fff}.button.is-black:hover,.button.is-black.is-hovered{background-color:#2c2c2c;border-color:transparent;color:#fff}.button.is-black:focus,.button.is-black.is-focused{border-color:transparent;color:#fff}.button.is-black:focus:not(:active),.button.is-black.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(50,50,50,.25)}.button.is-black:active,.button.is-black.is-active{background-color:#252525;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#323232;border-color:#323232;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#323232}.button.is-black.is-inverted:hover,.button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#323232}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined{background-color:transparent;border-color:#323232;color:#323232}.button.is-black.is-outlined:hover,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined.is-focused{background-color:#323232;border-color:#323232;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #323232 #323232 !important}.button.is-black.is-outlined.is-loading:hover::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#323232;box-shadow:none;color:#323232}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined:hover,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#323232}.button.is-black.is-inverted.is-outlined.is-loading:hover::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #323232 #323232 !important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light:hover,.button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light:focus,.button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light:focus:not(:active),.button.is-light.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.button.is-light:active,.button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted:hover,.button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7) !important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined:hover,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-outlined.is-loading:hover::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7) !important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-light.is-inverted.is-outlined:hover,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading:hover::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-dark{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark:hover,.button.is-dark.is-hovered{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark:focus,.button.is-dark.is-focused{border-color:transparent;color:#fff}.button.is-dark:focus:not(:active),.button.is-dark.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.button.is-dark:active,.button.is-dark.is-active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],fieldset[disabled] .button.is-dark{background-color:#363636;border-color:#363636;box-shadow:none}.button.is-dark.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted:hover,.button.is-dark.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined:hover,.button.is-dark.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.button.is-dark.is-outlined.is-focused{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-outlined.is-loading:hover::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined:hover,.button.is-dark.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined.is-focused{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading:hover::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary{background-color:#da8f4c;border-color:transparent;color:#fff}.button.is-primary:hover,.button.is-primary.is-hovered{background-color:#d88841;border-color:transparent;color:#fff}.button.is-primary:focus,.button.is-primary.is-focused{border-color:transparent;color:#fff}.button.is-primary:focus:not(:active),.button.is-primary.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(218,143,76,.25)}.button.is-primary:active,.button.is-primary.is-active{background-color:#d68237;border-color:transparent;color:#fff}.button.is-primary[disabled],fieldset[disabled] .button.is-primary{background-color:#da8f4c;border-color:#da8f4c;box-shadow:none}.button.is-primary.is-inverted{background-color:#fff;color:#da8f4c}.button.is-primary.is-inverted:hover,.button.is-primary.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],fieldset[disabled] .button.is-primary.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#da8f4c}.button.is-primary.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined{background-color:transparent;border-color:#da8f4c;color:#da8f4c}.button.is-primary.is-outlined:hover,.button.is-primary.is-outlined.is-hovered,.button.is-primary.is-outlined:focus,.button.is-primary.is-outlined.is-focused{background-color:#da8f4c;border-color:#da8f4c;color:#fff}.button.is-primary.is-outlined.is-loading::after{border-color:transparent transparent #da8f4c #da8f4c !important}.button.is-primary.is-outlined.is-loading:hover::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-outlined.is-loading:focus::after,.button.is-primary.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-outlined{background-color:transparent;border-color:#da8f4c;box-shadow:none;color:#da8f4c}.button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined:hover,.button.is-primary.is-inverted.is-outlined.is-hovered,.button.is-primary.is-inverted.is-outlined:focus,.button.is-primary.is-inverted.is-outlined.is-focused{background-color:#fff;color:#da8f4c}.button.is-primary.is-inverted.is-outlined.is-loading:hover::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #da8f4c #da8f4c !important}.button.is-primary.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light{background-color:#fcf4ee;color:#94561f}.button.is-primary.is-light:hover,.button.is-primary.is-light.is-hovered{background-color:#f9eee4;border-color:transparent;color:#94561f}.button.is-primary.is-light:active,.button.is-primary.is-light.is-active{background-color:#f7e7d9;border-color:transparent;color:#94561f}.button.is-link{background-color:#da8f4c;border-color:transparent;color:#fff}.button.is-link:hover,.button.is-link.is-hovered{background-color:#d88841;border-color:transparent;color:#fff}.button.is-link:focus,.button.is-link.is-focused{border-color:transparent;color:#fff}.button.is-link:focus:not(:active),.button.is-link.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(218,143,76,.25)}.button.is-link:active,.button.is-link.is-active{background-color:#d68237;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#da8f4c;border-color:#da8f4c;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#da8f4c}.button.is-link.is-inverted:hover,.button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#da8f4c}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined{background-color:transparent;border-color:#da8f4c;color:#da8f4c}.button.is-link.is-outlined:hover,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined.is-focused{background-color:#da8f4c;border-color:#da8f4c;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #da8f4c #da8f4c !important}.button.is-link.is-outlined.is-loading:hover::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#da8f4c;box-shadow:none;color:#da8f4c}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined:hover,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#da8f4c}.button.is-link.is-inverted.is-outlined.is-loading:hover::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #da8f4c #da8f4c !important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#fcf4ee;color:#94561f}.button.is-link.is-light:hover,.button.is-link.is-light.is-hovered{background-color:#f9eee4;border-color:transparent;color:#94561f}.button.is-link.is-light:active,.button.is-link.is-light.is-active{background-color:#f7e7d9;border-color:transparent;color:#94561f}.button.is-info{background-color:#3e8ed0;border-color:transparent;color:#fff}.button.is-info:hover,.button.is-info.is-hovered{background-color:#3488ce;border-color:transparent;color:#fff}.button.is-info:focus,.button.is-info.is-focused{border-color:transparent;color:#fff}.button.is-info:focus:not(:active),.button.is-info.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.button.is-info:active,.button.is-info.is-active{background-color:#3082c5;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#3e8ed0;border-color:#3e8ed0;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#3e8ed0}.button.is-info.is-inverted:hover,.button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3e8ed0}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined{background-color:transparent;border-color:#3e8ed0;color:#3e8ed0}.button.is-info.is-outlined:hover,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined.is-focused{background-color:#3e8ed0;border-color:#3e8ed0;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3e8ed0 #3e8ed0 !important}.button.is-info.is-outlined.is-loading:hover::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#3e8ed0;box-shadow:none;color:#3e8ed0}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined:hover,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#3e8ed0}.button.is-info.is-inverted.is-outlined.is-loading:hover::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #3e8ed0 #3e8ed0 !important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#eff5fb;color:#296fa8}.button.is-info.is-light:hover,.button.is-info.is-light.is-hovered{background-color:#e4eff9;border-color:transparent;color:#296fa8}.button.is-info.is-light:active,.button.is-info.is-light.is-active{background-color:#dae9f6;border-color:transparent;color:#296fa8}.button.is-success{background-color:#b9eec5;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-success:hover,.button.is-success.is-hovered{background-color:#afecbd;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-success:focus,.button.is-success.is-focused{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-success:focus:not(:active),.button.is-success.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(185,238,197,.25)}.button.is-success:active,.button.is-success.is-active{background-color:#a4e9b4;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#b9eec5;border-color:#b9eec5;box-shadow:none}.button.is-success.is-inverted{background-color:rgba(0,0,0,.7);color:#b9eec5}.button.is-success.is-inverted:hover,.button.is-success.is-inverted.is-hovered{background-color:rgba(0,0,0,.7)}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#b9eec5}.button.is-success.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7) !important}.button.is-success.is-outlined{background-color:transparent;border-color:#b9eec5;color:#b9eec5}.button.is-success.is-outlined:hover,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined.is-focused{background-color:#b9eec5;border-color:#b9eec5;color:rgba(0,0,0,.7)}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #b9eec5 #b9eec5 !important}.button.is-success.is-outlined.is-loading:hover::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7) !important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#b9eec5;box-shadow:none;color:#b9eec5}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-success.is-inverted.is-outlined:hover,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,.7);color:#b9eec5}.button.is-success.is-inverted.is-outlined.is-loading:hover::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #b9eec5 #b9eec5 !important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-success.is-light{background-color:#effbf1;color:#1d7731}.button.is-success.is-light:hover,.button.is-success.is-light.is-hovered{background-color:#e4f9e9;border-color:transparent;color:#1d7731}.button.is-success.is-light:active,.button.is-success.is-light.is-active{background-color:#daf6e0;border-color:transparent;color:#1d7731}.button.is-warning{background-color:#ffe08a;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning:hover,.button.is-warning.is-hovered{background-color:#ffdc7d;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning:focus,.button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning:focus:not(:active),.button.is-warning.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.button.is-warning:active,.button.is-warning.is-active{background-color:#ffd970;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#ffe08a;border-color:#ffe08a;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);color:#ffe08a}.button.is-warning.is-inverted:hover,.button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,.7)}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#ffe08a}.button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7) !important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffe08a;color:#ffe08a}.button.is-warning.is-outlined:hover,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined.is-focused{background-color:#ffe08a;border-color:#ffe08a;color:rgba(0,0,0,.7)}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ffe08a #ffe08a !important}.button.is-warning.is-outlined.is-loading:hover::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7) !important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#ffe08a;box-shadow:none;color:#ffe08a}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-warning.is-inverted.is-outlined:hover,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,.7);color:#ffe08a}.button.is-warning.is-inverted.is-outlined.is-loading:hover::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ffe08a #ffe08a !important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-warning.is-light{background-color:#fffaeb;color:#946c00}.button.is-warning.is-light:hover,.button.is-warning.is-light.is-hovered{background-color:#fff6de;border-color:transparent;color:#946c00}.button.is-warning.is-light:active,.button.is-warning.is-light.is-active{background-color:#fff3d1;border-color:transparent;color:#946c00}.button.is-danger{background-color:#f14668;border-color:transparent;color:#fff}.button.is-danger:hover,.button.is-danger.is-hovered{background-color:#f03a5f;border-color:transparent;color:#fff}.button.is-danger:focus,.button.is-danger.is-focused{border-color:transparent;color:#fff}.button.is-danger:focus:not(:active),.button.is-danger.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.button.is-danger:active,.button.is-danger.is-active{background-color:#ef2e55;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#f14668;border-color:#f14668;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#f14668}.button.is-danger.is-inverted:hover,.button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#f14668}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;color:#f14668}.button.is-danger.is-outlined:hover,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined.is-focused{background-color:#f14668;border-color:#f14668;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #f14668 #f14668 !important}.button.is-danger.is-outlined.is-loading:hover::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;box-shadow:none;color:#f14668}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined:hover,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-outlined.is-loading:hover::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f14668 #f14668 !important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.button.is-danger.is-light:hover,.button.is-danger.is-light.is-hovered{background-color:#fde0e6;border-color:transparent;color:#cc0f35}.button.is-danger.is-light:active,.button.is-danger.is-light.is-active{background-color:#fcd4dc;border-color:transparent;color:#cc0f35}.button.is-small{font-size:.75rem}.button.is-small:not(.is-rounded){border-radius:2px}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent !important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#7a7a7a;box-shadow:none;pointer-events:none}.button.is-rounded{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-0.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:2px}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button:hover,.buttons.has-addons .button.is-hovered{z-index:2}.buttons.has-addons .button:focus,.buttons.has-addons .button.is-focused,.buttons.has-addons .button:active,.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-selected{z-index:3}.buttons.has-addons .button:focus:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-selected:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}@media screen and (max-width: 768px){.button.is-responsive.is-small{font-size:.5625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.65625rem}.button.is-responsive.is-medium{font-size:.75rem}.button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.button.is-responsive.is-small{font-size:.65625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.75rem}.button.is-responsive.is-medium{font-size:1rem}.button.is-responsive.is-large{font-size:1.25rem}}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1024px){.container{max-width:960px}}@media screen and (max-width: 1215px){.container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1216px){.container:not(.is-max-desktop){max-width:1152px}}.content li+li{margin-top:.25em}.content p:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content ul:not(:last-child),.content blockquote:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#363636;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:.8em}.content h5{font-size:1.125em;margin-bottom:.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol:not([type]).is-lower-alpha{list-style-type:lower-alpha}.content ol:not([type]).is-lower-roman{list-style-type:lower-roman}.content ol:not([type]).is-upper-alpha{list-style-type:upper-alpha}.content ol:not([type]).is-upper-roman{list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal}.content sup,.content sub{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.content table th{color:#363636}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#363636}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#363636}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small{font-size:.75rem}.content.is-normal{font-size:1rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}.icon-text .icon{flex-grow:0;flex-shrink:0}.icon-text .icon:not(:last-child){margin-right:.25em}.icon-text .icon:not(:first-child){margin-left:.25em}div.icon-text{display:flex}.image{display:block;position:relative}.image img{display:block;height:auto;width:100%}.image img.is-rounded{border-radius:9999px}.image.is-fullwidth{width:100%}.image.is-square img,.image.is-square .has-ratio,.image.is-1by1 img,.image.is-1by1 .has-ratio,.image.is-5by4 img,.image.is-5by4 .has-ratio,.image.is-4by3 img,.image.is-4by3 .has-ratio,.image.is-3by2 img,.image.is-3by2 .has-ratio,.image.is-5by3 img,.image.is-5by3 .has-ratio,.image.is-16by9 img,.image.is-16by9 .has-ratio,.image.is-2by1 img,.image.is-2by1 .has-ratio,.image.is-3by1 img,.image.is-3by1 .has-ratio,.image.is-4by5 img,.image.is-4by5 .has-ratio,.image.is-3by4 img,.image.is-3by4 .has-ratio,.image.is-2by3 img,.image.is-2by3 .has-ratio,.image.is-3by5 img,.image.is-3by5 .has-ratio,.image.is-9by16 img,.image.is-9by16 .has-ratio,.image.is-1by2 img,.image.is-1by2 .has-ratio,.image.is-1by3 img,.image.is-1by3 .has-ratio{height:100%;width:100%}.image.is-square,.image.is-1by1{padding-top:100%}.image.is-5by4{padding-top:80%}.image.is-4by3{padding-top:75%}.image.is-3by2{padding-top:66.6666%}.image.is-5by3{padding-top:60%}.image.is-16by9{padding-top:56.25%}.image.is-2by1{padding-top:50%}.image.is-3by1{padding-top:33.3333%}.image.is-4by5{padding-top:125%}.image.is-3by4{padding-top:133.3333%}.image.is-2by3{padding-top:150%}.image.is-3by5{padding-top:166.6666%}.image.is-9by16{padding-top:177.7777%}.image.is-1by2{padding-top:200%}.image.is-1by3{padding-top:300%}.image.is-16x16{height:16px;width:16px}.image.is-24x24{height:24px;width:24px}.image.is-32x32{height:32px;width:32px}.image.is-48x48{height:48px;width:48px}.image.is-64x64{height:64px;width:64px}.image.is-96x96{height:96px;width:96px}.image.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:transparent}.notification>.delete{right:.5rem;position:absolute;top:.5rem}.notification .title,.notification .subtitle,.notification .content{color:currentColor}.notification.is-white{background-color:#fff;color:#323232}.notification.is-black{background-color:#323232;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.notification.is-dark{background-color:#363636;color:#fff}.notification.is-primary{background-color:#da8f4c;color:#fff}.notification.is-primary.is-light{background-color:#fcf4ee;color:#94561f}.notification.is-link{background-color:#da8f4c;color:#fff}.notification.is-link.is-light{background-color:#fcf4ee;color:#94561f}.notification.is-info{background-color:#3e8ed0;color:#fff}.notification.is-info.is-light{background-color:#eff5fb;color:#296fa8}.notification.is-success{background-color:#b9eec5;color:rgba(0,0,0,.7)}.notification.is-success.is-light{background-color:#effbf1;color:#1d7731}.notification.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.notification.is-warning.is-light{background-color:#fffaeb;color:#946c00}.notification.is-danger{background-color:#f14668;color:#fff}.notification.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#999}.progress::-moz-progress-bar{background-color:#999}.progress::-ms-fill{background-color:#999;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right, #ffffff 30%, #ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#323232}.progress.is-black::-moz-progress-bar{background-color:#323232}.progress.is-black::-ms-fill{background-color:#323232}.progress.is-black:indeterminate{background-image:linear-gradient(to right, #323232 30%, #ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right, whitesmoke 30%, #ededed 30%)}.progress.is-dark::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate{background-image:linear-gradient(to right, #363636 30%, #ededed 30%)}.progress.is-primary::-webkit-progress-value{background-color:#da8f4c}.progress.is-primary::-moz-progress-bar{background-color:#da8f4c}.progress.is-primary::-ms-fill{background-color:#da8f4c}.progress.is-primary:indeterminate{background-image:linear-gradient(to right, #da8f4c 30%, #ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#da8f4c}.progress.is-link::-moz-progress-bar{background-color:#da8f4c}.progress.is-link::-ms-fill{background-color:#da8f4c}.progress.is-link:indeterminate{background-image:linear-gradient(to right, #da8f4c 30%, #ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#3e8ed0}.progress.is-info::-moz-progress-bar{background-color:#3e8ed0}.progress.is-info::-ms-fill{background-color:#3e8ed0}.progress.is-info:indeterminate{background-image:linear-gradient(to right, #3e8ed0 30%, #ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#b9eec5}.progress.is-success::-moz-progress-bar{background-color:#b9eec5}.progress.is-success::-ms-fill{background-color:#b9eec5}.progress.is-success:indeterminate{background-image:linear-gradient(to right, #b9eec5 30%, #ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#ffe08a}.progress.is-warning::-moz-progress-bar{background-color:#ffe08a}.progress.is-warning::-ms-fill{background-color:#ffe08a}.progress.is-warning:indeterminate{background-image:linear-gradient(to right, #ffe08a 30%, #ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#f14668}.progress.is-danger::-moz-progress-bar{background-color:#f14668}.progress.is-danger::-ms-fill{background-color:#f14668}.progress.is-danger:indeterminate{background-image:linear-gradient(to right, #f14668 30%, #ededed 30%)}.progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right, #999999 30%, #ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress:indeterminate::-ms-fill{animation-name:none}.progress.is-small{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#363636}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#323232}.table td.is-black,.table th.is-black{background-color:#323232;border-color:#323232;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#da8f4c;border-color:#da8f4c;color:#fff}.table td.is-link,.table th.is-link{background-color:#da8f4c;border-color:#da8f4c;color:#fff}.table td.is-info,.table th.is-info{background-color:#3e8ed0;border-color:#3e8ed0;color:#fff}.table td.is-success,.table th.is-success{background-color:#b9eec5;border-color:#b9eec5;color:rgba(0,0,0,.7)}.table td.is-warning,.table th.is-warning{background-color:#ffe08a;border-color:#ffe08a;color:rgba(0,0,0,.7)}.table td.is-danger,.table th.is-danger{background-color:#f14668;border-color:#f14668;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#da8f4c;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#363636}.table th:not([align]){text-align:left}.table tr.is-selected{background-color:#da8f4c;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:transparent}.table thead td,.table thead th{border-width:0 0 2px;color:#363636}.table tfoot{background-color:transparent}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#363636}.table tbody{background-color:transparent}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:.25em .5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag{margin-bottom:.5rem}.tags .tag:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-0.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag{margin-right:.25rem;margin-left:.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child){margin-left:.5rem}.tags.is-right .tag:not(:last-child){margin-right:0}.tags.has-addons .tag{margin-right:0}.tags.has-addons .tag:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#999;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:.75em;padding-right:.75em;white-space:nowrap}.tag:not(body) .delete{margin-left:.25rem;margin-right:-0.375rem}.tag:not(body).is-white{background-color:#fff;color:#323232}.tag:not(body).is-black{background-color:#323232;color:#fff}.tag:not(body).is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.tag:not(body).is-dark{background-color:#363636;color:#fff}.tag:not(body).is-primary{background-color:#da8f4c;color:#fff}.tag:not(body).is-primary.is-light{background-color:#fcf4ee;color:#94561f}.tag:not(body).is-link{background-color:#da8f4c;color:#fff}.tag:not(body).is-link.is-light{background-color:#fcf4ee;color:#94561f}.tag:not(body).is-info{background-color:#3e8ed0;color:#fff}.tag:not(body).is-info.is-light{background-color:#eff5fb;color:#296fa8}.tag:not(body).is-success{background-color:#b9eec5;color:rgba(0,0,0,.7)}.tag:not(body).is-success.is-light{background-color:#effbf1;color:#1d7731}.tag:not(body).is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.tag:not(body).is-warning.is-light{background-color:#fffaeb;color:#946c00}.tag:not(body).is-danger{background-color:#f14668;color:#fff}.tag:not(body).is-danger.is-light{background-color:#feecf0;color:#cc0f35}.tag:not(body).is-normal{font-size:.75rem}.tag:not(body).is-medium{font-size:1rem}.tag:not(body).is-large{font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child){margin-left:-0.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-0.375em}.tag:not(body) .icon:first-child:last-child{margin-left:-0.375em;margin-right:-0.375em}.tag:not(body).is-delete{margin-left:1px;padding:0;position:relative;width:2em}.tag:not(body).is-delete::before,.tag:not(body).is-delete::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag:not(body).is-delete::before{height:1px;width:50%}.tag:not(body).is-delete::after{height:50%;width:1px}.tag:not(body).is-delete:hover,.tag:not(body).is-delete:focus{background-color:#e8e8e8}.tag:not(body).is-delete:active{background-color:#dbdbdb}.tag:not(body).is-rounded{border-radius:9999px}a.tag:hover{text-decoration:underline}.title,.subtitle{word-break:break-word}.title em,.title span,.subtitle em,.subtitle span{font-weight:inherit}.title sub,.subtitle sub{font-size:.75em}.title sup,.subtitle sup{font-size:.75em}.title .tag,.subtitle .tag{vertical-align:middle}.title{color:#363636;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#999;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#363636;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.number{align-items:center;background-color:#f5f5f5;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:.25rem .5rem;text-align:center;vertical-align:top}.select select,.textarea,.input{background-color:#fff;border-color:#999;border-radius:4px;color:#363636}.select select::-moz-placeholder,.textarea::-moz-placeholder,.input::-moz-placeholder{color:rgba(54,54,54,.3)}.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder,.input::-webkit-input-placeholder{color:rgba(54,54,54,.3)}.select select:-moz-placeholder,.textarea:-moz-placeholder,.input:-moz-placeholder{color:rgba(54,54,54,.3)}.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder,.input:-ms-input-placeholder{color:rgba(54,54,54,.3)}.select select:hover,.textarea:hover,.input:hover,.select select.is-hovered,.is-hovered.textarea,.is-hovered.input{border-color:#fff}.select select:focus,.textarea:focus,.input:focus,.select select.is-focused,.is-focused.textarea,.is-focused.input,.select select:active,.textarea:active,.input:active,.select select.is-active,.is-active.textarea,.is-active.input{border-color:#da8f4c;box-shadow:0 0 0 .125em rgba(218,143,76,.25)}.select select[disabled],[disabled].textarea,[disabled].input,fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#7a7a7a}.select select[disabled]::-moz-placeholder,[disabled].textarea::-moz-placeholder,[disabled].input::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder{color:rgba(122,122,122,.3)}.select select[disabled]::-webkit-input-placeholder,[disabled].textarea::-webkit-input-placeholder,[disabled].input::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder{color:rgba(122,122,122,.3)}.select select[disabled]:-moz-placeholder,[disabled].textarea:-moz-placeholder,[disabled].input:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder{color:rgba(122,122,122,.3)}.select select[disabled]:-ms-input-placeholder,[disabled].textarea:-ms-input-placeholder,[disabled].input:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder{color:rgba(122,122,122,.3)}.textarea,.input{box-shadow:none;max-width:100%;width:100%}[readonly].textarea,[readonly].input{box-shadow:none}.is-white.textarea,.is-white.input{border-color:#fff}.is-white.textarea:focus,.is-white.input:focus,.is-white.is-focused.textarea,.is-white.is-focused.input,.is-white.textarea:active,.is-white.input:active,.is-white.is-active.textarea,.is-white.is-active.input{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.is-black.textarea,.is-black.input{border-color:#323232}.is-black.textarea:focus,.is-black.input:focus,.is-black.is-focused.textarea,.is-black.is-focused.input,.is-black.textarea:active,.is-black.input:active,.is-black.is-active.textarea,.is-black.is-active.input{box-shadow:0 0 0 .125em rgba(50,50,50,.25)}.is-light.textarea,.is-light.input{border-color:#f5f5f5}.is-light.textarea:focus,.is-light.input:focus,.is-light.is-focused.textarea,.is-light.is-focused.input,.is-light.textarea:active,.is-light.input:active,.is-light.is-active.textarea,.is-light.is-active.input{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.is-dark.textarea,.is-dark.input{border-color:#363636}.is-dark.textarea:focus,.is-dark.input:focus,.is-dark.is-focused.textarea,.is-dark.is-focused.input,.is-dark.textarea:active,.is-dark.input:active,.is-dark.is-active.textarea,.is-dark.is-active.input{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.is-primary.textarea,.is-primary.input{border-color:#da8f4c}.is-primary.textarea:focus,.is-primary.input:focus,.is-primary.is-focused.textarea,.is-primary.is-focused.input,.is-primary.textarea:active,.is-primary.input:active,.is-primary.is-active.textarea,.is-primary.is-active.input{box-shadow:0 0 0 .125em rgba(218,143,76,.25)}.is-link.textarea,.is-link.input{border-color:#da8f4c}.is-link.textarea:focus,.is-link.input:focus,.is-link.is-focused.textarea,.is-link.is-focused.input,.is-link.textarea:active,.is-link.input:active,.is-link.is-active.textarea,.is-link.is-active.input{box-shadow:0 0 0 .125em rgba(218,143,76,.25)}.is-info.textarea,.is-info.input{border-color:#3e8ed0}.is-info.textarea:focus,.is-info.input:focus,.is-info.is-focused.textarea,.is-info.is-focused.input,.is-info.textarea:active,.is-info.input:active,.is-info.is-active.textarea,.is-info.is-active.input{box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.is-success.textarea,.is-success.input{border-color:#b9eec5}.is-success.textarea:focus,.is-success.input:focus,.is-success.is-focused.textarea,.is-success.is-focused.input,.is-success.textarea:active,.is-success.input:active,.is-success.is-active.textarea,.is-success.is-active.input{box-shadow:0 0 0 .125em rgba(185,238,197,.25)}.is-warning.textarea,.is-warning.input{border-color:#ffe08a}.is-warning.textarea:focus,.is-warning.input:focus,.is-warning.is-focused.textarea,.is-warning.is-focused.input,.is-warning.textarea:active,.is-warning.input:active,.is-warning.is-active.textarea,.is-warning.is-active.input{box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.is-danger.textarea,.is-danger.input{border-color:#f14668}.is-danger.textarea:focus,.is-danger.input:focus,.is-danger.is-focused.textarea,.is-danger.is-focused.input,.is-danger.textarea:active,.is-danger.input:active,.is-danger.is-active.textarea,.is-danger.is-active.input{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.is-small.textarea,.is-small.input{border-radius:2px;font-size:.75rem}.is-medium.textarea,.is-medium.input{font-size:1.25rem}.is-large.textarea,.is-large.input{font-size:1.5rem}.is-fullwidth.textarea,.is-fullwidth.input{display:block;width:100%}.is-inline.textarea,.is-inline.input{display:inline;width:auto}.input.is-rounded{border-radius:9999px;padding-left:calc(calc(0.75em - 3px) + 0.375em);padding-right:calc(calc(0.75em - 3px) + 0.375em)}.input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 3px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.radio,.checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.radio input,.checkbox input{cursor:pointer}.radio:hover,.checkbox:hover{color:#363636}[disabled].radio,[disabled].checkbox,fieldset[disabled] .radio,fieldset[disabled] .checkbox,.radio input[disabled],.checkbox input[disabled]{color:#7a7a7a;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#da8f4c;right:1.125em;z-index:4}.select.is-rounded select{border-radius:9999px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#363636}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select:hover,.select.is-white select.is-hovered{border-color:#f2f2f2}.select.is-white select:focus,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select.is-active{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.select.is-black:not(:hover)::after{border-color:#323232}.select.is-black select{border-color:#323232}.select.is-black select:hover,.select.is-black select.is-hovered{border-color:#252525}.select.is-black select:focus,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select.is-active{box-shadow:0 0 0 .125em rgba(50,50,50,.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select:hover,.select.is-light select.is-hovered{border-color:#e8e8e8}.select.is-light select:focus,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select.is-active{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.select.is-dark:not(:hover)::after{border-color:#363636}.select.is-dark select{border-color:#363636}.select.is-dark select:hover,.select.is-dark select.is-hovered{border-color:#292929}.select.is-dark select:focus,.select.is-dark select.is-focused,.select.is-dark select:active,.select.is-dark select.is-active{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.select.is-primary:not(:hover)::after{border-color:#da8f4c}.select.is-primary select{border-color:#da8f4c}.select.is-primary select:hover,.select.is-primary select.is-hovered{border-color:#d68237}.select.is-primary select:focus,.select.is-primary select.is-focused,.select.is-primary select:active,.select.is-primary select.is-active{box-shadow:0 0 0 .125em rgba(218,143,76,.25)}.select.is-link:not(:hover)::after{border-color:#da8f4c}.select.is-link select{border-color:#da8f4c}.select.is-link select:hover,.select.is-link select.is-hovered{border-color:#d68237}.select.is-link select:focus,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select.is-active{box-shadow:0 0 0 .125em rgba(218,143,76,.25)}.select.is-info:not(:hover)::after{border-color:#3e8ed0}.select.is-info select{border-color:#3e8ed0}.select.is-info select:hover,.select.is-info select.is-hovered{border-color:#3082c5}.select.is-info select:focus,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select.is-active{box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.select.is-success:not(:hover)::after{border-color:#b9eec5}.select.is-success select{border-color:#b9eec5}.select.is-success select:hover,.select.is-success select.is-hovered{border-color:#a4e9b4}.select.is-success select:focus,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select.is-active{box-shadow:0 0 0 .125em rgba(185,238,197,.25)}.select.is-warning:not(:hover)::after{border-color:#ffe08a}.select.is-warning select{border-color:#ffe08a}.select.is-warning select:hover,.select.is-warning select.is-hovered{border-color:#ffd970}.select.is-warning select:focus,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select.is-active{box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.select.is-danger:not(:hover)::after{border-color:#f14668}.select.is-danger select{border-color:#f14668}.select.is-danger select:hover,.select.is-danger select.is-hovered{border-color:#ef2e55}.select.is-danger select:focus,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select.is-active{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.select.is-small{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#7a7a7a !important;opacity:.5}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:.625em;transform:none}.select.is-loading.is-small:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#323232}.file.is-white:hover .file-cta,.file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#323232}.file.is-white:focus .file-cta,.file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,255,255,.25);color:#323232}.file.is-white:active .file-cta,.file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#323232}.file.is-black .file-cta{background-color:#323232;border-color:transparent;color:#fff}.file.is-black:hover .file-cta,.file.is-black.is-hovered .file-cta{background-color:#2c2c2c;border-color:transparent;color:#fff}.file.is-black:focus .file-cta,.file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(50,50,50,.25);color:#fff}.file.is-black:active .file-cta,.file.is-black.is-active .file-cta{background-color:#252525;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light:hover .file-cta,.file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light:focus .file-cta,.file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(245,245,245,.25);color:rgba(0,0,0,.7)}.file.is-light:active .file-cta,.file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-dark .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark:hover .file-cta,.file.is-dark.is-hovered .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark:focus .file-cta,.file.is-dark.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(54,54,54,.25);color:#fff}.file.is-dark:active .file-cta,.file.is-dark.is-active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta{background-color:#da8f4c;border-color:transparent;color:#fff}.file.is-primary:hover .file-cta,.file.is-primary.is-hovered .file-cta{background-color:#d88841;border-color:transparent;color:#fff}.file.is-primary:focus .file-cta,.file.is-primary.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(218,143,76,.25);color:#fff}.file.is-primary:active .file-cta,.file.is-primary.is-active .file-cta{background-color:#d68237;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#da8f4c;border-color:transparent;color:#fff}.file.is-link:hover .file-cta,.file.is-link.is-hovered .file-cta{background-color:#d88841;border-color:transparent;color:#fff}.file.is-link:focus .file-cta,.file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(218,143,76,.25);color:#fff}.file.is-link:active .file-cta,.file.is-link.is-active .file-cta{background-color:#d68237;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#3e8ed0;border-color:transparent;color:#fff}.file.is-info:hover .file-cta,.file.is-info.is-hovered .file-cta{background-color:#3488ce;border-color:transparent;color:#fff}.file.is-info:focus .file-cta,.file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(62,142,208,.25);color:#fff}.file.is-info:active .file-cta,.file.is-info.is-active .file-cta{background-color:#3082c5;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#b9eec5;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-success:hover .file-cta,.file.is-success.is-hovered .file-cta{background-color:#afecbd;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-success:focus .file-cta,.file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(185,238,197,.25);color:rgba(0,0,0,.7)}.file.is-success:active .file-cta,.file.is-success.is-active .file-cta{background-color:#a4e9b4;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning .file-cta{background-color:#ffe08a;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning:hover .file-cta,.file.is-warning.is-hovered .file-cta{background-color:#ffdc7d;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning:focus .file-cta,.file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,224,138,.25);color:rgba(0,0,0,.7)}.file.is-warning:active .file-cta,.file.is-warning.is-active .file-cta{background-color:#ffd970;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-danger .file-cta{background-color:#f14668;border-color:transparent;color:#fff}.file.is-danger:hover .file-cta,.file.is-danger.is-hovered .file-cta{background-color:#f03a5f;border-color:transparent;color:#fff}.file.is-danger:focus .file-cta,.file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(241,70,104,.25);color:#fff}.file.is-danger:active .file-cta,.file.is-danger.is-active .file-cta{background-color:#ef2e55;border-color:transparent;color:#fff}.file.is-small{font-size:.75rem}.file.is-normal{font-size:1rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#363636}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#363636}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#999}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#363636;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:.5em}.label.is-small{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:.25rem}.help.is-white{color:#fff}.help.is-black{color:#323232}.help.is-light{color:#f5f5f5}.help.is-dark{color:#363636}.help.is-primary{color:#da8f4c}.help.is-link{color:#da8f4c}.help.is-info{color:#3e8ed0}.help.is-success{color:#b9eec5}.help.is-warning{color:#ffe08a}.help.is-danger{color:#f14668}.field:not(:last-child){margin-bottom:.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .button:not([disabled]).is-hovered,.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control .input:not([disabled]).is-hovered,.field.has-addons .control .select select:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]).is-hovered{z-index:2}.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .button:not([disabled]).is-focused,.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button:not([disabled]).is-active,.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control .input:not([disabled]).is-focused,.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control .input:not([disabled]).is-active,.field.has-addons .control .select select:not([disabled]):focus,.field.has-addons .control .select select:not([disabled]).is-focused,.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select:not([disabled]).is-active{z-index:3}.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .button:not([disabled]).is-focused:hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button:not([disabled]).is-active:hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control .input:not([disabled]).is-focused:hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control .input:not([disabled]).is-active:hover,.field.has-addons .control .select select:not([disabled]):focus:hover,.field.has-addons .control .select select:not([disabled]).is-focused:hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select:not([disabled]).is-active:hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width: 768px){.field-label{margin-bottom:.5rem}}@media screen and (min-width: 769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small{font-size:.75rem;padding-top:.375em}.field-label.is-normal{padding-top:.375em}.field-label.is-medium{font-size:1.25rem;padding-top:.375em}.field-label.is-large{font-size:1.5rem;padding-top:.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#999}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute !important;right:.625em;top:.625em;z-index:4}.control.is-loading.is-small:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none;width:unset}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0%}.columns.is-mobile>.column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>.column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>.column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>.column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>.column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){.column.is-narrow-mobile{flex:none;width:unset}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0%}.column.is-1-mobile{flex:none;width:8.33333337%}.column.is-offset-1-mobile{margin-left:8.33333337%}.column.is-2-mobile{flex:none;width:16.66666674%}.column.is-offset-2-mobile{margin-left:16.66666674%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333337%}.column.is-offset-4-mobile{margin-left:33.33333337%}.column.is-5-mobile{flex:none;width:41.66666674%}.column.is-offset-5-mobile{margin-left:41.66666674%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333337%}.column.is-offset-7-mobile{margin-left:58.33333337%}.column.is-8-mobile{flex:none;width:66.66666674%}.column.is-offset-8-mobile{margin-left:66.66666674%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333337%}.column.is-offset-10-mobile{margin-left:83.33333337%}.column.is-11-mobile{flex:none;width:91.66666674%}.column.is-offset-11-mobile{margin-left:91.66666674%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none;width:unset}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0%}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333337%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333337%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66666674%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66666674%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333337%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333337%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66666674%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66666674%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333337%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333337%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66666674%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66666674%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333337%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333337%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66666674%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66666674%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1023px){.column.is-narrow-touch{flex:none;width:unset}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0%}.column.is-1-touch{flex:none;width:8.33333337%}.column.is-offset-1-touch{margin-left:8.33333337%}.column.is-2-touch{flex:none;width:16.66666674%}.column.is-offset-2-touch{margin-left:16.66666674%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333337%}.column.is-offset-4-touch{margin-left:33.33333337%}.column.is-5-touch{flex:none;width:41.66666674%}.column.is-offset-5-touch{margin-left:41.66666674%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333337%}.column.is-offset-7-touch{margin-left:58.33333337%}.column.is-8-touch{flex:none;width:66.66666674%}.column.is-offset-8-touch{margin-left:66.66666674%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333337%}.column.is-offset-10-touch{margin-left:83.33333337%}.column.is-11-touch{flex:none;width:91.66666674%}.column.is-offset-11-touch{margin-left:91.66666674%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1024px){.column.is-narrow-desktop{flex:none;width:unset}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0%}.column.is-1-desktop{flex:none;width:8.33333337%}.column.is-offset-1-desktop{margin-left:8.33333337%}.column.is-2-desktop{flex:none;width:16.66666674%}.column.is-offset-2-desktop{margin-left:16.66666674%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333337%}.column.is-offset-4-desktop{margin-left:33.33333337%}.column.is-5-desktop{flex:none;width:41.66666674%}.column.is-offset-5-desktop{margin-left:41.66666674%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333337%}.column.is-offset-7-desktop{margin-left:58.33333337%}.column.is-8-desktop{flex:none;width:66.66666674%}.column.is-offset-8-desktop{margin-left:66.66666674%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333337%}.column.is-offset-10-desktop{margin-left:83.33333337%}.column.is-11-desktop{flex:none;width:91.66666674%}.column.is-offset-11-desktop{margin-left:91.66666674%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){.column.is-narrow-widescreen{flex:none;width:unset}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0%}.column.is-1-widescreen{flex:none;width:8.33333337%}.column.is-offset-1-widescreen{margin-left:8.33333337%}.column.is-2-widescreen{flex:none;width:16.66666674%}.column.is-offset-2-widescreen{margin-left:16.66666674%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333337%}.column.is-offset-4-widescreen{margin-left:33.33333337%}.column.is-5-widescreen{flex:none;width:41.66666674%}.column.is-offset-5-widescreen{margin-left:41.66666674%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333337%}.column.is-offset-7-widescreen{margin-left:58.33333337%}.column.is-8-widescreen{flex:none;width:66.66666674%}.column.is-offset-8-widescreen{margin-left:66.66666674%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333337%}.column.is-offset-10-widescreen{margin-left:83.33333337%}.column.is-11-widescreen{flex:none;width:91.66666674%}.column.is-offset-11-widescreen{margin-left:91.66666674%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}.columns{margin-left:-0.75rem;margin-right:-0.75rem;margin-top:-0.75rem}.columns:last-child{margin-bottom:-0.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - 0.75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0 !important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1024px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){.columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-0-widescreen{--columnGap: 0rem}}.columns.is-variable.is-1{--columnGap: 0.25rem}@media screen and (max-width: 768px){.columns.is-variable.is-1-mobile{--columnGap: 0.25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-1-tablet{--columnGap: 0.25rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-1-tablet-only{--columnGap: 0.25rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-1-touch{--columnGap: 0.25rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-1-desktop{--columnGap: 0.25rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-1-desktop-only{--columnGap: 0.25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-1-widescreen{--columnGap: 0.25rem}}.columns.is-variable.is-2{--columnGap: 0.5rem}@media screen and (max-width: 768px){.columns.is-variable.is-2-mobile{--columnGap: 0.5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-2-tablet{--columnGap: 0.5rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-2-tablet-only{--columnGap: 0.5rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-2-touch{--columnGap: 0.5rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-2-desktop{--columnGap: 0.5rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-2-desktop-only{--columnGap: 0.5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-2-widescreen{--columnGap: 0.5rem}}.columns.is-variable.is-3{--columnGap: 0.75rem}@media screen and (max-width: 768px){.columns.is-variable.is-3-mobile{--columnGap: 0.75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-3-tablet{--columnGap: 0.75rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-3-tablet-only{--columnGap: 0.75rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-3-touch{--columnGap: 0.75rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-3-desktop{--columnGap: 0.75rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-3-desktop-only{--columnGap: 0.75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-3-widescreen{--columnGap: 0.75rem}}.columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){.columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-4-widescreen{--columnGap: 1rem}}.columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){.columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}.columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){.columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}.columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){.columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}.columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){.columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-8-widescreen{--columnGap: 2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}.tile.is-ancestor{margin-left:-0.75rem;margin-right:-0.75rem;margin-top:-0.75rem}.tile.is-ancestor:last-child{margin-bottom:-0.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0 !important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333337%}.tile.is-2{flex:none;width:16.66666674%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333337%}.tile.is-5{flex:none;width:41.66666674%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333337%}.tile.is-8{flex:none;width:66.66666674%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333337%}.tile.is-11{flex:none;width:91.66666674%}.tile.is-12{flex:none;width:100%}}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#323232 !important}a.has-text-black:hover,a.has-text-black:focus{color:#191919 !important}.has-background-black{background-color:#323232 !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#363636 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#1c1c1c !important}.has-background-dark{background-color:#363636 !important}.has-text-primary{color:#da8f4c !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#c9752a !important}.has-background-primary{background-color:#da8f4c !important}.has-text-primary-light{color:#fcf4ee !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#f3dac4 !important}.has-background-primary-light{background-color:#fcf4ee !important}.has-text-primary-dark{color:#94561f !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#be6f27 !important}.has-background-primary-dark{background-color:#94561f !important}.has-text-link{color:#da8f4c !important}a.has-text-link:hover,a.has-text-link:focus{color:#c9752a !important}.has-background-link{background-color:#da8f4c !important}.has-text-link-light{color:#fcf4ee !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#f3dac4 !important}.has-background-link-light{background-color:#fcf4ee !important}.has-text-link-dark{color:#94561f !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#be6f27 !important}.has-background-link-dark{background-color:#94561f !important}.has-text-info{color:#3e8ed0 !important}a.has-text-info:hover,a.has-text-info:focus{color:#2b74b1 !important}.has-background-info{background-color:#3e8ed0 !important}.has-text-info-light{color:#eff5fb !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c6ddf1 !important}.has-background-info-light{background-color:#eff5fb !important}.has-text-info-dark{color:#296fa8 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#368ace !important}.has-background-info-dark{background-color:#296fa8 !important}.has-text-success{color:#b9eec5 !important}a.has-text-success:hover,a.has-text-success:focus{color:#90e4a3 !important}.has-background-success{background-color:#b9eec5 !important}.has-text-success-light{color:#effbf1 !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#c6f1cf !important}.has-background-success-light{background-color:#effbf1 !important}.has-text-success-dark{color:#1d7731 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#27a042 !important}.has-background-success-dark{background-color:#1d7731 !important}.has-text-warning{color:#ffe08a !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#ffd257 !important}.has-background-warning{background-color:#ffe08a !important}.has-text-warning-light{color:#fffaeb !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#ffecb8 !important}.has-background-warning-light{background-color:#fffaeb !important}.has-text-warning-dark{color:#946c00 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#c79200 !important}.has-background-warning-dark{background-color:#946c00 !important}.has-text-danger{color:#f14668 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#ee1742 !important}.has-background-danger{background-color:#f14668 !important}.has-text-danger-light{color:#feecf0 !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#fabdc9 !important}.has-background-danger-light{background-color:#feecf0 !important}.has-text-danger-dark{color:#cc0f35 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#ee2049 !important}.has-background-danger-dark{background-color:#cc0f35 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#363636 !important}.has-background-grey-darker{background-color:#363636 !important}.has-text-grey-dark{color:#999 !important}.has-background-grey-dark{background-color:#999 !important}.has-text-grey{color:#7a7a7a !important}.has-background-grey{background-color:#7a7a7a !important}.has-text-grey-light{color:#fff !important}.has-background-grey-light{background-color:#fff !important}.has-text-grey-lighter{color:#dbdbdb !important}.has-background-grey-lighter{background-color:#dbdbdb !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1023px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1024px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1023px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1024px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1023px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1024px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1023px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1024px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1023px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1024px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Open Sans",sans-serif !important}.is-family-secondary{font-family:"Open Sans",sans-serif !important}.is-family-sans-serif{font-family:"Open Sans",sans-serif !important}.is-family-monospace{font-family:monospace !important}.is-family-code{font-family:monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1023px){.is-block-touch{display:block !important}}@media screen and (min-width: 1024px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1023px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1024px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1023px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1024px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1023px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1024px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1023px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1024px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1023px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1024px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1023px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1024px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:none}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#323232}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#323232}.hero.is-white .subtitle{color:rgba(50,50,50,.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#323232}@media screen and (max-width: 1023px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(50,50,50,.7)}.hero.is-white a.navbar-item:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white .navbar-link:hover,.hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#323232}.hero.is-white .tabs a{color:#323232;opacity:.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#323232}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(50,50,50,.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#323232;border-color:#323232;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #ffffff 71%, white 100%)}@media screen and (max-width: 768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #ffffff 71%, white 100%)}}.hero.is-black{background-color:#323232;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-black .navbar-menu{background-color:#323232}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,.7)}.hero.is-black a.navbar-item:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black .navbar-link:hover,.hero.is-black .navbar-link.is-active{background-color:#252525;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{color:#323232 !important;opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(50,50,50,.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#323232}.hero.is-black.is-bold{background-image:linear-gradient(141deg, #1b1617 0%, #323232 71%, #423d3c 100%)}@media screen and (max-width: 768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1b1617 0%, #323232 71%, #423d3c 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,.7)}.hero.is-light .subtitle{color:rgba(0,0,0,.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width: 1023px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,.7)}.hero.is-light a.navbar-item:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light .navbar-link:hover,.hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.hero.is-light .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(50,50,50,.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, whitesmoke 71%, white 100%)}@media screen and (max-width: 768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, whitesmoke 71%, white 100%)}}.hero.is-dark{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong{color:inherit}.hero.is-dark .title{color:#fff}.hero.is-dark .subtitle{color:rgba(255,255,255,.9)}.hero.is-dark .subtitle a:not(.button),.hero.is-dark .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-dark .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.hero.is-dark .navbar-link{color:rgba(255,255,255,.7)}.hero.is-dark a.navbar-item:hover,.hero.is-dark a.navbar-item.is-active,.hero.is-dark .navbar-link:hover,.hero.is-dark .navbar-link.is-active{background-color:#292929;color:#fff}.hero.is-dark .tabs a{color:#fff;opacity:.9}.hero.is-dark .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a{color:#363636 !important;opacity:1}.hero.is-dark .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover{background-color:rgba(50,50,50,.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}@media screen and (max-width: 768px){.hero.is-dark.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}}.hero.is-primary{background-color:#da8f4c;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong{color:inherit}.hero.is-primary .title{color:#fff}.hero.is-primary .subtitle{color:rgba(255,255,255,.9)}.hero.is-primary .subtitle a:not(.button),.hero.is-primary .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-primary .navbar-menu{background-color:#da8f4c}}.hero.is-primary .navbar-item,.hero.is-primary .navbar-link{color:rgba(255,255,255,.7)}.hero.is-primary a.navbar-item:hover,.hero.is-primary a.navbar-item.is-active,.hero.is-primary .navbar-link:hover,.hero.is-primary .navbar-link.is-active{background-color:#d68237;color:#fff}.hero.is-primary .tabs a{color:#fff;opacity:.9}.hero.is-primary .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a{color:#da8f4c !important;opacity:1}.hero.is-primary .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover{background-color:rgba(50,50,50,.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#da8f4c}.hero.is-primary.is-bold{background-image:linear-gradient(141deg, #d6561d 0%, #da8f4c 71%, #e3b25c 100%)}@media screen and (max-width: 768px){.hero.is-primary.is-bold .navbar-menu{background-image:linear-gradient(141deg, #d6561d 0%, #da8f4c 71%, #e3b25c 100%)}}.hero.is-link{background-color:#da8f4c;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-link .navbar-menu{background-color:#da8f4c}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,.7)}.hero.is-link a.navbar-item:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link .navbar-link:hover,.hero.is-link .navbar-link.is-active{background-color:#d68237;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{color:#da8f4c !important;opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(50,50,50,.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#da8f4c}.hero.is-link.is-bold{background-image:linear-gradient(141deg, #d6561d 0%, #da8f4c 71%, #e3b25c 100%)}@media screen and (max-width: 768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #d6561d 0%, #da8f4c 71%, #e3b25c 100%)}}.hero.is-info{background-color:#3e8ed0;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-info .navbar-menu{background-color:#3e8ed0}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,.7)}.hero.is-info a.navbar-item:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info .navbar-link:hover,.hero.is-info .navbar-link.is-active{background-color:#3082c5;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{color:#3e8ed0 !important;opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(50,50,50,.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3e8ed0}.hero.is-info.is-bold{background-image:linear-gradient(141deg, #208fbc 0%, #3e8ed0 71%, #4d83db 100%)}@media screen and (max-width: 768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #208fbc 0%, #3e8ed0 71%, #4d83db 100%)}}.hero.is-success{background-color:#b9eec5;color:rgba(0,0,0,.7)}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:rgba(0,0,0,.7)}.hero.is-success .subtitle{color:rgba(0,0,0,.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width: 1023px){.hero.is-success .navbar-menu{background-color:#b9eec5}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(0,0,0,.7)}.hero.is-success a.navbar-item:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success .navbar-link:hover,.hero.is-success .navbar-link.is-active{background-color:#a4e9b4;color:rgba(0,0,0,.7)}.hero.is-success .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{color:#b9eec5 !important;opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(50,50,50,.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#b9eec5}.hero.is-success.is-bold{background-image:linear-gradient(141deg, #89eb8f 0%, #b9eec5 71%, #ccf5dc 100%)}@media screen and (max-width: 768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #89eb8f 0%, #b9eec5 71%, #ccf5dc 100%)}}.hero.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width: 1023px){.hero.is-warning .navbar-menu{background-color:#ffe08a}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(0,0,0,.7)}.hero.is-warning a.navbar-item:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning .navbar-link.is-active{background-color:#ffd970;color:rgba(0,0,0,.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{color:#ffe08a !important;opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(50,50,50,.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#ffe08a}.hero.is-warning.is-bold{background-image:linear-gradient(141deg, #ffb657 0%, #ffe08a 71%, #fff6a3 100%)}@media screen and (max-width: 768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ffb657 0%, #ffe08a 71%, #fff6a3 100%)}}.hero.is-danger{background-color:#f14668;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-danger .navbar-menu{background-color:#f14668}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,.7)}.hero.is-danger a.navbar-item:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger .navbar-link.is-active{background-color:#ef2e55;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{color:#f14668 !important;opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(50,50,50,.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#f14668}.hero.is-danger.is-bold{background-image:linear-gradient(141deg, #fa0a62 0%, #f14668 71%, #f7595f 100%)}@media screen and (max-width: 768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #fa0a62 0%, #f14668 71%, #f7595f 100%)}}.hero.is-small .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{.hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{.hero.is-large .hero-body{padding:18rem 6rem}}.hero.is-halfheight .hero-body,.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}.hero.is-halfheight .hero-body>.container,.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}.hero-video.is-transparent{opacity:.3}@media screen and (max-width: 768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:.75rem}}@media screen and (min-width: 769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-head,.hero-foot{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{.hero-body{padding:3rem 3rem}}.section{padding:3rem 1.5rem}@media screen and (min-width: 1024px){.section{padding:3rem 3rem}.section.is-medium{padding:9rem 4.5rem}.section.is-large{padding:18rem 6rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem}code .number{align-items:normal;background-color:transparent;border-radius:0;display:inline;height:auto;justify-content:normal;margin-right:auto;min-width:auto;padding:0;text-align:left;vertical-align:baseline;font-size:inherit}html{scroll-behavior:smooth}@media(prefers-reduced-motion: reduce){html{scroll-behavior:auto}#faq p.desc{transition:none}}.svg-icon svg{width:3rem}.svg-icon svg{fill:#da8f4c}.title{font-family:"Playfair Display",serif}.title.underline span{background-size:2px 1em;box-shadow:inset 0 -0.175em #b9eec5,inset 0 -0.2em #b9eec5;display:inline;box-shadow:inset 0 -0.175em #b9eec5,inset 0 -0.2em #b9eec5}.title.underline-bright span{background-size:2px 1em;box-shadow:inset 0 -0.175em #f2fff5,inset 0 -0.2em #f2fff5;display:inline;box-shadow:inset 0 -0.175em #f2fff5,inset 0 -0.2em #f2fff5}.container{color:#4c4c4c}.quote::before{display:inline-block;content:'"';font-size:3rem;line-height:100%;font-family:"Coustard",serif;position:relative;bottom:-0.75rem;margin-right:.25rem;color:#da8f4c}p,h1,h2,h3,h4,div{line-height:1.5rem}img.avatar{border:4px solid #b9eec5}nav a.navbar-item{color:#656565;font-weight:bold}.bg-green{background-color:#b9eec5}.bg-white{background-color:#fff}.subtitle{color:#656565}.pos-rel{position:relative}.chapter9{transform:rotate(-10deg) translateY(30px);margin-bottom:-20px !important}@media screen and (min-width: 769px){.chapter9{transform:rotate(-10deg) translate(30px, -30px);margin:0 auto}}nav.navbar{z-index:99999 !important}#hero h1{line-height:2.75rem;font-size:2.5rem}#hero h2{line-height:2rem;font-size:1.2rem}#hero .buttons{margin:1.5rem 0 0 0}#hero .buttons .button{height:3rem}#hero .book-cover{transform:translateX(-60px)}#key-characteristics{padding:4.5rem 1rem}#key-characteristics h2{font-size:2rem}#key-characteristics h4{margin-bottom:.5rem}#key-characteristics p{margin:0 0 1.5rem 0}@media screen and (min-width: 769px){#key-characteristics{padding:4.5rem}#key-characteristics h2{font-size:2.5rem}#key-characteristics h4{margin-bottom:1.25rem}#key-characteristics p{margin:0}}#the-problem{padding:4.5rem 1rem}#the-problem h2{font-size:2rem;text-align:center}@media screen and (min-width: 769px){#the-problem{padding:4.5rem}#the-problem h2{font-size:2.5rem;text-align:left}}#the-problem p{margin:1.5rem 0 0 0}#the-problem .buttons{margin:1.5rem 0 0 0}#the-problem .buttons .button{height:3rem}#alternative-solution section{padding:4.5rem 1rem}#alternative-solution h2{font-size:2rem;text-align:center;margin:0 0 2rem 0}@media screen and (min-width: 769px){#alternative-solution section{padding:4.5rem}#alternative-solution h2{font-size:2.5rem;text-align:left}}#alternative-solution picture{display:block;margin:1.5rem 0 0 0}#alternative-solution img{border-radius:20px}#alternative-solution p{margin:1.5rem 0 0 0}#alternative-solution .buttons{margin:1.5rem 0 0 0}#alternative-solution .buttons .button{height:3rem}#complete-description{padding:0}#complete-description h2{font-size:1.5rem}#complete-description #complete-description-features{padding:2rem 1rem}#complete-description #chapters-container{margin:0 1rem}#complete-description #chapters-container ol{padding-left:2rem}#complete-description #chapters-container li p{max-width:600px}@media screen and (min-width: 769px){#complete-description{padding:0 4.5rem 4.5rem 4.5rem}#complete-description #complete-description-features{padding:3rem}#complete-description h2{font-size:2rem;margin:0 0 3rem 0}}#complete-description h3{font-size:1.5rem;margin:0 0 3rem 0}#complete-description ul li{font-size:1.25rem;margin:0 0 1.5rem 0}#complete-description ol li:first-child h3{padding:0}#complete-description div.container.bg-green{background-image:linear-gradient(to right bottom, #b9eec5, #b1e8be, #aae1b6, #a2dbaf, #9bd5a8)}@counter-style chapter{system:extends decimal;prefix:"Chapter "}#chapters h2,#chapters h3{margin:0;padding:0;line-height:2rem}#chapters h3{padding:3rem 0 0 0}#chapters h4{margin:3rem 0 1.5rem 0;line-height:1.5rem;font-size:1.3rem}#chapters .columns{margin:0 0 0 0}#chapters .columns .column{padding-top:0;padding-bottom:0}@media screen and (min-width: 769px){#chapters ol{list-style:chapter}}#benefits section{padding:4.5rem 1rem}#benefits h2{font-size:2rem}@media screen and (min-width: 769px){#benefits section{padding:4.5rem}#benefits h2{font-size:2.5rem}}#authors section{padding:4.5rem 1rem}#authors h2{line-height:2.5rem;margin:0;padding:0}#authors h3.title{margin:3rem 0 1.5rem 0;padding:0;line-height:3rem;text-align:center}#authors h3.subtitle{line-height:2rem;font-size:1.5rem;padding:3rem 0 0 0}@media screen and (min-width: 769px){#authors section{padding:4.5rem}#authors h2{line-height:2.5rem;margin:0;padding:0}#authors h3.title{text-align:left}#authors h3.subtitle{line-height:2.5rem;font-size:1.5rem;padding:3rem 0 0 0}}#authors p{line-height:1.5rem}#authors .column{padding-top:0;padding-bottom:0}#authors .avatar{border-radius:50%;margin-bottom:1.5rem !important}#authors ul{margin:1.5rem 0 1.5rem 0;padding:0;list-style:none;display:flex;justify-content:left;flex-wrap:wrap;gap:1rem}#authors ul li a{color:#da8f4c;fill:#754418;vertical-align:baseline;font-weight:bold}#authors ul li a:hover{color:#754418;fill:#323232}#reviews section{padding:4.5rem 1rem}@media screen and (min-width: 769px){#reviews section{padding:4.5rem}}#reviews h2{margin:0 0 3rem 0;padding:0;line-height:3rem}#reviews .column{padding:1.5rem 1rem}#who-is-it-for{padding:4.5rem 0rem}#who-is-it-for h2{font-size:2rem}#who-is-it-for #who-is-it-for-main{padding:2rem 1rem}#who-is-it-for #who-is-it-for-why{padding:2rem 1rem}@media screen and (min-width: 769px){#who-is-it-for{padding:4.5rem}#who-is-it-for h2{font-size:2.5rem}#who-is-it-for #who-is-it-for-main{padding:3rem}}#who-is-it-for ul li{font-size:1.25rem;margin:0 0 1.5rem 0}#who-is-it-for div.container.bg-green{background-image:linear-gradient(to right bottom, #b9eec5, #b1e8be, #aae1b6, #a2dbaf, #9bd5a8)}#who-is-it-for .container p{font-size:1.25rem;margin:2rem 0 0 0}#final-cta section{padding:4.5rem 1rem}#final-cta h2{font-size:2rem}@media screen and (min-width: 769px){#final-cta section{padding:4.5rem}#final-cta h2{font-size:2.5rem;line-height:3rem}#final-cta h3{line-height:2rem}}#amazon-reviews section{padding:4.5rem 1rem}#amazon-reviews h2{font-size:2rem}@media screen and (min-width: 769px){#amazon-reviews section{padding:4.5rem}#amazon-reviews h2{font-size:2.5rem}}#faq section{padding:4.5rem 1rem}@media screen and (min-width: 769px){#faq section{padding:4.5rem}}#faq dt{padding-top:1em}#faq button{border:none;text-align:left;background:transparent;width:100%;cursor:pointer;position:relative}#faq button:active{border:none}#faq button:focus{border:none;outline:0}#faq button h3{font-size:1rem;line-height:1.5rem;margin:1.5rem 0 0 0;text-align:left;font-weight:bold;padding-left:2em}#faq button[aria-expanded=false] h3::before{content:"+ ";font-weight:bold;color:#3ed05f;display:inline-block;position:absolute;left:0}#faq button[aria-expanded=true] h3::before{content:"- ";font-weight:bold;color:#3ed05f;display:inline-block;position:absolute;left:0}#faq div.desc{padding:1.5rem 0 1.5rem 3.2rem;margin-bottom:1.5rem;line-height:1.5rem;overflow:hidden;transition:all .1s linear}#faq div.desc ul{list-style:disc;padding:1.5em 0 1.5em 3em}#faq dd{border-bottom:1px solid #b9eec5;margin:0 0}#sample-chapter{padding:4.5rem 1rem}@media screen and (min-width: 769px){#sample-chapter{padding:4.5rem}}#sample-chapter h2,#sample-chapter h3{line-height:3rem;margin:0}#sample-chapter p{margin-top:1.5rem}#sample-chapter .buttons .button{height:3rem}#page404{background:#b9eec5}#page404 .content{padding:3em;border-radius:20px;background:#fff}#page404 img{border-radius:20px}.is-green{color:#2bb049}.bd-lt{border-radius:6rem 0 0 0}.bd-rt{border-radius:0 6rem 0 0}.bd-rd{border-radius:0 0 6rem 0}.bd-ld{border-radius:0 0 0 6rem}.bd-lt-rd{border-radius:6rem 0 6rem 0}.bd-all-small{border-radius:3rem}.article article h2,.article article h3,.article article h4{margin-top:1.25em}.article article h2 code,.article article h3 code,.article article h4 code{font-size:inherit;padding:inherit;background:none;color:inherit}.article article p{margin-top:.75em}.article article p:first-child{margin-top:0;font-size:1.25em;line-height:1.5em}.article article .author-info{padding:0 0 1em 0;margin:0 0 1em 0;border-bottom:1px solid #eee}.article article .author-info p{color:#616161;display:flex;align-items:center;font-size:1em}.article article .author-info a{display:flex;align-items:center;margin:0 .75em 0 0}.article article .author-info img{width:2em;border-radius:50%;margin:0 .75em}.article article .author-info svg{margin:0 .75em 0 0}.article aside .wrapper-sticky{position:-webkit-sticky;position:sticky;top:80px}.article aside .aside-ad-block .book{transform:translateX(-1em)}.article aside .aside-ad-block a{background:#b9eec5;padding:1em;border-radius:1em;margin:0 0 2em 0;color:#323232;display:block;border:3px solid transparent}.article aside .aside-ad-block a:hover{border:3px solid #2bb049}.article aside .aside-ad-block h5{font-weight:bold;font-size:.8em;padding:0 0 .2em 0}.article aside .aside-ad-block p{font-size:.8em}.article aside h3{margin-bottom:1em}.article aside .toc{margin:0 0 2em 0}.article aside .toc ol{padding:0 0 0 2em;list-style:decimal-leading-zero}.blog .article-list{display:flex;flex-wrap:wrap;flex-direction:column}.blog .article-list .article{width:100%;max-width:800px}.blog .article-list .article a{border-radius:6px;color:#323232;display:block;font-size:1.25rem;padding:3rem 1.5rem 1.5rem 1.5rem;position:relative;transition-duration:86ms;transition-property:background-color,color}.blog .article-list .article a:hover{color:#000;background-color:#e2f8e7}.blog .article-list .article .date{position:absolute;left:1.5em;top:1em;text-align:center;min-width:1em;font-size:.75em;color:#da8f4c}.blog .article-list .article .article-title{color:#323232;font-size:1.5rem;font-weight:600;padding:0 0 .5em 0}.blog .article-list .article .description{color:#4c4c4c;font-size:.75em}.blog .resources{display:flex;justify-content:space-around;flex-wrap:wrap;margin:3em 0 3em 0}.blog .resources .resource{width:33%;min-width:300px;margin:0 0 2em 0}.blog .resources .resource a{display:block;border-radius:6px;padding:1em 0}.blog .resources .resource a:hover{color:#323232;background-color:#ebc4a1}.blog .resources .resource a:hover h4{color:#323232}.blog .resources .resource figure{text-align:center;padding:1em 0}.blog .resources .resource figure span.icon{width:2.5em}.blog .resources .resource figure svg{font-size:2em;height:48px}.blog .resources .resource h4{text-align:center;color:#da8f4c;font-weight:bold;font-size:1.5em}.blog .resources .resource p{padding:1em;text-align:center;color:#323232}@media screen and (min-width: 769px){.blog .article-list .article .date{left:1em;top:1.5em}.blog .article-list .article a{padding:1.5rem 1.5rem 1.5rem 8rem}}.hhh{background-color:#fff;background-size:100% 1.5em;background-image:linear-gradient(0deg, transparent 79px, #abced4 79px, #abced4 81px, transparent 81px),linear-gradient(#eee 0.05em, transparent 0.05em)} + +/*# sourceMappingURL=style.2099d5a76e3050e6dea6.css.map*/ \ No newline at end of file diff --git a/assets/style.2099d5a76e3050e6dea6.css.map b/assets/style.2099d5a76e3050e6dea6.css.map new file mode 100644 index 0000000..003d1fa --- /dev/null +++ b/assets/style.2099d5a76e3050e6dea6.css.map @@ -0,0 +1 @@ +{"version":3,"file":"style.2099d5a76e3050e6dea6.css","mappings":"AAAA,WCCA,sBACE,kBACA,gBACA,4CACA,4VACA,YASF,uBACE,kBACA,gBACA,4CACA,+VACA,YASF,uBACE,kBACA,gBACA,6CACA,gWACA,YASF,uBACE,kBACA,gBACA,6CACA,4VACA,YASF,uBACE,kBACA,gBACA,6CACA,yWACA,YASF,8BACE,kBACA,gBACA,6CACA,uTACA,0IC1EF,oBCYE,wBACA,mBACA,6BACA,kBCqDO,gBDnDP,oBACA,eCiBO,aD9BQ,2BAgBf,gBAfoB,iCAEK,gCACE,+DADF,kBAmBzB,mBACA,y3BAEA,YAIE,ulBACF,kBAEE,8HDlCJ,0BG4LE,yBACA,sBACA,qBACA,iBACA,0FH7LF,4BGgME,kBACA,eACA,aACA,YACA,cACA,cACA,qBACA,oBACA,kBACA,QACA,yBACA,wBACA,aACA,mXAGA,oBDxKc,sBFlChB,0BGmLE,yBACA,sBACA,qBACA,iBACA,qBAwBA,wBACA,mCACA,YACA,qBDxJe,eC0Jf,oBACA,qBACA,YACA,cACA,YACA,YACA,gBACA,eACA,gBACA,eACA,aACA,kBACA,mBACA,WACA,yEACA,qBLjNM,WKoNJ,cACA,SACA,kBACA,QACA,0DACA,+BACA,sCACF,UACE,UACA,oCACF,UACE,UACA,mEACF,kCAEE,oCACF,kCACE,wCAEF,WACE,gBACA,eACA,gBACA,eACA,WACA,0CACF,WACE,gBACA,eACA,gBACA,eACA,WACA,wCACF,WACE,gBACA,eACA,gBACA,eACA,WACA,wFH1QJ,0CG6QE,yBACA,qBDvNe,+BCyNf,6BACA,WACA,cACA,WACA,kBACA,UACA,0wBHnRF,QGqRkB,yBAGhB,QAHgB,sBHlRlB,oBGqDE,wBACA,gBACA,gBACA,YACA,mBACA,oBACA,cACA,SACA,UACA,2ECrFF,0GAEA,QAuBE,UACA,mBAGF,cAME,mBACA,IAGF,eACE,8BAGF,QAIE,MAGF,qBACE,sBAGA,kBAGE,WAGJ,WAEE,eACA,QAGF,QACE,OAGF,wBACE,iBACA,OAEF,SAEE,iCACA,kBACE,MC7CJ,qBPDQ,eO7BI,kCAiCV,mCACA,gB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CAiTlB,cAjTkB,gCAiTlB,iBAjTkB,aEzC1B,WACE,cACA,kBACA,WACA,qBACA,yBACE,kBrB0CE,8BqBvCF,uCpBwGF,WoBjHF,eAWI,wCpBgHA,8CoB9GA,gBACE,wCpBkHF,gCoB7GA,gBACE,iBCEJ,gBACE,uNASA,iBA/B0B,yEAiC5B,atBjCa,gBAqCG,kBsBvCY,aA4C5B,aACE,mBACA,+BACA,cACE,aACJ,gBACE,sBACA,+BACA,mBACE,aACJ,eACE,sBACA,+BACA,mBACE,aACJ,gBACE,mBACA,aACF,iBACE,sBACA,aACF,aACE,kBACA,qBACF,wBtB3Da,8BsBJkB,qBACJ,aAkE3B,2BACE,gBACwB,eACxB,yBACA,uBACE,wCACA,2BACE,wCACF,2BACE,wCACF,2BACE,wCACF,2BACE,aACN,uBACE,gBACwB,eACxB,gBACA,sBACE,gBACA,mBACA,sBACE,aACN,eAC0B,iBAC1B,eACE,iBACA,kBACA,mCACA,cACE,kCACF,iBACE,qBACF,oBACE,4BACF,iBACE,cACJ,gCrB9CA,gBqBgDE,qBAvGkB,gBAyGlB,iBACA,2BACF,aAEE,gBACF,UACE,qCACA,wBA9GwB,qBACM,mBACL,mBAiHvB,mBACF,atB5HW,gCsB8HT,kBACE,iDAEF,oBArHiC,ctBZxB,iDsBsIT,oBAvHiC,ctBfxB,6EsB6IL,qBA/H6C,sBAmInD,YACE,mBAEJ,gBtBpHO,oBsBsHP,ctBvHO,oBsByHP,iBtB1HO,mBsB4HP,gBtB7HO,OuB7BT,kBACE,oBACA,uBACA,cATgB,6BAahB,WAZsB,4BAetB,WAduB,2BAiBvB,WAhBsB,uBAoBxB,sBACE,cACA,oBACA,eACA,mBA3BgB,mBA6BhB,kBACA,WACE,cACA,mCACA,kBA7BgB,oCAkChB,iBAlCgB,eAwCpB,YACE,QCzCF,aACE,kBACA,YACA,aACE,YACA,WACA,uBACA,oBxB6Da,qBwB3Df,UACE,ytBAkBA,WAGE,WACA,iCACJ,gBAEE,gBACF,eACE,gBACF,eACE,gBACF,oBACE,gBACF,eACE,iBACF,kBACE,gBACF,eACE,gBACF,oBAC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BAEF,wBACE,6DAEA,wBAEE,8BACJ,mCACE,gBA5BJ,wBACE,6CAEA,wBAEE,sBACJ,mCACE,sBAKA,wBACE,yDAEA,wBAEE,4BACJ,mCACE,qBAEF,wBACE,uDAEA,wBAEE,2BACJ,mCACE,gBA5BJ,wBACE,6CAEA,wBAEE,sBACJ,mCACE,sBAKA,wBACE,yDAEA,wBAEE,4BACJ,mCACE,qBAEF,wBACE,uDAEA,wBAEE,2BACJ,mCACE,mBA5BJ,wBACE,mDAEA,wBAEE,yBACJ,mCACE,yBAKA,wBACE,+DAEA,wBAEE,+BACJ,mCACE,wBAEF,wBACE,6DAEA,wBAEE,8BACJ,mCACE,mBA5BJ,wBACE,mDAEA,wBAEE,yBACJ,mCACE,yBAKA,wBACE,+DAEA,wBAEE,+BACJ,mCACE,wBAEF,wBACE,6DAEA,wBAEE,8BACJ,mCACE,kBA5BJ,wBACE,iDAEA,wBAEE,wBACJ,mCACE,wBAKA,wBACE,6DAEA,wBAEE,8BACJ,mCACE,uBAEF,wBACE,2DAEA,wBAEE,6BACJ,mCACE,qBAGJ,wBACE,2BACF,mCACE,qBAHF,wBACE,2BACF,mCACE,uBAHF,wBACE,6BACF,mCACE,qBAHF,qBACE,2BACF,gCACE,gBAHF,wBACE,sBACF,mCACE,sBAHF,qBACE,4BACF,gCACE,wBAHF,wBACE,8BACF,mCACE,qBAHF,wBACE,2BACF,mCACE,qBAHF,wBACE,2BACF,mCACE,wBCpCF,6BACE,gCADF,qCACE,2BADF,gCACE,mCADF,wCACE,sBAIF,2BACE,oBADF,yBACE,4BADF,iCACE,gCAIF,qCACE,8BADF,mCACE,4BADF,iCACE,mCADF,wCACE,kCADF,uCACE,kCADF,uCACE,2BADF,gCACE,yBADF,8BACE,0BADF,+BACE,2BADF,gCACE,8BAIF,mCACE,4BADF,iCACE,0BADF,+BACE,iCADF,sCACE,gCADF,qCACE,gCADF,qCACE,2BADF,gCACE,yBADF,8BACE,uBADF,4BACE,4BADF,iCACE,yBAIF,8BACE,4BADF,iCACE,0BADF,+BACE,wBADF,6BACE,0BADF,+BACE,uBADF,4BACE,qBADF,0BACE,4BADF,iCACE,0BADF,+BACE,qBAIF,0BACE,2BADF,gCACE,yBADF,8BACE,uBADF,4BACE,yBADF,8BACE,wBADF,6BACE,iBAKA,sBACE,iBADF,sBACE,iBADF,sBACE,iBADF,sBACE,iBADF,sBACE,iBADF,sBACE,mBADF,wBACE,mBADF,wBACE,mBADF,wBACE,mBADF,wBACE,mBADF,wBACE,mBADF,wBACE,qBvC/BJ,UACE,YACA,cACA,iBwCDJ,qBACE,kBAEF,sBACE,gBCPF,0BACE,gBAEF,0BACE,eAEF,yBACE,8BACA,aCVF,0BACE,cCIF,4BACE,gBCNF,mBACE,iBAEF,oBACE,MAWE,mBACE,OAGA,uBACE,OADF,yBACE,OADF,0BACE,OADF,wBACE,OAGF,wBACE,0BACA,OAGF,uBACE,2BACA,MAfJ,wBACE,OAGA,4BACE,OADF,8BACE,OADF,+BACE,OADF,6BACE,OAGF,6BACE,+BACA,OAGF,4BACE,gCACA,MAfJ,uBACE,OAGA,2BACE,OADF,6BACE,OADF,8BACE,OADF,4BACE,OAGF,4BACE,8BACA,OAGF,2BACE,+BACA,MAfJ,wBACE,OAGA,4BACE,OADF,8BACE,OADF,+BACE,OADF,6BACE,OAGF,6BACE,+BACA,OAGF,4BACE,gCACA,MAfJ,sBACE,OAGA,0BACE,OADF,4BACE,OADF,6BACE,OADF,2BACE,OAGF,2BACE,6BACA,OAGF,0BACE,8BACA,MAfJ,wBACE,OAGA,4BACE,OADF,8BACE,OADF,+BACE,OADF,6BACE,OAGF,6BACE,+BACA,OAGF,4BACE,gCACA,MAfJ,sBACE,OAGA,0BACE,OADF,4BACE,OADF,6BACE,OADF,2BACE,OAGF,2BACE,6BACA,OAGF,0BACE,8BACA,SAfJ,sBACE,UAGA,0BACE,UADF,4BACE,UADF,6BACE,UADF,2BACE,UAGF,2BACE,6BACA,UAGF,0BACE,8BACA,MAfJ,oBACE,OAGA,wBACE,OADF,0BACE,OADF,2BACE,OADF,yBACE,OAGF,yBACE,2BACA,OAGF,wBACE,4BACA,MAfJ,yBACE,OAGA,6BACE,OADF,+BACE,OADF,gCACE,OADF,8BACE,OAGF,8BACE,gCACA,OAGF,6BACE,iCACA,MAfJ,wBACE,OAGA,4BACE,OADF,8BACE,OADF,+BACE,OADF,6BACE,OAGF,6BACE,+BACA,OAGF,4BACE,gCACA,MAfJ,yBACE,OAGA,6BACE,OADF,+BACE,OADF,gCACE,OADF,8BACE,OAGF,8BACE,gCACA,OAGF,6BACE,iCACA,MAfJ,uBACE,OAGA,2BACE,OADF,6BACE,OADF,8BACE,OADF,4BACE,OAGF,4BACE,8BACA,OAGF,2BACE,+BACA,MAfJ,yBACE,OAGA,6BACE,OADF,+BACE,OADF,gCACE,OADF,8BACE,OAGF,8BACE,gCACA,OAGF,6BACE,iCACA,MAfJ,uBACE,OAGA,2BACE,OADF,6BACE,OADF,8BACE,OADF,4BACE,OAGF,4BACE,8BACA,OAGF,2BACE,+BACA,SAfJ,uBACE,UAGA,2BACE,UADF,6BACE,UADF,8BACE,UADF,4BACE,UAGF,4BACE,8BACA,UAGF,2BACE,+BACA,YCzBJ,yBACE,YADF,2BACE,YADF,yBACE,YADF,2BACE,YADF,4BACE,YADF,yBACE,YADF,2BACE,sC7CgGJ,kB6CjGE,yBACE,mBADF,2BACE,mBADF,yBACE,mBADF,2BACE,mBADF,4BACE,mBADF,yBACE,mBADF,2BACE,6C7CoGJ,kB6CrGE,yBACE,mBADF,2BACE,mBADF,yBACE,mBADF,2BACE,mBADF,4BACE,mBADF,yBACE,mBADF,2BACE,wC7C4GJ,iB6C7GE,yBACE,kBADF,2BACE,kBADF,yBACE,kBADF,2BACE,kBADF,4BACE,kBADF,yBACE,kBADF,2BACE,wC7CgHJ,mB6CjHE,yBACE,oBADF,2BACE,oBADF,yBACE,oBADF,2BACE,oBADF,4BACE,oBADF,yBACE,oBADF,2BACE,wC7C+HF,sB6ChIA,yBACE,uBADF,2BACE,uBADF,yBACE,uBADF,2BACE,uBADF,4BACE,uBADF,yBACE,uBADF,2BACE,qBAyBJ,4BACE,qBADF,6BACE,gBADF,0BACE,iBADF,2BACE,sC7CsEF,0B6ClEE,4BACE,6C7CqEJ,0B6CnEE,4BACE,8D7CsEJ,+B6CpEE,4BACE,wC7CuEJ,yB6CrEE,4BACE,wC7CwEJ,2B6CtEE,4BACE,+D7C0EF,gC6CxEA,4BACE,wC7CiFF,8B6C/EA,4BACE,uC7C+CJ,2B6ClEE,6BACE,6C7CqEJ,2B6CnEE,6BACE,8D7CsEJ,gC6CpEE,6BACE,wC7CuEJ,0B6CrEE,6BACE,wC7CwEJ,4B6CtEE,6BACE,+D7C0EF,iC6CxEA,6BACE,wC7CiFF,+B6C/EA,6BACE,uC7C+CJ,sB6ClEE,0BACE,6C7CqEJ,sB6CnEE,0BACE,8D7CsEJ,2B6CpEE,0BACE,wC7CuEJ,qB6CrEE,0BACE,wC7CwEJ,uB6CtEE,0BACE,+D7C0EF,4B6CxEA,0BACE,wC7CiFF,0B6C/EA,0BACE,uC7C+CJ,uB6ClEE,2BACE,6C7CqEJ,uB6CnEE,2BACE,8D7CsEJ,4B6CpEE,2BACE,wC7CuEJ,sB6CrEE,2BACE,wC7CwEJ,wB6CtEE,2BACE,+D7C0EF,6B6CxEA,2BACE,wC7CiFF,2B6C/EA,2BACE,kBAQN,oCACE,eAEF,mCACE,eAEF,mCACE,YAEF,4BACE,gBAEF,oCACE,wBAEF,0BACE,yBACF,0BACE,yBACF,0BACE,2BACF,0BACE,uBACF,0BACE,oBAEF,6CACE,sBAEF,6CACE,uBAEF,6CACE,sBAEF,gCACE,iBAEF,gCACE,WCjGA,wBACE,sC9CgGF,iB8C9FE,wBACE,6C9CiGJ,iB8C/FE,wBACE,8D9CkGJ,sB8ChGE,wBACE,wC9CmGJ,gB8CjGE,wBACE,wC9CoGJ,kB8ClGE,wBACE,+D9CsGF,uB8CpGA,wBACE,wC9C6GF,qB8C3GA,wBACE,WAtBJ,uBACE,sC9CgGF,gB8C9FE,uBACE,6C9CiGJ,gB8C/FE,uBACE,8D9CkGJ,qB8ChGE,uBACE,wC9CmGJ,e8CjGE,uBACE,wC9CoGJ,iB8ClGE,uBACE,+D9CsGF,sB8CpGA,uBACE,wC9C6GF,oB8C3GA,uBACE,aAtBJ,yBACE,sC9CgGF,kB8C9FE,yBACE,6C9CiGJ,kB8C/FE,yBACE,8D9CkGJ,uB8ChGE,yBA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F,wBACE,QAGF,wBACE,WAGF,2BACE,eAGF,kBACE,6DAME,iBAGE,4EAEA,iBACE,gBACA,gBACA,cACA,oBAIJ,gBACE,gCAGF,YACE,iBACA,kBACA,+BAGF,iBACE,iBACA,6BACA,iCAEA,aACE,aACA,mBACA,cACA,iCAGF,YACE,mBACA,mBACA,mCAGF,SACE,kBACA,eACA,mCAGF,kBACE,gCAMJ,uBACE,gBACA,SACA,sCAIA,0BACE,kCAGF,kBAtxBE,YAwxBA,kBACA,iBACA,cAxyBA,cA0yBA,6BACA,wCAGF,wBACE,mCAGF,gBACE,eACA,mBACA,kCAGF,cACE,mBAIJ,iBACE,qBAGF,gBACE,wBAEA,iBACE,gCACA,qBAON,YACE,eACA,sBACA,8BAEA,UACE,gBACA,gCAEA,iBACE,cAv1BA,cAy1BA,kBACA,kCACA,kBACA,yBACA,2CACA,sCAGF,UA91BI,yBAgBA,oCAm1BJ,iBACE,WACA,QACA,kBACA,cACA,gBACA,cAr2BC,6CAy2BH,aAh3BE,iBAk3BA,gBACA,mBACA,2CAGF,aAn3BI,gBAq3BF,kBAKN,YACE,6BACA,eACA,mBACA,4BAEA,SACE,gBACA,iBACA,8BAEA,aACE,kBACA,cACA,oCAEA,aA94BA,yBAYG,uCAs4BD,aAl5BF,mCAw5BF,iBACE,cACA,6CAEA,WACE,uCAGF,aACE,YACA,+BAIJ,iBACE,cAh6BC,iBAk6BD,gBACA,8BAGF,WACE,kBACA,cA/6BA,sCAs7BR,mCAEI,QACE,UACA,gCAGF,iCACE,OAKN,qBACE,2BACA,wJACA,wB","sources":["webpack://nodejs-design-patterns-book/./src/scss/style.scss","webpack://nodejs-design-patterns-book/./src/scss/fonts.scss","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/utilities/extends.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/utilities/controls.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/utilities/initial-variables.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/utilities/mixins.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/base/minireset.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/base/generic.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/utilities/derived-variables.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/base/animations.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/components/breadcrumb.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/components/card.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/components/dropdown.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/components/level.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/components/media.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/components/menu.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/components/message.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/components/modal.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/components/navbar.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/components/pagination.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/components/panel.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/components/tabs.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/elements/box.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/elements/button.sass","webpack://nodejs-design-patterns-book/./src/scss/bulma-custom.scss","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/elements/container.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/elements/content.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/elements/icon.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/elements/image.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/elements/notification.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/elements/progress.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/elements/table.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/elements/tag.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/elements/title.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/elements/other.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/form/shared.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/form/input-textarea.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/form/checkbox-radio.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/form/select.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/form/file.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/form/tools.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/grid/columns.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/grid/tiles.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/helpers/color.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/helpers/flexbox.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/helpers/float.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/helpers/other.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/helpers/overflow.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/helpers/position.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/helpers/spacing.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/helpers/typography.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/helpers/visibility.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/layout/hero.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/layout/section.sass","webpack://nodejs-design-patterns-book/./node_modules/bulma/sass/layout/footer.sass"],"sourcesContent":["@charset \"utf-8\";\n\n// palette\n$black: #323232;\n$blackD1: darken($black, 10%);\n$blackD2: darken($black, 20%);\n$blackD3: darken($black, 30%);\n$blackL1: lighten($black, 10%);\n$blackL2: lighten($black, 20%);\n$blackL3: lighten($black, 30%);\n$orange: #da8f4c;\n$orangeD1: darken($orange, 10%);\n$orangeD2: darken($orange, 20%);\n$orangeD3: darken($orange, 30%);\n$orangeL1: lighten($orange, 10%);\n$orangeL2: lighten($orange, 20%);\n$orangeL3: lighten($orange, 30%);\n$green: #b9eec5;\n$greenD1: darken($green, 10%);\n$greenD2: darken($green, 20%);\n$greenD3: darken($green, 30%);\n$greenD4: darken($green, 40%);\n$greenL1: lighten($green, 10%);\n$greenL2: lighten($green, 20%);\n$greenL3: lighten($green, 30%);\n$gray: #e5e5e5;\n$grayD1: darken($gray, 10%);\n$grayD2: darken($gray, 20%);\n$grayD3: darken($gray, 30%);\n$grayL1: lighten($gray, 10%);\n$grayL2: lighten($gray, 20%);\n$grayL3: lighten($gray, 30%);\n$white: #ffffff;\n$whiteD1: darken($white, 10%);\n$whiteD2: darken($white, 20%);\n$whiteD3: darken($white, 30%);\n$whiteL1: lighten($white, 10%);\n$whiteL2: lighten($white, 20%);\n$whiteL3: lighten($white, 30%);\n\n@import \"./fonts.scss\";\n\n$family-serif: 'Playfair Display', serif;\n$family-sans-serif: 'Open Sans', sans-serif;\n\n@import \"./bulma-custom.scss\";\n\nhtml {\n scroll-behavior: smooth;\n}\n\n@media(prefers-reduced-motion: reduce) {\n html {\n scroll-behavior: auto;\n }\n\n #faq p.desc {\n transition: none;\n }\n}\n\n.svg-icon svg {\n width: 3rem;\n}\n\n.svg-icon svg {\n fill: $orange;\n}\n\n.title {\n font-family: $family-serif;\n}\n\n.title.underline span {\n background-size: 2px 1em;\n box-shadow: inset 0 -0.175em #b9eec5, inset 0 -0.2em #b9eec5;\n display: inline;\n box-shadow: inset 0 -0.175em #b9eec5, inset 0 -0.2em #b9eec5;\n}\n\n.title.underline-bright span {\n background-size: 2px 1em;\n box-shadow: inset 0 -0.175em #f2fff5, inset 0 -0.2em #f2fff5;\n display: inline;\n box-shadow: inset 0 -0.175em #f2fff5, inset 0 -0.2em #f2fff5;\n}\n\n.container {\n color: $blackL1;\n}\n\n.quote::before {\n display: inline-block;\n content: '\"';\n font-size: 3rem;\n line-height: 100%;\n font-family: \"Coustard\", serif;\n position: relative;\n bottom: -.75rem;\n margin-right: .25rem;\n color: $orange;\n}\n\np,\nh1,\nh2,\nh3,\nh4,\ndiv {\n line-height: 1.5rem;\n ;\n}\n\nimg.avatar {\n border: 4px solid $green;\n}\n\nnav a.navbar-item {\n color: $blackL2;\n font-weight: bold;\n}\n\n.bg-green {\n background-color: $green;\n}\n\n.bg-white {\n background-color: $white;\n}\n\n.subtitle {\n color: $blackL2;\n}\n\n.pos-rel {\n position: relative;\n}\n\n.chapter9 {\n transform: rotate(-10deg) translateY(30px);\n margin-bottom: -20px !important;\n}\n\n@media screen and (min-width: 769px) {\n .chapter9 {\n transform: rotate(-10deg) translate(30px, -30px);\n margin: 0 auto;\n }\n}\n\nnav.navbar {\n z-index: 99999 !important;\n}\n\n#hero {\n h1 {\n line-height: 2.75rem;\n font-size: 2.5rem;\n }\n\n h2 {\n line-height: 2rem;\n font-size: 1.2rem;\n }\n\n .buttons {\n margin: 1.5rem 0 0 0;\n\n .button {\n height: 3rem;\n }\n }\n\n .book-cover {\n transform: translateX(-60px);\n }\n}\n\n#key-characteristics {\n padding: 4.5rem 1rem;\n\n h2 {\n font-size: 2rem;\n }\n\n h4 {\n margin-bottom: 0.5rem;\n }\n\n p {\n margin: 0 0 1.5rem 0;\n }\n\n @media screen and (min-width: 769px) {\n padding: 4.5rem;\n\n h2 {\n font-size: 2.5rem;\n }\n\n h4 {\n margin-bottom: 1.25rem;\n }\n\n p {\n margin: 0;\n }\n }\n}\n\n#the-problem {\n padding: 4.5rem 1rem;\n\n h2 {\n font-size: 2rem;\n text-align: center;\n }\n\n @media screen and (min-width: 769px) {\n padding: 4.5rem;\n\n h2 {\n font-size: 2.5rem;\n text-align: left;\n }\n }\n\n p {\n margin: 1.5rem 0 0 0;\n }\n\n .buttons {\n margin: 1.5rem 0 0 0;\n\n .button {\n height: 3rem;\n }\n }\n}\n\n#alternative-solution {\n section {\n padding: 4.5rem 1rem;\n }\n\n h2 {\n font-size: 2rem;\n text-align: center;\n margin: 0 0 2rem 0;\n }\n\n @media screen and (min-width: 769px) {\n section {\n padding: 4.5rem;\n }\n\n h2 {\n font-size: 2.5rem;\n text-align: left;\n }\n }\n\n picture {\n display: block;\n margin: 1.5rem 0 0 0;\n }\n\n img {\n border-radius: 20px;\n }\n\n p {\n margin: 1.5rem 0 0 0;\n }\n\n .buttons {\n margin: 1.5rem 0 0 0;\n\n .button {\n height: 3rem;\n }\n }\n}\n\n#complete-description {\n padding: 0;\n\n h2 {\n font-size: 1.5rem;\n }\n\n #complete-description-features {\n padding: 2rem 1rem;\n }\n\n #chapters-container {\n margin: 0 1rem;\n\n ol {\n padding-left: 2rem;\n }\n\n li p {\n max-width: 600px;\n }\n }\n\n @media screen and (min-width: 769px) {\n padding: 0 4.5rem 4.5rem 4.5rem;\n\n #complete-description-features {\n padding: 3rem;\n }\n\n h2 {\n font-size: 2rem;\n margin: 0 0 3rem 0;\n }\n }\n\n h3 {\n font-size: 1.5rem;\n margin: 0 0 3rem 0;\n }\n\n ul li {\n font-size: 1.25rem;\n margin: 0 0 1.5rem 0;\n }\n\n ol li:first-child h3 {\n padding: 0;\n }\n\n div.container.bg-green {\n background-image: linear-gradient(to right bottom, #b9eec5, #b1e8be, #aae1b6, #a2dbaf, #9bd5a8);\n }\n}\n\n@counter-style chapter {\n system: extends decimal;\n prefix: \"Chapter \";\n}\n\n#chapters {\n\n h2,\n h3 {\n margin: 0;\n padding: 0;\n line-height: 2rem;\n }\n\n h3 {\n padding: 3rem 0 0 0;\n }\n\n h4 {\n margin: 3rem 0 1.5rem 0;\n line-height: 1.5rem;\n font-size: 1.3rem;\n }\n\n .columns {\n margin: 0 0 0 0;\n\n .column {\n padding-top: 0;\n padding-bottom: 0;\n }\n }\n\n @media screen and (min-width: 769px) {\n ol {\n list-style: chapter;\n }\n }\n}\n\n#benefits {\n section {\n padding: 4.5rem 1rem;\n }\n\n h2 {\n font-size: 2rem;\n }\n\n @media screen and (min-width: 769px) {\n section {\n padding: 4.5rem;\n }\n\n h2 {\n font-size: 2.5rem;\n }\n }\n}\n\n#authors {\n section {\n padding: 4.5rem 1rem;\n }\n\n h2 {\n line-height: 2.5rem;\n margin: 0;\n padding: 0;\n }\n\n h3.title {\n margin: 3rem 0 1.5rem 0;\n padding: 0;\n line-height: 3rem;\n text-align: center;\n }\n\n h3.subtitle {\n line-height: 2rem;\n font-size: 1.5rem;\n padding: 3rem 0 0 0;\n }\n\n\n @media screen and (min-width: 769px) {\n section {\n padding: 4.5rem;\n }\n\n h2 {\n line-height: 2.5rem;\n margin: 0;\n padding: 0;\n }\n\n h3.title {\n text-align: left;\n }\n\n h3.subtitle {\n line-height: 2.5rem;\n font-size: 1.5rem;\n padding: 3rem 0 0 0;\n }\n }\n\n p {\n line-height: 1.5rem;\n }\n\n .column {\n padding-top: 0;\n padding-bottom: 0;\n }\n\n .avatar {\n border-radius: 50%;\n margin-bottom: 1.5rem !important;\n }\n\n ul {\n margin: 1.5rem 0 1.5rem 0;\n padding: 0;\n list-style: none;\n display: flex;\n justify-content: left;\n flex-wrap: wrap;\n gap: 1rem;\n\n li a {\n color: $orange;\n fill: $orangeD3;\n vertical-align: baseline;\n font-weight: bold;\n\n &:hover {\n color: $orangeD3;\n fill: $black;\n }\n }\n }\n}\n\n#reviews {\n section {\n padding: 4.5rem 1rem;\n }\n\n @media screen and (min-width: 769px) {\n section {\n padding: 4.5rem;\n }\n }\n\n h2 {\n margin: 0 0 3rem 0;\n padding: 0;\n line-height: 3rem;\n }\n\n .column {\n padding: 1.5rem 1rem;\n }\n}\n\n#who-is-it-for {\n padding: 4.5rem 0rem;\n\n h2 {\n font-size: 2rem;\n }\n\n #who-is-it-for-main {\n padding: 2rem 1rem;\n }\n\n #who-is-it-for-why {\n padding: 2rem 1rem;\n }\n\n\n @media screen and (min-width: 769px) {\n padding: 4.5rem;\n\n h2 {\n font-size: 2.5rem;\n }\n\n #who-is-it-for-main {\n padding: 3rem;\n }\n }\n\n ul li {\n font-size: 1.25rem;\n margin: 0 0 1.5rem 0;\n }\n\n div.container.bg-green {\n background-image: linear-gradient(to right bottom, #b9eec5, #b1e8be, #aae1b6, #a2dbaf, #9bd5a8);\n }\n\n .container p {\n font-size: 1.25rem;\n margin: 2rem 0 0 0;\n }\n}\n\n#final-cta {\n section {\n padding: 4.5rem 1rem;\n }\n\n h2 {\n font-size: 2rem;\n }\n\n @media screen and (min-width: 769px) {\n section {\n padding: 4.5rem;\n }\n\n h2 {\n font-size: 2.5rem;\n line-height: 3rem;\n }\n\n h3 {\n line-height: 2rem;\n }\n }\n}\n\n#amazon-reviews {\n section {\n padding: 4.5rem 1rem;\n }\n\n h2 {\n font-size: 2rem;\n }\n\n @media screen and (min-width: 769px) {\n section {\n padding: 4.5rem;\n }\n\n h2 {\n font-size: 2.5rem;\n }\n }\n}\n\n#faq {\n section {\n padding: 4.5rem 1rem;\n }\n\n @media screen and (min-width: 769px) {\n section {\n padding: 4.5rem;\n }\n }\n\n dt {\n padding-top: 1em\n }\n\n button {\n border: none;\n text-align: left;\n background: transparent;\n width: 100%;\n cursor: pointer;\n position: relative;\n\n\n &:active {\n border: none;\n }\n\n &:focus {\n border: none;\n outline: 0;\n }\n\n h3 {\n font-size: 1rem;\n line-height: 1.5rem;\n margin: 1.5rem 0 0 0;\n text-align: left;\n font-weight: bold;\n padding-left: 2em;\n }\n\n &[aria-expanded=false] h3::before {\n content: \"+ \";\n font-weight: bold;\n color: $greenD3;\n display: inline-block;\n position: absolute;\n left: 0;\n }\n\n &[aria-expanded=true] h3::before {\n content: \"- \";\n font-weight: bold;\n color: $greenD3;\n display: inline-block;\n position: absolute;\n left: 0;\n }\n }\n\n div.desc {\n padding: 1.5rem 0 1.5rem 3.2rem;\n margin-bottom: 1.5rem;\n line-height: 1.5rem;\n overflow: hidden;\n transition: all 0.1s linear;\n\n ul {\n list-style: disc;\n padding: 1.5em 0 1.5em 3em;\n }\n }\n\n dd {\n border-bottom: 1px solid $green;\n margin: 0 0;\n }\n}\n\n#sample-chapter {\n padding: 4.5rem 1rem;\n\n @media screen and (min-width: 769px) {\n padding: 4.5rem;\n }\n\n h2,\n h3 {\n line-height: 3rem;\n margin: 0;\n }\n\n p {\n margin-top: 1.5rem;\n }\n\n .buttons {\n .button {\n height: 3rem;\n }\n }\n}\n\n#page404 {\n background: $green;\n\n .content {\n padding: 3em;\n border-radius: 20px;\n background: $white;\n }\n\n img {\n border-radius: 20px;\n }\n}\n\n.is-green {\n color: $greenD4;\n}\n\n.bd-lt {\n border-radius: 6rem 0 0 0;\n}\n\n.bd-rt {\n border-radius: 0 6rem 0 0;\n}\n\n.bd-rd {\n border-radius: 0 0 6rem 0;\n}\n\n.bd-ld {\n border-radius: 0 0 0 6rem;\n}\n\n.bd-lt-rd {\n border-radius: 6rem 0 6rem 0;\n}\n\n.bd-all-small {\n border-radius: 3rem;\n}\n\n.article {\n article {\n\n h2,\n h3,\n h4 {\n margin-top: 1.25em;\n\n code {\n font-size: inherit;\n padding: inherit;\n background: none;\n color: inherit;\n }\n }\n\n p {\n margin-top: .75em;\n }\n\n p:first-child {\n margin-top: 0;\n font-size: 1.25em;\n line-height: 1.50em;\n }\n\n .author-info {\n padding: 0 0 1em 0;\n margin: 0 0 1em 0;\n border-bottom: 1px solid #eee;\n\n p {\n color: #616161;\n display: flex;\n align-items: center;\n font-size: 1em;\n }\n\n a {\n display: flex;\n align-items: center;\n margin: 0 .75em 0 0;\n }\n\n img {\n width: 2em;\n border-radius: 50%;\n margin: 0 .75em;\n }\n\n svg {\n margin: 0 .75em 0 0;\n }\n }\n }\n\n aside {\n .wrapper-sticky {\n position: -webkit-sticky;\n position: sticky;\n top: 80px;\n }\n\n .aside-ad-block {\n .book {\n transform: translateX(-1em);\n }\n\n a {\n background: $green;\n padding: 1em;\n border-radius: 1em;\n margin: 0 0 2em 0;\n color: $black;\n display: block;\n border: 3px solid transparent;\n }\n\n a:hover {\n border: 3px solid $greenD4;\n }\n\n h5 {\n font-weight: bold;\n font-size: .8em;\n padding: 0 0 .2em 0;\n }\n\n p {\n font-size: .8em;\n }\n }\n\n h3 {\n margin-bottom: 1em;\n }\n\n .toc {\n margin: 0 0 2em 0;\n\n ol {\n padding: 0 0 0 2em;\n list-style: decimal-leading-zero;\n }\n }\n }\n}\n\n.blog {\n .article-list {\n display: flex;\n flex-wrap: wrap;\n flex-direction: column;\n\n .article {\n width: 100%;\n max-width: 800px;\n\n a {\n border-radius: 6px;\n color: $black;\n display: block;\n font-size: 1.25rem;\n padding: 3rem 1.5rem 1.5rem 1.5rem;\n position: relative;\n transition-duration: 86ms;\n transition-property: background-color, color;\n }\n\n a:hover {\n color: $blackD3;\n background-color: $greenL1;\n }\n\n .date {\n position: absolute;\n left: 1.5em;\n top: 1em;\n text-align: center;\n min-width: 1em;\n font-size: .75em;\n color: $primary;\n }\n\n .article-title {\n color: $black;\n font-size: 1.5rem;\n font-weight: 600;\n padding: 0 0 .5em 0;\n }\n\n .description {\n color: $blackL1;\n font-size: .75em;\n }\n }\n }\n\n .resources {\n display: flex;\n justify-content: space-around;\n flex-wrap: wrap;\n margin: 3em 0 3em 0;\n\n .resource {\n width: 33%;\n min-width: 300px;\n margin: 0 0 2em 0;\n\n a {\n display: block;\n border-radius: 6px;\n padding: 1em 0;\n\n &:hover {\n color: $black;\n background-color: $orangeL2;\n\n h4 {\n color: $black;\n }\n }\n }\n\n figure {\n text-align: center;\n padding: 1em 0;\n\n span.icon {\n width: 2.5em;\n }\n\n svg {\n font-size: 2em;\n height: 48px;\n }\n }\n\n h4 {\n text-align: center;\n color: $primary;\n font-weight: bold;\n font-size: 1.5em;\n }\n\n p {\n padding: 1em;\n text-align: center;\n color: $black;\n }\n }\n }\n}\n\n@media screen and (min-width: 769px) {\n .blog .article-list .article {\n .date {\n left: 1em;\n top: 1.5em;\n }\n\n a {\n padding: 1.5rem 1.5rem 1.5rem 8rem;\n }\n }\n}\n\n.hhh {\n background-color: #fff;\n background-size: 100% 1.5em;\n background-image: linear-gradient(0deg, transparent 79px, #abced4 79px, #abced4 81px, transparent 81px), linear-gradient(#eee .05em, transparent .05em);\n}","/* Coustard_400 */\n@font-face {\n font-family: 'Coustard';\n font-style: normal;\n font-weight: 400;\n src: url('../_includes/fonts/Coustard_400.eot'); /* IE9 Compat Modes */\n src: local('Coustard Regular'), local('Coustard-Regular'),\n url('../_includes/fonts/Coustard_400.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */\n url('../_includes/fonts/Coustard_400.woff2') format('woff2'), /* Super Modern Browsers */\n url('../_includes/fonts/Coustard_400.woff') format('woff'), /* Modern Browsers */\n url('../_includes/fonts/Coustard_400.ttf') format('truetype'), /* Safari, Android, iOS */\n url('../_includes/fonts/Coustard_400.svg#Coustard') format('svg'); /* Legacy iOS */\n}\n\n/* open-sans-regular - latin */\n@font-face {\n font-family: 'Open Sans';\n font-style: normal;\n font-weight: 400;\n src: url('../_includes/fonts/Open_Sans_400.eot'); /* IE9 Compat Modes */\n src: local('Open Sans Regular'), local('OpenSans-Regular'),\n url('../_includes/fonts/Open_Sans_400.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */\n url('../_includes/fonts/Open_Sans_400.woff2') format('woff2'), /* Super Modern Browsers */\n url('../_includes/fonts/Open_Sans_400.woff') format('woff'), /* Modern Browsers */\n url('../_includes/fonts/Open_Sans_400.ttf') format('truetype'), /* Safari, Android, iOS */\n url('../_includes/fonts/Open_Sans_400.svg#OpenSans') format('svg'); /* Legacy iOS */\n}\n\n/* open-sans-italic - latin */\n@font-face {\n font-family: 'Open Sans';\n font-style: italic;\n font-weight: 400;\n src: url('../_includes/fonts/Open_Sans_400i.eot'); /* IE9 Compat Modes */\n src: local('Open Sans Italic'), local('OpenSans-Italic'),\n url('../_includes/fonts/Open_Sans_400i.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */\n url('../_includes/fonts/Open_Sans_400i.woff2') format('woff2'), /* Super Modern Browsers */\n url('../_includes/fonts/Open_Sans_400i.woff') format('woff'), /* Modern Browsers */\n url('../_includes/fonts/Open_Sans_400i.ttf') format('truetype'), /* Safari, Android, iOS */\n url('../_includes/fonts/Open_Sans_400i.svg#OpenSans') format('svg'); /* Legacy iOS */\n}\n\n/* open-sans-700 - latin */\n@font-face {\n font-family: 'Open Sans';\n font-style: normal;\n font-weight: 700;\n src: url('../_includes/fonts/Open_Sans_700.eot'); /* IE9 Compat Modes */\n src: local('Open Sans Bold'), local('OpenSans-Bold'),\n url('../_includes/fonts/Open_Sans_700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */\n url('../_includes/fonts/Open_Sans_700.woff2') format('woff2'), /* Super Modern Browsers */\n url('../_includes/fonts/Open_Sans_700.woff') format('woff'), /* Modern Browsers */\n url('../_includes/fonts/Open_Sans_700.ttf') format('truetype'), /* Safari, Android, iOS */\n url('../_includes/fonts/Open_Sans_700.svg#OpenSans') format('svg'); /* Legacy iOS */\n}\n\n/* open-sans-700italic - latin */\n@font-face {\n font-family: 'Open Sans';\n font-style: italic;\n font-weight: 700;\n src: url('../_includes/fonts/Open_Sans_700i.eot'); /* IE9 Compat Modes */\n src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'),\n url('../_includes/fonts/Open_Sans_700i.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */\n url('../_includes/fonts/Open_Sans_700i.woff2') format('woff2'), /* Super Modern Browsers */\n url('../_includes/fonts/Open_Sans_700i.woff') format('woff'), /* Modern Browsers */\n url('../_includes/fonts/Open_Sans_700i.ttf') format('truetype'), /* Safari, Android, iOS */\n url('../_includes/fonts/Open_Sans_700i.svg#OpenSans') format('svg'); /* Legacy iOS */\n}\n\n/* playfair-display-600 - latin */\n@font-face {\n font-family: 'Playfair Display';\n font-style: normal;\n font-weight: 600;\n src: url('../_includes/fonts/Playfair_Display_600.eot'); /* IE9 Compat Modes */\n src: local(''),\n url('../_includes/fonts/Playfair_Display_600.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */\n url('../_includes/fonts/Playfair_Display_600.woff2') format('woff2'), /* Super Modern Browsers */\n url('../_includes/fonts/Playfair_Display_600.woff') format('woff'), /* Modern Browsers */\n url('../_includes/fonts/Playfair_Display_600.ttf') format('truetype'), /* Safari, Android, iOS */\n url('../_includes/fonts/Playfair_Display_600.svg#PlayfairDisplay') format('svg'); /* Legacy iOS */\n}","@import \"mixins\"\n\n%control\n +control\n\n%unselectable\n +unselectable\n\n%arrow\n +arrow\n\n%block\n +block\n\n%delete\n +delete\n\n%loader\n +loader\n\n%overlay\n +overlay\n\n%reset\n +reset\n","@import \"derived-variables\"\n\n$control-radius: $radius !default\n$control-radius-small: $radius-small !default\n\n$control-border-width: 1px !default\n\n$control-height: 2.5em !default\n$control-line-height: 1.5 !default\n\n$control-padding-vertical: calc(0.5em - #{$control-border-width}) !default\n$control-padding-horizontal: calc(0.75em - #{$control-border-width}) !default\n\n=control\n -moz-appearance: none\n -webkit-appearance: none\n align-items: center\n border: $control-border-width solid transparent\n border-radius: $control-radius\n box-shadow: none\n display: inline-flex\n font-size: $size-normal\n height: $control-height\n justify-content: flex-start\n line-height: $control-line-height\n padding-bottom: $control-padding-vertical\n padding-left: $control-padding-horizontal\n padding-right: $control-padding-horizontal\n padding-top: $control-padding-vertical\n position: relative\n vertical-align: top\n // States\n &:focus,\n &.is-focused,\n &:active,\n &.is-active\n outline: none\n &[disabled],\n fieldset[disabled] &\n cursor: not-allowed\n\n// The controls sizes use mixins so they can be used at different breakpoints\n=control-small\n border-radius: $control-radius-small\n font-size: $size-small\n=control-medium\n font-size: $size-medium\n=control-large\n font-size: $size-large\n","// Colors\n\n$black: hsl(0, 0%, 4%) !default\n$black-bis: hsl(0, 0%, 7%) !default\n$black-ter: hsl(0, 0%, 14%) !default\n\n$grey-darker: hsl(0, 0%, 21%) !default\n$grey-dark: hsl(0, 0%, 29%) !default\n$grey: hsl(0, 0%, 48%) !default\n$grey-light: hsl(0, 0%, 71%) !default\n$grey-lighter: hsl(0, 0%, 86%) !default\n$grey-lightest: hsl(0, 0%, 93%) !default\n\n$white-ter: hsl(0, 0%, 96%) !default\n$white-bis: hsl(0, 0%, 98%) !default\n$white: hsl(0, 0%, 100%) !default\n\n$orange: hsl(14, 100%, 53%) !default\n$yellow: hsl(44, 100%, 77%) !default\n$green: hsl(153, 53%, 53%) !default\n$turquoise: hsl(171, 100%, 41%) !default\n$cyan: hsl(207, 61%, 53%) !default\n$blue: hsl(229, 53%, 53%) !default\n$purple: hsl(271, 100%, 71%) !default\n$red: hsl(348, 86%, 61%) !default\n\n// Typography\n\n$family-sans-serif: BlinkMacSystemFont, -apple-system, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif !default\n$family-monospace: monospace !default\n$render-mode: optimizeLegibility !default\n\n$size-1: 3rem !default\n$size-2: 2.5rem !default\n$size-3: 2rem !default\n$size-4: 1.5rem !default\n$size-5: 1.25rem !default\n$size-6: 1rem !default\n$size-7: 0.75rem !default\n\n$weight-light: 300 !default\n$weight-normal: 400 !default\n$weight-medium: 500 !default\n$weight-semibold: 600 !default\n$weight-bold: 700 !default\n\n// Spacing\n\n$block-spacing: 1.5rem !default\n\n// Responsiveness\n\n// The container horizontal gap, which acts as the offset for breakpoints\n$gap: 32px !default\n// 960, 1152, and 1344 have been chosen because they are divisible by both 12 and 16\n$tablet: 769px !default\n// 960px container + 4rem\n$desktop: 960px + (2 * $gap) !default\n// 1152px container + 4rem\n$widescreen: 1152px + (2 * $gap) !default\n$widescreen-enabled: true !default\n// 1344px container + 4rem\n$fullhd: 1344px + (2 * $gap) !default\n$fullhd-enabled: true !default\n$breakpoints: (\"mobile\": (\"until\": $tablet), \"tablet\": (\"from\": $tablet), \"tablet-only\": (\"from\": $tablet, \"until\": $desktop), \"touch\": (\"from\": $desktop), \"desktop\": (\"from\": $desktop), \"desktop-only\": (\"from\": $desktop, \"until\": $widescreen), \"until-widescreen\": (\"until\": $widescreen), \"widescreen\": (\"from\": $widescreen), \"widescreen-only\": (\"from\": $widescreen, \"until\": $fullhd), \"until-fullhd\": (\"until\": $fullhd), \"fullhd\": (\"from\": $fullhd)) !default\n\n// Miscellaneous\n\n$easing: ease-out !default\n$radius-small: 2px !default\n$radius: 4px !default\n$radius-large: 6px !default\n$radius-rounded: 9999px !default\n$speed: 86ms !default\n\n// Flags\n\n$variable-columns: true !default\n$rtl: false !default\n","@import \"derived-variables\"\n\n=clearfix\n &::after\n clear: both\n content: \" \"\n display: table\n\n=center($width, $height: 0)\n position: absolute\n @if $height != 0\n left: calc(50% - (#{$width} * 0.5))\n top: calc(50% - (#{$height} * 0.5))\n @else\n left: calc(50% - (#{$width} * 0.5))\n top: calc(50% - (#{$width} * 0.5))\n\n=fa($size, $dimensions)\n display: inline-block\n font-size: $size\n height: $dimensions\n line-height: $dimensions\n text-align: center\n vertical-align: top\n width: $dimensions\n\n=hamburger($dimensions)\n -moz-appearance: none\n -webkit-appearance: none\n appearance: none\n background: none\n border: none\n cursor: pointer\n display: block\n height: $dimensions\n position: relative\n width: $dimensions\n span\n background-color: currentColor\n display: block\n height: 1px\n left: calc(50% - 8px)\n position: absolute\n transform-origin: center\n transition-duration: $speed\n transition-property: background-color, opacity, transform\n transition-timing-function: $easing\n width: 16px\n &:nth-child(1)\n top: calc(50% - 6px)\n &:nth-child(2)\n top: calc(50% - 1px)\n &:nth-child(3)\n top: calc(50% + 4px)\n &:hover\n background-color: bulmaRgba(black, 0.05)\n // Modifers\n &.is-active\n span\n &:nth-child(1)\n transform: translateY(5px) rotate(45deg)\n &:nth-child(2)\n opacity: 0\n &:nth-child(3)\n transform: translateY(-5px) rotate(-45deg)\n\n=overflow-touch\n -webkit-overflow-scrolling: touch\n\n=placeholder\n $placeholders: ':-moz' ':-webkit-input' '-moz' '-ms-input'\n @each $placeholder in $placeholders\n &:#{$placeholder}-placeholder\n @content\n\n=reset\n -moz-appearance: none\n -webkit-appearance: none\n appearance: none\n background: none\n border: none\n color: currentColor\n font-family: inherit\n font-size: 1em\n margin: 0\n padding: 0\n\n// Responsiveness\n\n=from($device)\n @media screen and (min-width: $device)\n @content\n\n=until($device)\n @media screen and (max-width: $device - 1px)\n @content\n\n=between($from, $until)\n @media screen and (min-width: $from) and (max-width: $until - 1px)\n @content\n\n=mobile\n @media screen and (max-width: $tablet - 1px)\n @content\n\n=tablet\n @media screen and (min-width: $tablet), print\n @content\n\n=tablet-only\n @media screen and (min-width: $tablet) and (max-width: $desktop - 1px)\n @content\n\n=touch\n @media screen and (max-width: $desktop - 1px)\n @content\n\n=desktop\n @media screen and (min-width: $desktop)\n @content\n\n=desktop-only\n @if $widescreen-enabled\n @media screen and (min-width: $desktop) and (max-width: $widescreen - 1px)\n @content\n\n=until-widescreen\n @if $widescreen-enabled\n @media screen and (max-width: $widescreen - 1px)\n @content\n\n=widescreen\n @if $widescreen-enabled\n @media screen and (min-width: $widescreen)\n @content\n\n=widescreen-only\n @if $widescreen-enabled and $fullhd-enabled\n @media screen and (min-width: $widescreen) and (max-width: $fullhd - 1px)\n @content\n\n=until-fullhd\n @if $fullhd-enabled\n @media screen and (max-width: $fullhd - 1px)\n @content\n\n=fullhd\n @if $fullhd-enabled\n @media screen and (min-width: $fullhd)\n @content\n\n=breakpoint($name)\n $breakpoint: map-get($breakpoints, $name)\n @if $breakpoint\n $from: map-get($breakpoint, \"from\")\n $until: map-get($breakpoint, \"until\")\n @if $from and $until\n +between($from, $until)\n @content\n @else if $from\n +from($from)\n @content\n @else if $until\n +until($until)\n @content\n\n=ltr\n @if not $rtl\n @content\n\n=rtl\n @if $rtl\n @content\n\n=ltr-property($property, $spacing, $right: true)\n $normal: if($right, \"right\", \"left\")\n $opposite: if($right, \"left\", \"right\")\n @if $rtl\n #{$property}-#{$opposite}: $spacing\n @else\n #{$property}-#{$normal}: $spacing\n\n=ltr-position($spacing, $right: true)\n $normal: if($right, \"right\", \"left\")\n $opposite: if($right, \"left\", \"right\")\n @if $rtl\n #{$opposite}: $spacing\n @else\n #{$normal}: $spacing\n\n// Placeholders\n\n=unselectable\n -webkit-touch-callout: none\n -webkit-user-select: none\n -moz-user-select: none\n -ms-user-select: none\n user-select: none\n\n=arrow($color: transparent)\n border: 3px solid $color\n border-radius: 2px\n border-right: 0\n border-top: 0\n content: \" \"\n display: block\n height: 0.625em\n margin-top: -0.4375em\n pointer-events: none\n position: absolute\n top: 50%\n transform: rotate(-45deg)\n transform-origin: center\n width: 0.625em\n\n=block($spacing: $block-spacing)\n &:not(:last-child)\n margin-bottom: $spacing\n\n=delete\n +unselectable\n -moz-appearance: none\n -webkit-appearance: none\n background-color: bulmaRgba($scheme-invert, 0.2)\n border: none\n border-radius: $radius-rounded\n cursor: pointer\n pointer-events: auto\n display: inline-block\n flex-grow: 0\n flex-shrink: 0\n font-size: 0\n height: 20px\n max-height: 20px\n max-width: 20px\n min-height: 20px\n min-width: 20px\n outline: none\n position: relative\n vertical-align: top\n width: 20px\n &::before,\n &::after\n background-color: $scheme-main\n content: \"\"\n display: block\n left: 50%\n position: absolute\n top: 50%\n transform: translateX(-50%) translateY(-50%) rotate(45deg)\n transform-origin: center center\n &::before\n height: 2px\n width: 50%\n &::after\n height: 50%\n width: 2px\n &:hover,\n &:focus\n background-color: bulmaRgba($scheme-invert, 0.3)\n &:active\n background-color: bulmaRgba($scheme-invert, 0.4)\n // Sizes\n &.is-small\n height: 16px\n max-height: 16px\n max-width: 16px\n min-height: 16px\n min-width: 16px\n width: 16px\n &.is-medium\n height: 24px\n max-height: 24px\n max-width: 24px\n min-height: 24px\n min-width: 24px\n width: 24px\n &.is-large\n height: 32px\n max-height: 32px\n max-width: 32px\n min-height: 32px\n min-width: 32px\n width: 32px\n\n=loader\n animation: spinAround 500ms infinite linear\n border: 2px solid $grey-lighter\n border-radius: $radius-rounded\n border-right-color: transparent\n border-top-color: transparent\n content: \"\"\n display: block\n height: 1em\n position: relative\n width: 1em\n\n=overlay($offset: 0)\n bottom: $offset\n left: $offset\n position: absolute\n right: $offset\n top: $offset\n","/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */\n// Blocks\nhtml,\nbody,\np,\nol,\nul,\nli,\ndl,\ndt,\ndd,\nblockquote,\nfigure,\nfieldset,\nlegend,\ntextarea,\npre,\niframe,\nhr,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6\n margin: 0\n padding: 0\n\n// Headings\nh1,\nh2,\nh3,\nh4,\nh5,\nh6\n font-size: 100%\n font-weight: normal\n\n// List\nul\n list-style: none\n\n// Form\nbutton,\ninput,\nselect,\ntextarea\n margin: 0\n\n// Box sizing\nhtml\n box-sizing: border-box\n\n*\n &,\n &::before,\n &::after\n box-sizing: inherit\n\n// Media\nimg,\nvideo\n height: auto\n max-width: 100%\n\n// Iframe\niframe\n border: 0\n\n// Table\ntable\n border-collapse: collapse\n border-spacing: 0\n\ntd,\nth\n padding: 0\n &:not([align])\n text-align: inherit\n","@import \"../utilities/mixins\"\n\n$body-background-color: $scheme-main !default\n$body-size: 16px !default\n$body-min-width: 300px !default\n$body-rendering: optimizeLegibility !default\n$body-family: $family-primary !default\n$body-overflow-x: hidden !default\n$body-overflow-y: scroll !default\n\n$body-color: $text !default\n$body-font-size: 1em !default\n$body-weight: $weight-normal !default\n$body-line-height: 1.5 !default\n\n$code-family: $family-code !default\n$code-padding: 0.25em 0.5em 0.25em !default\n$code-weight: normal !default\n$code-size: 0.875em !default\n\n$small-font-size: 0.875em !default\n\n$hr-background-color: $background !default\n$hr-height: 2px !default\n$hr-margin: 1.5rem 0 !default\n\n$strong-color: $text-strong !default\n$strong-weight: $weight-bold !default\n\n$pre-font-size: 0.875em !default\n$pre-padding: 1.25rem 1.5rem !default\n$pre-code-font-size: 1em !default\n\nhtml\n background-color: $body-background-color\n font-size: $body-size\n -moz-osx-font-smoothing: grayscale\n -webkit-font-smoothing: antialiased\n min-width: $body-min-width\n overflow-x: $body-overflow-x\n overflow-y: $body-overflow-y\n text-rendering: $body-rendering\n text-size-adjust: 100%\n\narticle,\naside,\nfigure,\nfooter,\nheader,\nhgroup,\nsection\n display: block\n\nbody,\nbutton,\ninput,\noptgroup,\nselect,\ntextarea\n font-family: $body-family\n\ncode,\npre\n -moz-osx-font-smoothing: auto\n -webkit-font-smoothing: auto\n font-family: $code-family\n\nbody\n color: $body-color\n font-size: $body-font-size\n font-weight: $body-weight\n line-height: $body-line-height\n\n// Inline\n\na\n color: $link\n cursor: pointer\n text-decoration: none\n strong\n color: currentColor\n &:hover\n color: $link-hover\n\ncode\n background-color: $code-background\n color: $code\n font-size: $code-size\n font-weight: $code-weight\n padding: $code-padding\n\nhr\n background-color: $hr-background-color\n border: none\n display: block\n height: $hr-height\n margin: $hr-margin\n\nimg\n height: auto\n max-width: 100%\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"]\n vertical-align: baseline\n\nsmall\n font-size: $small-font-size\n\nspan\n font-style: inherit\n font-weight: inherit\n\nstrong\n color: $strong-color\n font-weight: $strong-weight\n\n// Block\n\nfieldset\n border: none\n\npre\n +overflow-touch\n background-color: $pre-background\n color: $pre\n font-size: $pre-font-size\n overflow-x: auto\n padding: $pre-padding\n white-space: pre\n word-wrap: normal\n code\n background-color: transparent\n color: currentColor\n font-size: $pre-code-font-size\n padding: 0\n\ntable\n td,\n th\n vertical-align: top\n &:not([align])\n text-align: inherit\n th\n color: $text-strong\n","@import \"initial-variables\"\n@import \"functions\"\n\n$primary: $turquoise !default\n\n$info: $cyan !default\n$success: $green !default\n$warning: $yellow !default\n$danger: $red !default\n\n$light: $white-ter !default\n$dark: $grey-darker !default\n\n// Invert colors\n\n$orange-invert: findColorInvert($orange) !default\n$yellow-invert: findColorInvert($yellow) !default\n$green-invert: findColorInvert($green) !default\n$turquoise-invert: findColorInvert($turquoise) !default\n$cyan-invert: findColorInvert($cyan) !default\n$blue-invert: findColorInvert($blue) !default\n$purple-invert: findColorInvert($purple) !default\n$red-invert: findColorInvert($red) !default\n\n$primary-invert: findColorInvert($primary) !default\n$primary-light: findLightColor($primary) !default\n$primary-dark: findDarkColor($primary) !default\n$info-invert: findColorInvert($info) !default\n$info-light: findLightColor($info) !default\n$info-dark: findDarkColor($info) !default\n$success-invert: findColorInvert($success) !default\n$success-light: findLightColor($success) !default\n$success-dark: findDarkColor($success) !default\n$warning-invert: findColorInvert($warning) !default\n$warning-light: findLightColor($warning) !default\n$warning-dark: findDarkColor($warning) !default\n$danger-invert: findColorInvert($danger) !default\n$danger-light: findLightColor($danger) !default\n$danger-dark: findDarkColor($danger) !default\n$light-invert: findColorInvert($light) !default\n$dark-invert: findColorInvert($dark) !default\n\n// General colors\n\n$scheme-main: $white !default\n$scheme-main-bis: $white-bis !default\n$scheme-main-ter: $white-ter !default\n$scheme-invert: $black !default\n$scheme-invert-bis: $black-bis !default\n$scheme-invert-ter: $black-ter !default\n\n$background: $white-ter !default\n\n$border: $grey-lighter !default\n$border-hover: $grey-light !default\n$border-light: $grey-lightest !default\n$border-light-hover: $grey-light !default\n\n// Text colors\n\n$text: $grey-dark !default\n$text-invert: findColorInvert($text) !default\n$text-light: $grey !default\n$text-strong: $grey-darker !default\n\n// Code colors\n\n$code: darken($red, 15%) !default\n$code-background: $background !default\n\n$pre: $text !default\n$pre-background: $background !default\n\n// Link colors\n\n$link: $blue !default\n$link-invert: findColorInvert($link) !default\n$link-light: findLightColor($link) !default\n$link-dark: findDarkColor($link) !default\n$link-visited: $purple !default\n\n$link-hover: $grey-darker !default\n$link-hover-border: $grey-light !default\n\n$link-focus: $grey-darker !default\n$link-focus-border: $blue !default\n\n$link-active: $grey-darker !default\n$link-active-border: $grey-dark !default\n\n// Typography\n\n$family-primary: $family-sans-serif !default\n$family-secondary: $family-sans-serif !default\n$family-code: $family-monospace !default\n\n$size-small: $size-7 !default\n$size-normal: $size-6 !default\n$size-medium: $size-5 !default\n$size-large: $size-4 !default\n\n// Effects\n\n$shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default\n\n// Lists and maps\n$custom-colors: null !default\n$custom-shades: null !default\n\n$colors: mergeColorMaps((\"white\": ($white, $black), \"black\": ($black, $white), \"light\": ($light, $light-invert), \"dark\": ($dark, $dark-invert), \"primary\": ($primary, $primary-invert, $primary-light, $primary-dark), \"link\": ($link, $link-invert, $link-light, $link-dark), \"info\": ($info, $info-invert, $info-light, $info-dark), \"success\": ($success, $success-invert, $success-light, $success-dark), \"warning\": ($warning, $warning-invert, $warning-light, $warning-dark), \"danger\": ($danger, $danger-invert, $danger-light, $danger-dark)), $custom-colors) !default\n\n$shades: mergeColorMaps((\"black-bis\": $black-bis, \"black-ter\": $black-ter, \"grey-darker\": $grey-darker, \"grey-dark\": $grey-dark, \"grey\": $grey, \"grey-light\": $grey-light, \"grey-lighter\": $grey-lighter, \"white-ter\": $white-ter, \"white-bis\": $white-bis), $custom-shades) !default\n\n$sizes: $size-1 $size-2 $size-3 $size-4 $size-5 $size-6 $size-7 !default\n","@keyframes spinAround\n from\n transform: rotate(0deg)\n to\n transform: rotate(359deg)\n","@import \"../utilities/mixins\"\n\n$breadcrumb-item-color: $link !default\n$breadcrumb-item-hover-color: $link-hover !default\n$breadcrumb-item-active-color: $text-strong !default\n\n$breadcrumb-item-padding-vertical: 0 !default\n$breadcrumb-item-padding-horizontal: 0.75em !default\n\n$breadcrumb-item-separator-color: $border-hover !default\n\n.breadcrumb\n @extend %block\n @extend %unselectable\n font-size: $size-normal\n white-space: nowrap\n a\n align-items: center\n color: $breadcrumb-item-color\n display: flex\n justify-content: center\n padding: $breadcrumb-item-padding-vertical $breadcrumb-item-padding-horizontal\n &:hover\n color: $breadcrumb-item-hover-color\n li\n align-items: center\n display: flex\n &:first-child a\n +ltr-property(\"padding\", 0, false)\n &.is-active\n a\n color: $breadcrumb-item-active-color\n cursor: default\n pointer-events: none\n & + li::before\n color: $breadcrumb-item-separator-color\n content: \"\\0002f\"\n ul,\n ol\n align-items: flex-start\n display: flex\n flex-wrap: wrap\n justify-content: flex-start\n .icon\n &:first-child\n +ltr-property(\"margin\", 0.5em)\n &:last-child\n +ltr-property(\"margin\", 0.5em, false)\n // Alignment\n &.is-centered\n ol,\n ul\n justify-content: center\n &.is-right\n ol,\n ul\n justify-content: flex-end\n // Sizes\n &.is-small\n font-size: $size-small\n &.is-medium\n font-size: $size-medium\n &.is-large\n font-size: $size-large\n // Styles\n &.has-arrow-separator\n li + li::before\n content: \"\\02192\"\n &.has-bullet-separator\n li + li::before\n content: \"\\02022\"\n &.has-dot-separator\n li + li::before\n content: \"\\000b7\"\n &.has-succeeds-separator\n li + li::before\n content: \"\\0227B\"\n","@import \"../utilities/mixins\"\n\n$card-color: $text !default\n$card-background-color: $scheme-main !default\n$card-shadow: $shadow !default\n$card-radius: 0.25rem !default\n\n$card-header-background-color: transparent !default\n$card-header-color: $text-strong !default\n$card-header-padding: 0.75rem 1rem !default\n$card-header-shadow: 0 0.125em 0.25em rgba($scheme-invert, 0.1) !default\n$card-header-weight: $weight-bold !default\n\n$card-content-background-color: transparent !default\n$card-content-padding: 1.5rem !default\n\n$card-footer-background-color: transparent !default\n$card-footer-border-top: 1px solid $border-light !default\n$card-footer-padding: 0.75rem !default\n\n$card-media-margin: $block-spacing !default\n\n.card\n background-color: $card-background-color\n border-radius: $card-radius\n box-shadow: $card-shadow\n color: $card-color\n max-width: 100%\n position: relative\n\n%card-item\n &:first-child\n border-top-left-radius: $card-radius\n border-top-right-radius: $card-radius\n &:last-child\n border-bottom-left-radius: $card-radius\n border-bottom-right-radius: $card-radius\n\n.card-header\n @extend %card-item\n background-color: $card-header-background-color\n align-items: stretch\n box-shadow: $card-header-shadow\n display: flex\n\n.card-header-title\n align-items: center\n color: $card-header-color\n display: flex\n flex-grow: 1\n font-weight: $card-header-weight\n padding: $card-header-padding\n &.is-centered\n justify-content: center\n\n.card-header-icon\n +reset\n align-items: center\n cursor: pointer\n display: flex\n justify-content: center\n padding: $card-header-padding\n\n.card-image\n display: block\n position: relative\n &:first-child\n img\n border-top-left-radius: $card-radius\n border-top-right-radius: $card-radius\n &:last-child\n img\n border-bottom-left-radius: $card-radius\n border-bottom-right-radius: $card-radius\n\n.card-content\n @extend %card-item\n background-color: $card-content-background-color\n padding: $card-content-padding\n\n.card-footer\n @extend %card-item\n background-color: $card-footer-background-color\n border-top: $card-footer-border-top\n align-items: stretch\n display: flex\n\n.card-footer-item\n align-items: center\n display: flex\n flex-basis: 0\n flex-grow: 1\n flex-shrink: 0\n justify-content: center\n padding: $card-footer-padding\n &:not(:last-child)\n +ltr-property(\"border\", $card-footer-border-top)\n\n// Combinations\n\n.card\n .media:not(:last-child)\n margin-bottom: $card-media-margin\n","@import \"../utilities/mixins\"\n\n$dropdown-menu-min-width: 12rem !default\n\n$dropdown-content-background-color: $scheme-main !default\n$dropdown-content-arrow: $link !default\n$dropdown-content-offset: 4px !default\n$dropdown-content-padding-bottom: 0.5rem !default\n$dropdown-content-padding-top: 0.5rem !default\n$dropdown-content-radius: $radius !default\n$dropdown-content-shadow: $shadow !default\n$dropdown-content-z: 20 !default\n\n$dropdown-item-color: $text !default\n$dropdown-item-hover-color: $scheme-invert !default\n$dropdown-item-hover-background-color: $background !default\n$dropdown-item-active-color: $link-invert !default\n$dropdown-item-active-background-color: $link !default\n\n$dropdown-divider-background-color: $border-light !default\n\n.dropdown\n display: inline-flex\n position: relative\n vertical-align: top\n &.is-active,\n &.is-hoverable:hover\n .dropdown-menu\n display: block\n &.is-right\n .dropdown-menu\n left: auto\n right: 0\n &.is-up\n .dropdown-menu\n bottom: 100%\n padding-bottom: $dropdown-content-offset\n padding-top: initial\n top: auto\n\n.dropdown-menu\n display: none\n +ltr-position(0, false)\n min-width: $dropdown-menu-min-width\n padding-top: $dropdown-content-offset\n position: absolute\n top: 100%\n z-index: $dropdown-content-z\n\n.dropdown-content\n background-color: $dropdown-content-background-color\n border-radius: $dropdown-content-radius\n box-shadow: $dropdown-content-shadow\n padding-bottom: $dropdown-content-padding-bottom\n padding-top: $dropdown-content-padding-top\n\n.dropdown-item\n color: $dropdown-item-color\n display: block\n font-size: 0.875rem\n line-height: 1.5\n padding: 0.375rem 1rem\n position: relative\n\na.dropdown-item,\nbutton.dropdown-item\n +ltr-property(\"padding\", 3rem)\n text-align: inherit\n white-space: nowrap\n width: 100%\n &:hover\n background-color: $dropdown-item-hover-background-color\n color: $dropdown-item-hover-color\n &.is-active\n background-color: $dropdown-item-active-background-color\n color: $dropdown-item-active-color\n\n.dropdown-divider\n background-color: $dropdown-divider-background-color\n border: none\n display: block\n height: 1px\n margin: 0.5rem 0\n","@import \"../utilities/mixins\"\n\n$level-item-spacing: ($block-spacing * 0.5) !default\n\n.level\n @extend %block\n align-items: center\n justify-content: space-between\n code\n border-radius: $radius\n img\n display: inline-block\n vertical-align: top\n // Modifiers\n &.is-mobile\n display: flex\n .level-left,\n .level-right\n display: flex\n .level-left + .level-right\n margin-top: 0\n .level-item\n &:not(:last-child)\n margin-bottom: 0\n +ltr-property(\"margin\", $level-item-spacing)\n &:not(.is-narrow)\n flex-grow: 1\n // Responsiveness\n +tablet\n display: flex\n & > .level-item\n &:not(.is-narrow)\n flex-grow: 1\n\n.level-item\n align-items: center\n display: flex\n flex-basis: auto\n flex-grow: 0\n flex-shrink: 0\n justify-content: center\n .title,\n .subtitle\n margin-bottom: 0\n // Responsiveness\n +mobile\n &:not(:last-child)\n margin-bottom: $level-item-spacing\n\n.level-left,\n.level-right\n flex-basis: auto\n flex-grow: 0\n flex-shrink: 0\n .level-item\n // Modifiers\n &.is-flexible\n flex-grow: 1\n // Responsiveness\n +tablet\n &:not(:last-child)\n +ltr-property(\"margin\", $level-item-spacing)\n\n.level-left\n align-items: center\n justify-content: flex-start\n // Responsiveness\n +mobile\n & + .level-right\n margin-top: 1.5rem\n +tablet\n display: flex\n\n.level-right\n align-items: center\n justify-content: flex-end\n // Responsiveness\n +tablet\n display: flex\n","@import \"../utilities/mixins\"\n\n$media-border-color: bulmaRgba($border, 0.5) !default\n$media-border-size: 1px !default\n$media-spacing: 1rem !default\n$media-spacing-large: 1.5rem !default\n$media-content-spacing: 0.75rem !default\n$media-level-1-spacing: 0.75rem !default\n$media-level-1-content-spacing: 0.5rem !default\n$media-level-2-spacing: 0.5rem !default\n\n.media\n align-items: flex-start\n display: flex\n text-align: inherit\n .content:not(:last-child)\n margin-bottom: $media-content-spacing\n .media\n border-top: $media-border-size solid $media-border-color\n display: flex\n padding-top: $media-level-1-spacing\n .content:not(:last-child),\n .control:not(:last-child)\n margin-bottom: $media-level-1-content-spacing\n .media\n padding-top: $media-level-2-spacing\n & + .media\n margin-top: $media-level-2-spacing\n & + .media\n border-top: $media-border-size solid $media-border-color\n margin-top: $media-spacing\n padding-top: $media-spacing\n // Sizes\n &.is-large\n & + .media\n margin-top: $media-spacing-large\n padding-top: $media-spacing-large\n\n.media-left,\n.media-right\n flex-basis: auto\n flex-grow: 0\n flex-shrink: 0\n\n.media-left\n +ltr-property(\"margin\", $media-spacing)\n\n.media-right\n +ltr-property(\"margin\", $media-spacing, false)\n\n.media-content\n flex-basis: auto\n flex-grow: 1\n flex-shrink: 1\n text-align: inherit\n\n+mobile\n .media-content\n overflow-x: auto\n","@import \"../utilities/mixins\"\n\n$menu-item-color: $text !default\n$menu-item-radius: $radius-small !default\n$menu-item-hover-color: $text-strong !default\n$menu-item-hover-background-color: $background !default\n$menu-item-active-color: $link-invert !default\n$menu-item-active-background-color: $link !default\n\n$menu-list-border-left: 1px solid $border !default\n$menu-list-line-height: 1.25 !default\n$menu-list-link-padding: 0.5em 0.75em !default\n$menu-nested-list-margin: 0.75em !default\n$menu-nested-list-padding-left: 0.75em !default\n\n$menu-label-color: $text-light !default\n$menu-label-font-size: 0.75em !default\n$menu-label-letter-spacing: 0.1em !default\n$menu-label-spacing: 1em !default\n\n.menu\n font-size: $size-normal\n // Sizes\n &.is-small\n font-size: $size-small\n &.is-medium\n font-size: $size-medium\n &.is-large\n font-size: $size-large\n\n.menu-list\n line-height: $menu-list-line-height\n a\n border-radius: $menu-item-radius\n color: $menu-item-color\n display: block\n padding: $menu-list-link-padding\n &:hover\n background-color: $menu-item-hover-background-color\n color: $menu-item-hover-color\n // Modifiers\n &.is-active\n background-color: $menu-item-active-background-color\n color: $menu-item-active-color\n li\n ul\n +ltr-property(\"border\", $menu-list-border-left, false)\n margin: $menu-nested-list-margin\n +ltr-property(\"padding\", $menu-nested-list-padding-left, false)\n\n.menu-label\n color: $menu-label-color\n font-size: $menu-label-font-size\n letter-spacing: $menu-label-letter-spacing\n text-transform: uppercase\n &:not(:first-child)\n margin-top: $menu-label-spacing\n &:not(:last-child)\n margin-bottom: $menu-label-spacing\n","@import \"../utilities/mixins\"\n\n$message-background-color: $background !default\n$message-radius: $radius !default\n\n$message-header-background-color: $text !default\n$message-header-color: $text-invert !default\n$message-header-weight: $weight-bold !default\n$message-header-padding: 0.75em 1em !default\n$message-header-radius: $radius !default\n\n$message-body-border-color: $border !default\n$message-body-border-width: 0 0 0 4px !default\n$message-body-color: $text !default\n$message-body-padding: 1.25em 1.5em !default\n$message-body-radius: $radius !default\n\n$message-body-pre-background-color: $scheme-main !default\n$message-body-pre-code-background-color: transparent !default\n\n$message-header-body-border-width: 0 !default\n$message-colors: $colors !default\n\n.message\n @extend %block\n background-color: $message-background-color\n border-radius: $message-radius\n font-size: $size-normal\n strong\n color: currentColor\n a:not(.button):not(.tag):not(.dropdown-item)\n color: currentColor\n text-decoration: underline\n // Sizes\n &.is-small\n font-size: $size-small\n &.is-medium\n font-size: $size-medium\n &.is-large\n font-size: $size-large\n // Colors\n @each $name, $components in $message-colors\n $color: nth($components, 1)\n $color-invert: nth($components, 2)\n $color-light: null\n $color-dark: null\n\n @if length($components) >= 3\n $color-light: nth($components, 3)\n @if length($components) >= 4\n $color-dark: nth($components, 4)\n @else\n $color-luminance: colorLuminance($color)\n $darken-percentage: $color-luminance * 70%\n $desaturate-percentage: $color-luminance * 30%\n $color-dark: desaturate(darken($color, $darken-percentage), $desaturate-percentage)\n @else\n $color-lightning: max((100% - lightness($color)) - 2%, 0%)\n $color-light: lighten($color, $color-lightning)\n\n &.is-#{$name}\n background-color: $color-light\n .message-header\n background-color: $color\n color: $color-invert\n .message-body\n border-color: $color\n color: $color-dark\n\n.message-header\n align-items: center\n background-color: $message-header-background-color\n border-radius: $message-header-radius $message-header-radius 0 0\n color: $message-header-color\n display: flex\n font-weight: $message-header-weight\n justify-content: space-between\n line-height: 1.25\n padding: $message-header-padding\n position: relative\n .delete\n flex-grow: 0\n flex-shrink: 0\n +ltr-property(\"margin\", 0.75em, false)\n & + .message-body\n border-width: $message-header-body-border-width\n border-top-left-radius: 0\n border-top-right-radius: 0\n\n.message-body\n border-color: $message-body-border-color\n border-radius: $message-body-radius\n border-style: solid\n border-width: $message-body-border-width\n color: $message-body-color\n padding: $message-body-padding\n code,\n pre\n background-color: $message-body-pre-background-color\n pre code\n background-color: $message-body-pre-code-background-color\n","@import \"../utilities/mixins\"\n\n$modal-z: 40 !default\n\n$modal-background-background-color: bulmaRgba($scheme-invert, 0.86) !default\n\n$modal-content-width: 640px !default\n$modal-content-margin-mobile: 20px !default\n$modal-content-spacing-mobile: 160px !default\n$modal-content-spacing-tablet: 40px !default\n\n$modal-close-dimensions: 40px !default\n$modal-close-right: 20px !default\n$modal-close-top: 20px !default\n\n$modal-card-spacing: 40px !default\n\n$modal-card-head-background-color: $background !default\n$modal-card-head-border-bottom: 1px solid $border !default\n$modal-card-head-padding: 20px !default\n$modal-card-head-radius: $radius-large !default\n\n$modal-card-title-color: $text-strong !default\n$modal-card-title-line-height: 1 !default\n$modal-card-title-size: $size-4 !default\n\n$modal-card-foot-radius: $radius-large !default\n$modal-card-foot-border-top: 1px solid $border !default\n\n$modal-card-body-background-color: $scheme-main !default\n$modal-card-body-padding: 20px !default\n\n$modal-breakpoint: $tablet !default\n\n.modal\n @extend %overlay\n align-items: center\n display: none\n flex-direction: column\n justify-content: center\n overflow: hidden\n position: fixed\n z-index: $modal-z\n // Modifiers\n &.is-active\n display: flex\n\n.modal-background\n @extend %overlay\n background-color: $modal-background-background-color\n\n.modal-content,\n.modal-card\n margin: 0 $modal-content-margin-mobile\n max-height: calc(100vh - #{$modal-content-spacing-mobile})\n overflow: auto\n position: relative\n width: 100%\n // Responsiveness\n +from($modal-breakpoint)\n margin: 0 auto\n max-height: calc(100vh - #{$modal-content-spacing-tablet})\n width: $modal-content-width\n\n.modal-close\n @extend %delete\n background: none\n height: $modal-close-dimensions\n position: fixed\n +ltr-position($modal-close-right)\n top: $modal-close-top\n width: $modal-close-dimensions\n\n.modal-card\n display: flex\n flex-direction: column\n max-height: calc(100vh - #{$modal-card-spacing})\n overflow: hidden\n -ms-overflow-y: visible\n\n.modal-card-head,\n.modal-card-foot\n align-items: center\n background-color: $modal-card-head-background-color\n display: flex\n flex-shrink: 0\n justify-content: flex-start\n padding: $modal-card-head-padding\n position: relative\n\n.modal-card-head\n border-bottom: $modal-card-head-border-bottom\n border-top-left-radius: $modal-card-head-radius\n border-top-right-radius: $modal-card-head-radius\n\n.modal-card-title\n color: $modal-card-title-color\n flex-grow: 1\n flex-shrink: 0\n font-size: $modal-card-title-size\n line-height: $modal-card-title-line-height\n\n.modal-card-foot\n border-bottom-left-radius: $modal-card-foot-radius\n border-bottom-right-radius: $modal-card-foot-radius\n border-top: $modal-card-foot-border-top\n .button\n &:not(:last-child)\n +ltr-property(\"margin\", 0.5em)\n\n.modal-card-body\n +overflow-touch\n background-color: $modal-card-body-background-color\n flex-grow: 1\n flex-shrink: 1\n overflow: auto\n padding: $modal-card-body-padding\n","@import \"../utilities/mixins\"\n\n$navbar-background-color: $scheme-main !default\n$navbar-box-shadow-size: 0 2px 0 0 !default\n$navbar-box-shadow-color: $background !default\n$navbar-height: 3.25rem !default\n$navbar-padding-vertical: 1rem !default\n$navbar-padding-horizontal: 2rem !default\n$navbar-z: 30 !default\n$navbar-fixed-z: 30 !default\n\n$navbar-item-color: $text !default\n$navbar-item-hover-color: $link !default\n$navbar-item-hover-background-color: $scheme-main-bis !default\n$navbar-item-active-color: $scheme-invert !default\n$navbar-item-active-background-color: transparent !default\n$navbar-item-img-max-height: 1.75rem !default\n\n$navbar-burger-color: $navbar-item-color !default\n\n$navbar-tab-hover-background-color: transparent !default\n$navbar-tab-hover-border-bottom-color: $link !default\n$navbar-tab-active-color: $link !default\n$navbar-tab-active-background-color: transparent !default\n$navbar-tab-active-border-bottom-color: $link !default\n$navbar-tab-active-border-bottom-style: solid !default\n$navbar-tab-active-border-bottom-width: 3px !default\n\n$navbar-dropdown-background-color: $scheme-main !default\n$navbar-dropdown-border-top: 2px solid $border !default\n$navbar-dropdown-offset: -4px !default\n$navbar-dropdown-arrow: $link !default\n$navbar-dropdown-radius: $radius-large !default\n$navbar-dropdown-z: 20 !default\n\n$navbar-dropdown-boxed-radius: $radius-large !default\n$navbar-dropdown-boxed-shadow: 0 8px 8px bulmaRgba($scheme-invert, 0.1), 0 0 0 1px bulmaRgba($scheme-invert, 0.1) !default\n\n$navbar-dropdown-item-hover-color: $scheme-invert !default\n$navbar-dropdown-item-hover-background-color: $background !default\n$navbar-dropdown-item-active-color: $link !default\n$navbar-dropdown-item-active-background-color: $background !default\n\n$navbar-divider-background-color: $background !default\n$navbar-divider-height: 2px !default\n\n$navbar-bottom-box-shadow-size: 0 -2px 0 0 !default\n\n$navbar-breakpoint: $desktop !default\n\n$navbar-colors: $colors !default\n\n=navbar-fixed\n left: 0\n position: fixed\n right: 0\n z-index: $navbar-fixed-z\n\n.navbar\n background-color: $navbar-background-color\n min-height: $navbar-height\n position: relative\n z-index: $navbar-z\n @each $name, $pair in $navbar-colors\n $color: nth($pair, 1)\n $color-invert: nth($pair, 2)\n &.is-#{$name}\n background-color: $color\n color: $color-invert\n .navbar-brand\n & > .navbar-item,\n .navbar-link\n color: $color-invert\n & > a.navbar-item,\n .navbar-link\n &:focus,\n &:hover,\n &.is-active\n background-color: bulmaDarken($color, 5%)\n color: $color-invert\n .navbar-link\n &::after\n border-color: $color-invert\n .navbar-burger\n color: $color-invert\n +from($navbar-breakpoint)\n .navbar-start,\n .navbar-end\n & > .navbar-item,\n .navbar-link\n color: $color-invert\n & > a.navbar-item,\n .navbar-link\n &:focus,\n &:hover,\n &.is-active\n background-color: bulmaDarken($color, 5%)\n color: $color-invert\n .navbar-link\n &::after\n border-color: $color-invert\n .navbar-item.has-dropdown:focus .navbar-link,\n .navbar-item.has-dropdown:hover .navbar-link,\n .navbar-item.has-dropdown.is-active .navbar-link\n background-color: bulmaDarken($color, 5%)\n color: $color-invert\n .navbar-dropdown\n a.navbar-item\n &.is-active\n background-color: $color\n color: $color-invert\n & > .container\n align-items: stretch\n display: flex\n min-height: $navbar-height\n width: 100%\n &.has-shadow\n box-shadow: $navbar-box-shadow-size $navbar-box-shadow-color\n &.is-fixed-bottom,\n &.is-fixed-top\n +navbar-fixed\n &.is-fixed-bottom\n bottom: 0\n &.has-shadow\n box-shadow: $navbar-bottom-box-shadow-size $navbar-box-shadow-color\n &.is-fixed-top\n top: 0\n\nhtml,\nbody\n &.has-navbar-fixed-top\n padding-top: $navbar-height\n &.has-navbar-fixed-bottom\n padding-bottom: $navbar-height\n\n.navbar-brand,\n.navbar-tabs\n align-items: stretch\n display: flex\n flex-shrink: 0\n min-height: $navbar-height\n\n.navbar-brand\n a.navbar-item\n &:focus,\n &:hover\n background-color: transparent\n\n.navbar-tabs\n +overflow-touch\n max-width: 100vw\n overflow-x: auto\n overflow-y: hidden\n\n.navbar-burger\n @extend %reset\n color: $navbar-burger-color\n +hamburger($navbar-height)\n +ltr-property(\"margin\", auto, false)\n\n.navbar-menu\n display: none\n\n.navbar-item,\n.navbar-link\n color: $navbar-item-color\n display: block\n line-height: 1.5\n padding: 0.5rem 0.75rem\n position: relative\n .icon\n &:only-child\n margin-left: -0.25rem\n margin-right: -0.25rem\n\na.navbar-item,\n.navbar-link\n cursor: pointer\n &:focus,\n &:focus-within,\n &:hover,\n &.is-active\n background-color: $navbar-item-hover-background-color\n color: $navbar-item-hover-color\n\n.navbar-item\n flex-grow: 0\n flex-shrink: 0\n img\n max-height: $navbar-item-img-max-height\n &.has-dropdown\n padding: 0\n &.is-expanded\n flex-grow: 1\n flex-shrink: 1\n &.is-tab\n border-bottom: 1px solid transparent\n min-height: $navbar-height\n padding-bottom: calc(0.5rem - 1px)\n &:focus,\n &:hover\n background-color: $navbar-tab-hover-background-color\n border-bottom-color: $navbar-tab-hover-border-bottom-color\n &.is-active\n background-color: $navbar-tab-active-background-color\n border-bottom-color: $navbar-tab-active-border-bottom-color\n border-bottom-style: $navbar-tab-active-border-bottom-style\n border-bottom-width: $navbar-tab-active-border-bottom-width\n color: $navbar-tab-active-color\n padding-bottom: calc(0.5rem - #{$navbar-tab-active-border-bottom-width})\n\n.navbar-content\n flex-grow: 1\n flex-shrink: 1\n\n.navbar-link:not(.is-arrowless)\n +ltr-property(\"padding\", 2.5em)\n &::after\n @extend %arrow\n border-color: $navbar-dropdown-arrow\n margin-top: -0.375em\n +ltr-position(1.125em)\n\n.navbar-dropdown\n font-size: 0.875rem\n padding-bottom: 0.5rem\n padding-top: 0.5rem\n .navbar-item\n padding-left: 1.5rem\n padding-right: 1.5rem\n\n.navbar-divider\n background-color: $navbar-divider-background-color\n border: none\n display: none\n height: $navbar-divider-height\n margin: 0.5rem 0\n\n+until($navbar-breakpoint)\n .navbar > .container\n display: block\n .navbar-brand,\n .navbar-tabs\n .navbar-item\n align-items: center\n display: flex\n .navbar-link\n &::after\n display: none\n .navbar-menu\n background-color: $navbar-background-color\n box-shadow: 0 8px 16px bulmaRgba($scheme-invert, 0.1)\n padding: 0.5rem 0\n &.is-active\n display: block\n // Fixed navbar\n .navbar\n &.is-fixed-bottom-touch,\n &.is-fixed-top-touch\n +navbar-fixed\n &.is-fixed-bottom-touch\n bottom: 0\n &.has-shadow\n box-shadow: 0 -2px 3px bulmaRgba($scheme-invert, 0.1)\n &.is-fixed-top-touch\n top: 0\n &.is-fixed-top,\n &.is-fixed-top-touch\n .navbar-menu\n +overflow-touch\n max-height: calc(100vh - #{$navbar-height})\n overflow: auto\n html,\n body\n &.has-navbar-fixed-top-touch\n padding-top: $navbar-height\n &.has-navbar-fixed-bottom-touch\n padding-bottom: $navbar-height\n\n+from($navbar-breakpoint)\n .navbar,\n .navbar-menu,\n .navbar-start,\n .navbar-end\n align-items: stretch\n display: flex\n .navbar\n min-height: $navbar-height\n &.is-spaced\n padding: $navbar-padding-vertical $navbar-padding-horizontal\n .navbar-start,\n .navbar-end\n align-items: center\n a.navbar-item,\n .navbar-link\n border-radius: $radius\n &.is-transparent\n a.navbar-item,\n .navbar-link\n &:focus,\n &:hover,\n &.is-active\n background-color: transparent !important\n .navbar-item.has-dropdown\n &.is-active,\n &.is-hoverable:focus,\n &.is-hoverable:focus-within,\n &.is-hoverable:hover\n .navbar-link\n background-color: transparent !important\n .navbar-dropdown\n a.navbar-item\n &:focus,\n &:hover\n background-color: $navbar-dropdown-item-hover-background-color\n color: $navbar-dropdown-item-hover-color\n &.is-active\n background-color: $navbar-dropdown-item-active-background-color\n color: $navbar-dropdown-item-active-color\n .navbar-burger\n display: none\n .navbar-item,\n .navbar-link\n align-items: center\n display: flex\n .navbar-item\n &.has-dropdown\n align-items: stretch\n &.has-dropdown-up\n .navbar-link::after\n transform: rotate(135deg) translate(0.25em, -0.25em)\n .navbar-dropdown\n border-bottom: $navbar-dropdown-border-top\n border-radius: $navbar-dropdown-radius $navbar-dropdown-radius 0 0\n border-top: none\n bottom: 100%\n box-shadow: 0 -8px 8px bulmaRgba($scheme-invert, 0.1)\n top: auto\n &.is-active,\n &.is-hoverable:focus,\n &.is-hoverable:focus-within,\n &.is-hoverable:hover\n .navbar-dropdown\n display: block\n .navbar.is-spaced &,\n &.is-boxed\n opacity: 1\n pointer-events: auto\n transform: translateY(0)\n .navbar-menu\n flex-grow: 1\n flex-shrink: 0\n .navbar-start\n justify-content: flex-start\n +ltr-property(\"margin\", auto)\n .navbar-end\n justify-content: flex-end\n +ltr-property(\"margin\", auto, false)\n .navbar-dropdown\n background-color: $navbar-dropdown-background-color\n border-bottom-left-radius: $navbar-dropdown-radius\n border-bottom-right-radius: $navbar-dropdown-radius\n border-top: $navbar-dropdown-border-top\n box-shadow: 0 8px 8px bulmaRgba($scheme-invert, 0.1)\n display: none\n font-size: 0.875rem\n +ltr-position(0, false)\n min-width: 100%\n position: absolute\n top: 100%\n z-index: $navbar-dropdown-z\n .navbar-item\n padding: 0.375rem 1rem\n white-space: nowrap\n a.navbar-item\n +ltr-property(\"padding\", 3rem)\n &:focus,\n &:hover\n background-color: $navbar-dropdown-item-hover-background-color\n color: $navbar-dropdown-item-hover-color\n &.is-active\n background-color: $navbar-dropdown-item-active-background-color\n color: $navbar-dropdown-item-active-color\n .navbar.is-spaced &,\n &.is-boxed\n border-radius: $navbar-dropdown-boxed-radius\n border-top: none\n box-shadow: $navbar-dropdown-boxed-shadow\n display: block\n opacity: 0\n pointer-events: none\n top: calc(100% + (#{$navbar-dropdown-offset}))\n transform: translateY(-5px)\n transition-duration: $speed\n transition-property: opacity, transform\n &.is-right\n left: auto\n right: 0\n .navbar-divider\n display: block\n .navbar > .container,\n .container > .navbar\n .navbar-brand\n +ltr-property(\"margin\", -.75rem, false)\n .navbar-menu\n +ltr-property(\"margin\", -.75rem)\n // Fixed navbar\n .navbar\n &.is-fixed-bottom-desktop,\n &.is-fixed-top-desktop\n +navbar-fixed\n &.is-fixed-bottom-desktop\n bottom: 0\n &.has-shadow\n box-shadow: 0 -2px 3px bulmaRgba($scheme-invert, 0.1)\n &.is-fixed-top-desktop\n top: 0\n html,\n body\n &.has-navbar-fixed-top-desktop\n padding-top: $navbar-height\n &.has-navbar-fixed-bottom-desktop\n padding-bottom: $navbar-height\n &.has-spaced-navbar-fixed-top\n padding-top: $navbar-height + ($navbar-padding-vertical * 2)\n &.has-spaced-navbar-fixed-bottom\n padding-bottom: $navbar-height + ($navbar-padding-vertical * 2)\n // Hover/Active states\n a.navbar-item,\n .navbar-link\n &.is-active\n color: $navbar-item-active-color\n &.is-active:not(:focus):not(:hover)\n background-color: $navbar-item-active-background-color\n .navbar-item.has-dropdown\n &:focus,\n &:hover,\n &.is-active\n .navbar-link\n background-color: $navbar-item-hover-background-color\n\n// Combination\n\n.hero\n &.is-fullheight-with-navbar\n min-height: calc(100vh - #{$navbar-height})\n","@import \"../utilities/controls\"\n@import \"../utilities/mixins\"\n\n$pagination-color: $text-strong !default\n$pagination-border-color: $border !default\n$pagination-margin: -0.25rem !default\n$pagination-min-width: $control-height !default\n\n$pagination-item-font-size: 1em !default\n$pagination-item-margin: 0.25rem !default\n$pagination-item-padding-left: 0.5em !default\n$pagination-item-padding-right: 0.5em !default\n\n$pagination-nav-padding-left: 0.75em !default\n$pagination-nav-padding-right: 0.75em !default\n\n$pagination-hover-color: $link-hover !default\n$pagination-hover-border-color: $link-hover-border !default\n\n$pagination-focus-color: $link-focus !default\n$pagination-focus-border-color: $link-focus-border !default\n\n$pagination-active-color: $link-active !default\n$pagination-active-border-color: $link-active-border !default\n\n$pagination-disabled-color: $text-light !default\n$pagination-disabled-background-color: $border !default\n$pagination-disabled-border-color: $border !default\n\n$pagination-current-color: $link-invert !default\n$pagination-current-background-color: $link !default\n$pagination-current-border-color: $link !default\n\n$pagination-ellipsis-color: $grey-light !default\n\n$pagination-shadow-inset: inset 0 1px 2px rgba($scheme-invert, 0.2) !default\n\n.pagination\n @extend %block\n font-size: $size-normal\n margin: $pagination-margin\n // Sizes\n &.is-small\n font-size: $size-small\n &.is-medium\n font-size: $size-medium\n &.is-large\n font-size: $size-large\n &.is-rounded\n .pagination-previous,\n .pagination-next\n padding-left: 1em\n padding-right: 1em\n border-radius: $radius-rounded\n .pagination-link\n border-radius: $radius-rounded\n\n.pagination,\n.pagination-list\n align-items: center\n display: flex\n justify-content: center\n text-align: center\n\n.pagination-previous,\n.pagination-next,\n.pagination-link,\n.pagination-ellipsis\n @extend %control\n @extend %unselectable\n font-size: $pagination-item-font-size\n justify-content: center\n margin: $pagination-item-margin\n padding-left: $pagination-item-padding-left\n padding-right: $pagination-item-padding-right\n text-align: center\n\n.pagination-previous,\n.pagination-next,\n.pagination-link\n border-color: $pagination-border-color\n color: $pagination-color\n min-width: $pagination-min-width\n &:hover\n border-color: $pagination-hover-border-color\n color: $pagination-hover-color\n &:focus\n border-color: $pagination-focus-border-color\n &:active\n box-shadow: $pagination-shadow-inset\n &[disabled],\n &.is-disabled\n background-color: $pagination-disabled-background-color\n border-color: $pagination-disabled-border-color\n box-shadow: none\n color: $pagination-disabled-color\n opacity: 0.5\n\n.pagination-previous,\n.pagination-next\n padding-left: $pagination-nav-padding-left\n padding-right: $pagination-nav-padding-right\n white-space: nowrap\n\n.pagination-link\n &.is-current\n background-color: $pagination-current-background-color\n border-color: $pagination-current-border-color\n color: $pagination-current-color\n\n.pagination-ellipsis\n color: $pagination-ellipsis-color\n pointer-events: none\n\n.pagination-list\n flex-wrap: wrap\n li\n list-style: none\n\n+mobile\n .pagination\n flex-wrap: wrap\n .pagination-previous,\n .pagination-next\n flex-grow: 1\n flex-shrink: 1\n .pagination-list\n li\n flex-grow: 1\n flex-shrink: 1\n\n+tablet\n .pagination-list\n flex-grow: 1\n flex-shrink: 1\n justify-content: flex-start\n order: 1\n .pagination-previous,\n .pagination-next,\n .pagination-link,\n .pagination-ellipsis\n margin-bottom: 0\n margin-top: 0\n .pagination-previous\n order: 2\n .pagination-next\n order: 3\n .pagination\n justify-content: space-between\n margin-bottom: 0\n margin-top: 0\n &.is-centered\n .pagination-previous\n order: 1\n .pagination-list\n justify-content: center\n order: 2\n .pagination-next\n order: 3\n &.is-right\n .pagination-previous\n order: 1\n .pagination-next\n order: 2\n .pagination-list\n justify-content: flex-end\n order: 3\n","@import \"../utilities/mixins\"\n\n$panel-margin: $block-spacing !default\n$panel-item-border: 1px solid $border-light !default\n$panel-radius: $radius-large !default\n$panel-shadow: $shadow !default\n\n$panel-heading-background-color: $border-light !default\n$panel-heading-color: $text-strong !default\n$panel-heading-line-height: 1.25 !default\n$panel-heading-padding: 0.75em 1em !default\n$panel-heading-radius: $radius !default\n$panel-heading-size: 1.25em !default\n$panel-heading-weight: $weight-bold !default\n\n$panel-tabs-font-size: 0.875em !default\n$panel-tab-border-bottom: 1px solid $border !default\n$panel-tab-active-border-bottom-color: $link-active-border !default\n$panel-tab-active-color: $link-active !default\n\n$panel-list-item-color: $text !default\n$panel-list-item-hover-color: $link !default\n\n$panel-block-color: $text-strong !default\n$panel-block-hover-background-color: $background !default\n$panel-block-active-border-left-color: $link !default\n$panel-block-active-color: $link-active !default\n$panel-block-active-icon-color: $link !default\n\n$panel-icon-color: $text-light !default\n$panel-colors: $colors !default\n\n.panel\n border-radius: $panel-radius\n box-shadow: $panel-shadow\n font-size: $size-normal\n &:not(:last-child)\n margin-bottom: $panel-margin\n // Colors\n @each $name, $components in $panel-colors\n $color: nth($components, 1)\n $color-invert: nth($components, 2)\n &.is-#{$name}\n .panel-heading\n background-color: $color\n color: $color-invert\n .panel-tabs a.is-active\n border-bottom-color: $color\n .panel-block.is-active .panel-icon\n color: $color\n\n.panel-tabs,\n.panel-block\n &:not(:last-child)\n border-bottom: $panel-item-border\n\n.panel-heading\n background-color: $panel-heading-background-color\n border-radius: $panel-radius $panel-radius 0 0\n color: $panel-heading-color\n font-size: $panel-heading-size\n font-weight: $panel-heading-weight\n line-height: $panel-heading-line-height\n padding: $panel-heading-padding\n\n.panel-tabs\n align-items: flex-end\n display: flex\n font-size: $panel-tabs-font-size\n justify-content: center\n a\n border-bottom: $panel-tab-border-bottom\n margin-bottom: -1px\n padding: 0.5em\n // Modifiers\n &.is-active\n border-bottom-color: $panel-tab-active-border-bottom-color\n color: $panel-tab-active-color\n\n.panel-list\n a\n color: $panel-list-item-color\n &:hover\n color: $panel-list-item-hover-color\n\n.panel-block\n align-items: center\n color: $panel-block-color\n display: flex\n justify-content: flex-start\n padding: 0.5em 0.75em\n input[type=\"checkbox\"]\n +ltr-property(\"margin\", 0.75em)\n & > .control\n flex-grow: 1\n flex-shrink: 1\n width: 100%\n &.is-wrapped\n flex-wrap: wrap\n &.is-active\n border-left-color: $panel-block-active-border-left-color\n color: $panel-block-active-color\n .panel-icon\n color: $panel-block-active-icon-color\n &:last-child\n border-bottom-left-radius: $panel-radius\n border-bottom-right-radius: $panel-radius\n\na.panel-block,\nlabel.panel-block\n cursor: pointer\n &:hover\n background-color: $panel-block-hover-background-color\n\n.panel-icon\n +fa(14px, 1em)\n color: $panel-icon-color\n +ltr-property(\"margin\", 0.75em)\n .fa\n font-size: inherit\n line-height: inherit\n","@import \"../utilities/mixins\"\n\n$tabs-border-bottom-color: $border !default\n$tabs-border-bottom-style: solid !default\n$tabs-border-bottom-width: 1px !default\n$tabs-link-color: $text !default\n$tabs-link-hover-border-bottom-color: $text-strong !default\n$tabs-link-hover-color: $text-strong !default\n$tabs-link-active-border-bottom-color: $link !default\n$tabs-link-active-color: $link !default\n$tabs-link-padding: 0.5em 1em !default\n\n$tabs-boxed-link-radius: $radius !default\n$tabs-boxed-link-hover-background-color: $background !default\n$tabs-boxed-link-hover-border-bottom-color: $border !default\n\n$tabs-boxed-link-active-background-color: $scheme-main !default\n$tabs-boxed-link-active-border-color: $border !default\n$tabs-boxed-link-active-border-bottom-color: transparent !default\n\n$tabs-toggle-link-border-color: $border !default\n$tabs-toggle-link-border-style: solid !default\n$tabs-toggle-link-border-width: 1px !default\n$tabs-toggle-link-hover-background-color: $background !default\n$tabs-toggle-link-hover-border-color: $border-hover !default\n$tabs-toggle-link-radius: $radius !default\n$tabs-toggle-link-active-background-color: $link !default\n$tabs-toggle-link-active-border-color: $link !default\n$tabs-toggle-link-active-color: $link-invert !default\n\n.tabs\n @extend %block\n +overflow-touch\n @extend %unselectable\n align-items: stretch\n display: flex\n font-size: $size-normal\n justify-content: space-between\n overflow: hidden\n overflow-x: auto\n white-space: nowrap\n a\n align-items: center\n border-bottom-color: $tabs-border-bottom-color\n border-bottom-style: $tabs-border-bottom-style\n border-bottom-width: $tabs-border-bottom-width\n color: $tabs-link-color\n display: flex\n justify-content: center\n margin-bottom: -#{$tabs-border-bottom-width}\n padding: $tabs-link-padding\n vertical-align: top\n &:hover\n border-bottom-color: $tabs-link-hover-border-bottom-color\n color: $tabs-link-hover-color\n li\n display: block\n &.is-active\n a\n border-bottom-color: $tabs-link-active-border-bottom-color\n color: $tabs-link-active-color\n ul\n align-items: center\n border-bottom-color: $tabs-border-bottom-color\n border-bottom-style: $tabs-border-bottom-style\n border-bottom-width: $tabs-border-bottom-width\n display: flex\n flex-grow: 1\n flex-shrink: 0\n justify-content: flex-start\n &.is-left\n padding-right: 0.75em\n &.is-center\n flex: none\n justify-content: center\n padding-left: 0.75em\n padding-right: 0.75em\n &.is-right\n justify-content: flex-end\n padding-left: 0.75em\n .icon\n &:first-child\n +ltr-property(\"margin\", 0.5em)\n &:last-child\n +ltr-property(\"margin\", 0.5em, false)\n // Alignment\n &.is-centered\n ul\n justify-content: center\n &.is-right\n ul\n justify-content: flex-end\n // Styles\n &.is-boxed\n a\n border: 1px solid transparent\n +ltr\n border-radius: $tabs-boxed-link-radius $tabs-boxed-link-radius 0 0\n +rtl\n border-radius: 0 0 $tabs-boxed-link-radius $tabs-boxed-link-radius\n &:hover\n background-color: $tabs-boxed-link-hover-background-color\n border-bottom-color: $tabs-boxed-link-hover-border-bottom-color\n li\n &.is-active\n a\n background-color: $tabs-boxed-link-active-background-color\n border-color: $tabs-boxed-link-active-border-color\n border-bottom-color: $tabs-boxed-link-active-border-bottom-color !important\n &.is-fullwidth\n li\n flex-grow: 1\n flex-shrink: 0\n &.is-toggle\n a\n border-color: $tabs-toggle-link-border-color\n border-style: $tabs-toggle-link-border-style\n border-width: $tabs-toggle-link-border-width\n margin-bottom: 0\n position: relative\n &:hover\n background-color: $tabs-toggle-link-hover-background-color\n border-color: $tabs-toggle-link-hover-border-color\n z-index: 2\n li\n & + li\n +ltr-property(\"margin\", -#{$tabs-toggle-link-border-width}, false)\n &:first-child a\n +ltr\n border-top-left-radius: $tabs-toggle-link-radius\n border-bottom-left-radius: $tabs-toggle-link-radius\n +rtl\n border-top-right-radius: $tabs-toggle-link-radius\n border-bottom-right-radius: $tabs-toggle-link-radius\n &:last-child a\n +ltr\n border-top-right-radius: $tabs-toggle-link-radius\n border-bottom-right-radius: $tabs-toggle-link-radius\n +rtl\n border-top-left-radius: $tabs-toggle-link-radius\n border-bottom-left-radius: $tabs-toggle-link-radius\n &.is-active\n a\n background-color: $tabs-toggle-link-active-background-color\n border-color: $tabs-toggle-link-active-border-color\n color: $tabs-toggle-link-active-color\n z-index: 1\n ul\n border-bottom: none\n &.is-toggle-rounded\n li\n &:first-child a\n +ltr\n border-bottom-left-radius: $radius-rounded\n border-top-left-radius: $radius-rounded\n padding-left: 1.25em\n +rtl\n border-bottom-right-radius: $radius-rounded\n border-top-right-radius: $radius-rounded\n padding-right: 1.25em\n &:last-child a\n +ltr\n border-bottom-right-radius: $radius-rounded\n border-top-right-radius: $radius-rounded\n padding-right: 1.25em\n +rtl\n border-bottom-left-radius: $radius-rounded\n border-top-left-radius: $radius-rounded\n padding-left: 1.25em\n // Sizes\n &.is-small\n font-size: $size-small\n &.is-medium\n font-size: $size-medium\n &.is-large\n font-size: $size-large\n","@import \"../utilities/mixins\"\n\n$box-color: $text !default\n$box-background-color: $scheme-main !default\n$box-radius: $radius-large !default\n$box-shadow: $shadow !default\n$box-padding: 1.25rem !default\n\n$box-link-hover-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0 0 1px $link !default\n$box-link-active-shadow: inset 0 1px 2px rgba($scheme-invert, 0.2), 0 0 0 1px $link !default\n\n.box\n @extend %block\n background-color: $box-background-color\n border-radius: $box-radius\n box-shadow: $box-shadow\n color: $box-color\n display: block\n padding: $box-padding\n\na.box\n &:hover,\n &:focus\n box-shadow: $box-link-hover-shadow\n &:active\n box-shadow: $box-link-active-shadow\n","@import \"../utilities/controls\"\n@import \"../utilities/mixins\"\n\n$button-color: $text-strong !default\n$button-background-color: $scheme-main !default\n$button-family: false !default\n\n$button-border-color: $border !default\n$button-border-width: $control-border-width !default\n\n$button-padding-vertical: calc(0.5em - #{$button-border-width}) !default\n$button-padding-horizontal: 1em !default\n\n$button-hover-color: $link-hover !default\n$button-hover-border-color: $link-hover-border !default\n\n$button-focus-color: $link-focus !default\n$button-focus-border-color: $link-focus-border !default\n$button-focus-box-shadow-size: 0 0 0 0.125em !default\n$button-focus-box-shadow-color: bulmaRgba($link, 0.25) !default\n\n$button-active-color: $link-active !default\n$button-active-border-color: $link-active-border !default\n\n$button-text-color: $text !default\n$button-text-decoration: underline !default\n$button-text-hover-background-color: $background !default\n$button-text-hover-color: $text-strong !default\n\n$button-ghost-background: none !default\n$button-ghost-border-color: transparent !default\n$button-ghost-color: $link !default\n$button-ghost-decoration: none !default\n$button-ghost-hover-color: $link !default\n$button-ghost-hover-decoration: underline !default\n\n$button-disabled-background-color: $scheme-main !default\n$button-disabled-border-color: $border !default\n$button-disabled-shadow: none !default\n$button-disabled-opacity: 0.5 !default\n\n$button-static-color: $text-light !default\n$button-static-background-color: $scheme-main-ter !default\n$button-static-border-color: $border !default\n\n$button-colors: $colors !default\n$button-responsive-sizes: (\"mobile\": (\"small\": ($size-small * 0.75), \"normal\": ($size-small * 0.875), \"medium\": $size-small, \"large\": $size-normal), \"tablet-only\": (\"small\": ($size-small * 0.875), \"normal\": ($size-small), \"medium\": $size-normal, \"large\": $size-medium)) !default\n\n// The button sizes use mixins so they can be used at different breakpoints\n=button-small\n &:not(.is-rounded)\n border-radius: $radius-small\n font-size: $size-small\n=button-normal\n font-size: $size-normal\n=button-medium\n font-size: $size-medium\n=button-large\n font-size: $size-large\n\n.button\n @extend %control\n @extend %unselectable\n background-color: $button-background-color\n border-color: $button-border-color\n border-width: $button-border-width\n color: $button-color\n cursor: pointer\n @if $button-family\n font-family: $button-family\n justify-content: center\n padding-bottom: $button-padding-vertical\n padding-left: $button-padding-horizontal\n padding-right: $button-padding-horizontal\n padding-top: $button-padding-vertical\n text-align: center\n white-space: nowrap\n strong\n color: inherit\n .icon\n &,\n &.is-small,\n &.is-medium,\n &.is-large\n height: 1.5em\n width: 1.5em\n &:first-child:not(:last-child)\n +ltr-property(\"margin\", calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width}), false)\n +ltr-property(\"margin\", $button-padding-horizontal * 0.25)\n &:last-child:not(:first-child)\n +ltr-property(\"margin\", $button-padding-horizontal * 0.25, false)\n +ltr-property(\"margin\", calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width}))\n &:first-child:last-child\n margin-left: calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width})\n margin-right: calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width})\n // States\n &:hover,\n &.is-hovered\n border-color: $button-hover-border-color\n color: $button-hover-color\n &:focus,\n &.is-focused\n border-color: $button-focus-border-color\n color: $button-focus-color\n &:not(:active)\n box-shadow: $button-focus-box-shadow-size $button-focus-box-shadow-color\n &:active,\n &.is-active\n border-color: $button-active-border-color\n color: $button-active-color\n // Colors\n &.is-text\n background-color: transparent\n border-color: transparent\n color: $button-text-color\n text-decoration: $button-text-decoration\n &:hover,\n &.is-hovered,\n &:focus,\n &.is-focused\n background-color: $button-text-hover-background-color\n color: $button-text-hover-color\n &:active,\n &.is-active\n background-color: bulmaDarken($button-text-hover-background-color, 5%)\n color: $button-text-hover-color\n &[disabled],\n fieldset[disabled] &\n background-color: transparent\n border-color: transparent\n box-shadow: none\n &.is-ghost\n background: $button-ghost-background\n border-color: $button-ghost-border-color\n color: $button-ghost-color\n text-decoration: $button-ghost-decoration\n &:hover,\n &.is-hovered\n color: $button-ghost-hover-color\n text-decoration: $button-ghost-hover-decoration\n @each $name, $pair in $button-colors\n $color: nth($pair, 1)\n $color-invert: nth($pair, 2)\n &.is-#{$name}\n background-color: $color\n border-color: transparent\n color: $color-invert\n &:hover,\n &.is-hovered\n background-color: bulmaDarken($color, 2.5%)\n border-color: transparent\n color: $color-invert\n &:focus,\n &.is-focused\n border-color: transparent\n color: $color-invert\n &:not(:active)\n box-shadow: $button-focus-box-shadow-size bulmaRgba($color, 0.25)\n &:active,\n &.is-active\n background-color: bulmaDarken($color, 5%)\n border-color: transparent\n color: $color-invert\n &[disabled],\n fieldset[disabled] &\n background-color: $color\n border-color: $color\n box-shadow: none\n &.is-inverted\n background-color: $color-invert\n color: $color\n &:hover,\n &.is-hovered\n background-color: bulmaDarken($color-invert, 5%)\n &[disabled],\n fieldset[disabled] &\n background-color: $color-invert\n border-color: transparent\n box-shadow: none\n color: $color\n &.is-loading\n &::after\n border-color: transparent transparent $color-invert $color-invert !important\n &.is-outlined\n background-color: transparent\n border-color: $color\n color: $color\n &:hover,\n &.is-hovered,\n &:focus,\n &.is-focused\n background-color: $color\n border-color: $color\n color: $color-invert\n &.is-loading\n &::after\n border-color: transparent transparent $color $color !important\n &:hover,\n &.is-hovered,\n &:focus,\n &.is-focused\n &::after\n border-color: transparent transparent $color-invert $color-invert !important\n &[disabled],\n fieldset[disabled] &\n background-color: transparent\n border-color: $color\n box-shadow: none\n color: $color\n &.is-inverted.is-outlined\n background-color: transparent\n border-color: $color-invert\n color: $color-invert\n &:hover,\n &.is-hovered,\n &:focus,\n &.is-focused\n background-color: $color-invert\n color: $color\n &.is-loading\n &:hover,\n &.is-hovered,\n &:focus,\n &.is-focused\n &::after\n border-color: transparent transparent $color $color !important\n &[disabled],\n fieldset[disabled] &\n background-color: transparent\n border-color: $color-invert\n box-shadow: none\n color: $color-invert\n // If light and dark colors are provided\n @if length($pair) >= 4\n $color-light: nth($pair, 3)\n $color-dark: nth($pair, 4)\n &.is-light\n background-color: $color-light\n color: $color-dark\n &:hover,\n &.is-hovered\n background-color: bulmaDarken($color-light, 2.5%)\n border-color: transparent\n color: $color-dark\n &:active,\n &.is-active\n background-color: bulmaDarken($color-light, 5%)\n border-color: transparent\n color: $color-dark\n // Sizes\n &.is-small\n +button-small\n &.is-normal\n +button-normal\n &.is-medium\n +button-medium\n &.is-large\n +button-large\n // Modifiers\n &[disabled],\n fieldset[disabled] &\n background-color: $button-disabled-background-color\n border-color: $button-disabled-border-color\n box-shadow: $button-disabled-shadow\n opacity: $button-disabled-opacity\n &.is-fullwidth\n display: flex\n width: 100%\n &.is-loading\n color: transparent !important\n pointer-events: none\n &::after\n @extend %loader\n +center(1em)\n position: absolute !important\n &.is-static\n background-color: $button-static-background-color\n border-color: $button-static-border-color\n color: $button-static-color\n box-shadow: none\n pointer-events: none\n &.is-rounded\n border-radius: $radius-rounded\n padding-left: calc(#{$button-padding-horizontal} + 0.25em)\n padding-right: calc(#{$button-padding-horizontal} + 0.25em)\n\n.buttons\n align-items: center\n display: flex\n flex-wrap: wrap\n justify-content: flex-start\n .button\n margin-bottom: 0.5rem\n &:not(:last-child):not(.is-fullwidth)\n +ltr-property(\"margin\", 0.5rem)\n &:last-child\n margin-bottom: -0.5rem\n &:not(:last-child)\n margin-bottom: 1rem\n // Sizes\n &.are-small\n .button:not(.is-normal):not(.is-medium):not(.is-large)\n +button-small\n &.are-medium\n .button:not(.is-small):not(.is-normal):not(.is-large)\n +button-medium\n &.are-large\n .button:not(.is-small):not(.is-normal):not(.is-medium)\n +button-large\n &.has-addons\n .button\n &:not(:first-child)\n border-bottom-left-radius: 0\n border-top-left-radius: 0\n &:not(:last-child)\n border-bottom-right-radius: 0\n border-top-right-radius: 0\n +ltr-property(\"margin\", -1px)\n &:last-child\n +ltr-property(\"margin\", 0)\n &:hover,\n &.is-hovered\n z-index: 2\n &:focus,\n &.is-focused,\n &:active,\n &.is-active,\n &.is-selected\n z-index: 3\n &:hover\n z-index: 4\n &.is-expanded\n flex-grow: 1\n flex-shrink: 1\n &.is-centered\n justify-content: center\n &:not(.has-addons)\n .button:not(.is-fullwidth)\n margin-left: 0.25rem\n margin-right: 0.25rem\n &.is-right\n justify-content: flex-end\n &:not(.has-addons)\n .button:not(.is-fullwidth)\n margin-left: 0.25rem\n margin-right: 0.25rem\n\n@each $bp-name, $bp-sizes in $button-responsive-sizes\n +breakpoint($bp-name)\n @each $size, $value in $bp-sizes\n @if $size != \"normal\"\n .button.is-responsive.is-#{$size}\n font-size: $value\n @else\n .button.is-responsive,\n .button.is-responsive.is-normal\n font-size: $value\n","@charset \"utf-8\";\n\n// Update Bulma's global variables\n\n$grey-dark: $grayD3;\n$grey-light: $grayL3;\n$primary: $orange;\n$link: $orange;\n$widescreen-enabled: true;\n$fullhd-enabled: false;\n\n// Update some of Bulma's component variables\n$body-background-color: $white;\n$button-active-border-color: $primary;\n$button-hover-border-color: $primary;\n$button-focus-border-color: $orangeD3;\n$control-border-width: 3px;\n$input-border-color: $grayD3;\n$input-shadow: none;\n\n// Import only what you need from Bulma\n@import \"../../node_modules/bulma/sass/utilities/_all.sass\";\n@import \"../../node_modules/bulma/sass/base/_all.sass\";\n@import \"../../node_modules/bulma/sass/components/_all.sass\";\n@import \"../../node_modules/bulma/sass/elements/_all.sass\";\n@import \"../../node_modules/bulma/sass/form/_all.sass\";\n@import \"../../node_modules/bulma/sass/grid/_all.sass\";\n@import \"../../node_modules/bulma/sass/helpers/_all.sass\";\n@import \"../../node_modules/bulma/sass/layout/_all.sass\";\n\n// reset .number to avoid conflicts with prism.js\ncode .number {\n align-items: normal;\n background-color: transparent;\n border-radius: 0;\n display: inline;\n height: auto;\n justify-content: normal;\n margin-right: auto;\n min-width: auto;\n padding: 0;\n text-align: left;\n vertical-align: baseline;\n font-size: inherit;\n}","@import \"../utilities/mixins\"\n\n$container-offset: (2 * $gap) !default\n$container-max-width: $fullhd !default\n\n.container\n flex-grow: 1\n margin: 0 auto\n position: relative\n width: auto\n &.is-fluid\n max-width: none !important\n padding-left: $gap\n padding-right: $gap\n width: 100%\n +desktop\n max-width: $desktop - $container-offset\n +until-widescreen\n &.is-widescreen:not(.is-max-desktop)\n max-width: min($widescreen, $container-max-width) - $container-offset\n +until-fullhd\n &.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen)\n max-width: min($fullhd, $container-max-width) - $container-offset\n +widescreen\n &:not(.is-max-desktop)\n max-width: min($widescreen, $container-max-width) - $container-offset\n +fullhd\n &:not(.is-max-desktop):not(.is-max-widescreen)\n max-width: min($fullhd, $container-max-width) - $container-offset\n","@import \"../utilities/mixins\"\n\n$content-heading-color: $text-strong !default\n$content-heading-weight: $weight-semibold !default\n$content-heading-line-height: 1.125 !default\n\n$content-block-margin-bottom: 1em !default\n\n$content-blockquote-background-color: $background !default\n$content-blockquote-border-left: 5px solid $border !default\n$content-blockquote-padding: 1.25em 1.5em !default\n\n$content-pre-padding: 1.25em 1.5em !default\n\n$content-table-cell-border: 1px solid $border !default\n$content-table-cell-border-width: 0 0 1px !default\n$content-table-cell-padding: 0.5em 0.75em !default\n$content-table-cell-heading-color: $text-strong !default\n$content-table-head-cell-border-width: 0 0 2px !default\n$content-table-head-cell-color: $text-strong !default\n$content-table-body-last-row-cell-border-bottom-width: 0 !default\n$content-table-foot-cell-border-width: 2px 0 0 !default\n$content-table-foot-cell-color: $text-strong !default\n\n.content\n @extend %block\n // Inline\n li + li\n margin-top: 0.25em\n // Block\n p,\n dl,\n ol,\n ul,\n blockquote,\n pre,\n table\n &:not(:last-child)\n margin-bottom: $content-block-margin-bottom\n h1,\n h2,\n h3,\n h4,\n h5,\n h6\n color: $content-heading-color\n font-weight: $content-heading-weight\n line-height: $content-heading-line-height\n h1\n font-size: 2em\n margin-bottom: 0.5em\n &:not(:first-child)\n margin-top: 1em\n h2\n font-size: 1.75em\n margin-bottom: 0.5714em\n &:not(:first-child)\n margin-top: 1.1428em\n h3\n font-size: 1.5em\n margin-bottom: 0.6666em\n &:not(:first-child)\n margin-top: 1.3333em\n h4\n font-size: 1.25em\n margin-bottom: 0.8em\n h5\n font-size: 1.125em\n margin-bottom: 0.8888em\n h6\n font-size: 1em\n margin-bottom: 1em\n blockquote\n background-color: $content-blockquote-background-color\n +ltr-property(\"border\", $content-blockquote-border-left, false)\n padding: $content-blockquote-padding\n ol\n list-style-position: outside\n +ltr-property(\"margin\", 2em, false)\n margin-top: 1em\n &:not([type])\n list-style-type: decimal\n &.is-lower-alpha\n list-style-type: lower-alpha\n &.is-lower-roman\n list-style-type: lower-roman\n &.is-upper-alpha\n list-style-type: upper-alpha\n &.is-upper-roman\n list-style-type: upper-roman\n ul\n list-style: disc outside\n +ltr-property(\"margin\", 2em, false)\n margin-top: 1em\n ul\n list-style-type: circle\n margin-top: 0.5em\n ul\n list-style-type: square\n dd\n +ltr-property(\"margin\", 2em, false)\n figure\n margin-left: 2em\n margin-right: 2em\n text-align: center\n &:not(:first-child)\n margin-top: 2em\n &:not(:last-child)\n margin-bottom: 2em\n img\n display: inline-block\n figcaption\n font-style: italic\n pre\n +overflow-touch\n overflow-x: auto\n padding: $content-pre-padding\n white-space: pre\n word-wrap: normal\n sup,\n sub\n font-size: 75%\n table\n width: 100%\n td,\n th\n border: $content-table-cell-border\n border-width: $content-table-cell-border-width\n padding: $content-table-cell-padding\n vertical-align: top\n th\n color: $content-table-cell-heading-color\n &:not([align])\n text-align: inherit\n thead\n td,\n th\n border-width: $content-table-head-cell-border-width\n color: $content-table-head-cell-color\n tfoot\n td,\n th\n border-width: $content-table-foot-cell-border-width\n color: $content-table-foot-cell-color\n tbody\n tr\n &:last-child\n td,\n th\n border-bottom-width: $content-table-body-last-row-cell-border-bottom-width\n .tabs\n li + li\n margin-top: 0\n // Sizes\n &.is-small\n font-size: $size-small\n &.is-normal\n font-size: $size-normal\n &.is-medium\n font-size: $size-medium\n &.is-large\n font-size: $size-large\n","$icon-dimensions: 1.5rem !default\n$icon-dimensions-small: 1rem !default\n$icon-dimensions-medium: 2rem !default\n$icon-dimensions-large: 3rem !default\n$icon-text-spacing: 0.25em !default\n\n.icon\n align-items: center\n display: inline-flex\n justify-content: center\n height: $icon-dimensions\n width: $icon-dimensions\n // Sizes\n &.is-small\n height: $icon-dimensions-small\n width: $icon-dimensions-small\n &.is-medium\n height: $icon-dimensions-medium\n width: $icon-dimensions-medium\n &.is-large\n height: $icon-dimensions-large\n width: $icon-dimensions-large\n\n.icon-text\n align-items: flex-start\n color: inherit\n display: inline-flex\n flex-wrap: wrap\n line-height: $icon-dimensions\n vertical-align: top\n .icon\n flex-grow: 0\n flex-shrink: 0\n &:not(:last-child)\n +ltr\n margin-right: $icon-text-spacing\n +rtl\n margin-left: $icon-text-spacing\n &:not(:first-child)\n +ltr\n margin-left: $icon-text-spacing\n +rtl\n margin-right: $icon-text-spacing\n\ndiv.icon-text\n display: flex\n","@import \"../utilities/mixins\"\n\n$dimensions: 16 24 32 48 64 96 128 !default\n\n.image\n display: block\n position: relative\n img\n display: block\n height: auto\n width: 100%\n &.is-rounded\n border-radius: $radius-rounded\n &.is-fullwidth\n width: 100%\n // Ratio\n &.is-square,\n &.is-1by1,\n &.is-5by4,\n &.is-4by3,\n &.is-3by2,\n &.is-5by3,\n &.is-16by9,\n &.is-2by1,\n &.is-3by1,\n &.is-4by5,\n &.is-3by4,\n &.is-2by3,\n &.is-3by5,\n &.is-9by16,\n &.is-1by2,\n &.is-1by3\n img,\n .has-ratio\n @extend %overlay\n height: 100%\n width: 100%\n &.is-square,\n &.is-1by1\n padding-top: 100%\n &.is-5by4\n padding-top: 80%\n &.is-4by3\n padding-top: 75%\n &.is-3by2\n padding-top: 66.6666%\n &.is-5by3\n padding-top: 60%\n &.is-16by9\n padding-top: 56.25%\n &.is-2by1\n padding-top: 50%\n &.is-3by1\n padding-top: 33.3333%\n &.is-4by5\n padding-top: 125%\n &.is-3by4\n padding-top: 133.3333%\n &.is-2by3\n padding-top: 150%\n &.is-3by5\n padding-top: 166.6666%\n &.is-9by16\n padding-top: 177.7777%\n &.is-1by2\n padding-top: 200%\n &.is-1by3\n padding-top: 300%\n // Sizes\n @each $dimension in $dimensions\n &.is-#{$dimension}x#{$dimension}\n height: $dimension * 1px\n width: $dimension * 1px\n","@import \"../utilities/mixins\"\n\n$notification-background-color: $background !default\n$notification-code-background-color: $scheme-main !default\n$notification-radius: $radius !default\n$notification-padding: 1.25rem 2.5rem 1.25rem 1.5rem !default\n$notification-padding-ltr: 1.25rem 2.5rem 1.25rem 1.5rem !default\n$notification-padding-rtl: 1.25rem 1.5rem 1.25rem 2.5rem !default\n\n$notification-colors: $colors !default\n\n.notification\n @extend %block\n background-color: $notification-background-color\n border-radius: $notification-radius\n position: relative\n +ltr\n padding: $notification-padding-ltr\n +rtl\n padding: $notification-padding-rtl\n a:not(.button):not(.dropdown-item)\n color: currentColor\n text-decoration: underline\n strong\n color: currentColor\n code,\n pre\n background: $notification-code-background-color\n pre code\n background: transparent\n & > .delete\n +ltr-position(0.5rem)\n position: absolute\n top: 0.5rem\n .title,\n .subtitle,\n .content\n color: currentColor\n // Colors\n @each $name, $pair in $notification-colors\n $color: nth($pair, 1)\n $color-invert: nth($pair, 2)\n &.is-#{$name}\n background-color: $color\n color: $color-invert\n // If light and dark colors are provided\n @if length($pair) >= 4\n $color-light: nth($pair, 3)\n $color-dark: nth($pair, 4)\n &.is-light\n background-color: $color-light\n color: $color-dark\n","@import \"../utilities/mixins\"\n\n$progress-bar-background-color: $border-light !default\n$progress-value-background-color: $text !default\n$progress-border-radius: $radius-rounded !default\n\n$progress-indeterminate-duration: 1.5s !default\n\n$progress-colors: $colors !default\n\n.progress\n @extend %block\n -moz-appearance: none\n -webkit-appearance: none\n border: none\n border-radius: $progress-border-radius\n display: block\n height: $size-normal\n overflow: hidden\n padding: 0\n width: 100%\n &::-webkit-progress-bar\n background-color: $progress-bar-background-color\n &::-webkit-progress-value\n background-color: $progress-value-background-color\n &::-moz-progress-bar\n background-color: $progress-value-background-color\n &::-ms-fill\n background-color: $progress-value-background-color\n border: none\n // Colors\n @each $name, $pair in $progress-colors\n $color: nth($pair, 1)\n &.is-#{$name}\n &::-webkit-progress-value\n background-color: $color\n &::-moz-progress-bar\n background-color: $color\n &::-ms-fill\n background-color: $color\n &:indeterminate\n background-image: linear-gradient(to right, $color 30%, $progress-bar-background-color 30%)\n\n &:indeterminate\n animation-duration: $progress-indeterminate-duration\n animation-iteration-count: infinite\n animation-name: moveIndeterminate\n animation-timing-function: linear\n background-color: $progress-bar-background-color\n background-image: linear-gradient(to right, $text 30%, $progress-bar-background-color 30%)\n background-position: top left\n background-repeat: no-repeat\n background-size: 150% 150%\n &::-webkit-progress-bar\n background-color: transparent\n &::-moz-progress-bar\n background-color: transparent\n &::-ms-fill\n animation-name: none\n\n // Sizes\n &.is-small\n height: $size-small\n &.is-medium\n height: $size-medium\n &.is-large\n height: $size-large\n\n@keyframes moveIndeterminate\n from\n background-position: 200% 0\n to\n background-position: -200% 0\n","@import \"../utilities/mixins\"\n\n$table-color: $text-strong !default\n$table-background-color: $scheme-main !default\n\n$table-cell-border: 1px solid $border !default\n$table-cell-border-width: 0 0 1px !default\n$table-cell-padding: 0.5em 0.75em !default\n$table-cell-heading-color: $text-strong !default\n$table-cell-text-align: left !default\n\n$table-head-cell-border-width: 0 0 2px !default\n$table-head-cell-color: $text-strong !default\n$table-foot-cell-border-width: 2px 0 0 !default\n$table-foot-cell-color: $text-strong !default\n\n$table-head-background-color: transparent !default\n$table-body-background-color: transparent !default\n$table-foot-background-color: transparent !default\n\n$table-row-hover-background-color: $scheme-main-bis !default\n\n$table-row-active-background-color: $primary !default\n$table-row-active-color: $primary-invert !default\n\n$table-striped-row-even-background-color: $scheme-main-bis !default\n$table-striped-row-even-hover-background-color: $scheme-main-ter !default\n\n$table-colors: $colors !default\n\n.table\n @extend %block\n background-color: $table-background-color\n color: $table-color\n td,\n th\n border: $table-cell-border\n border-width: $table-cell-border-width\n padding: $table-cell-padding\n vertical-align: top\n // Colors\n @each $name, $pair in $table-colors\n $color: nth($pair, 1)\n $color-invert: nth($pair, 2)\n &.is-#{$name}\n background-color: $color\n border-color: $color\n color: $color-invert\n // Modifiers\n &.is-narrow\n white-space: nowrap\n width: 1%\n &.is-selected\n background-color: $table-row-active-background-color\n color: $table-row-active-color\n a,\n strong\n color: currentColor\n &.is-vcentered\n vertical-align: middle\n th\n color: $table-cell-heading-color\n &:not([align])\n text-align: $table-cell-text-align\n tr\n &.is-selected\n background-color: $table-row-active-background-color\n color: $table-row-active-color\n a,\n strong\n color: currentColor\n td,\n th\n border-color: $table-row-active-color\n color: currentColor\n thead\n background-color: $table-head-background-color\n td,\n th\n border-width: $table-head-cell-border-width\n color: $table-head-cell-color\n tfoot\n background-color: $table-foot-background-color\n td,\n th\n border-width: $table-foot-cell-border-width\n color: $table-foot-cell-color\n tbody\n background-color: $table-body-background-color\n tr\n &:last-child\n td,\n th\n border-bottom-width: 0\n // Modifiers\n &.is-bordered\n td,\n th\n border-width: 1px\n tr\n &:last-child\n td,\n th\n border-bottom-width: 1px\n &.is-fullwidth\n width: 100%\n &.is-hoverable\n tbody\n tr:not(.is-selected)\n &:hover\n background-color: $table-row-hover-background-color\n &.is-striped\n tbody\n tr:not(.is-selected)\n &:hover\n background-color: $table-row-hover-background-color\n &:nth-child(even)\n background-color: $table-striped-row-even-hover-background-color\n &.is-narrow\n td,\n th\n padding: 0.25em 0.5em\n &.is-striped\n tbody\n tr:not(.is-selected)\n &:nth-child(even)\n background-color: $table-striped-row-even-background-color\n\n.table-container\n @extend %block\n +overflow-touch\n overflow: auto\n overflow-y: hidden\n max-width: 100%\n","@import \"../utilities/mixins\"\n\n$tag-background-color: $background !default\n$tag-color: $text !default\n$tag-radius: $radius !default\n$tag-delete-margin: 1px !default\n\n$tag-colors: $colors !default\n\n.tags\n align-items: center\n display: flex\n flex-wrap: wrap\n justify-content: flex-start\n .tag\n margin-bottom: 0.5rem\n &:not(:last-child)\n +ltr-property(\"margin\", 0.5rem)\n &:last-child\n margin-bottom: -0.5rem\n &:not(:last-child)\n margin-bottom: 1rem\n // Sizes\n &.are-medium\n .tag:not(.is-normal):not(.is-large)\n font-size: $size-normal\n &.are-large\n .tag:not(.is-normal):not(.is-medium)\n font-size: $size-medium\n &.is-centered\n justify-content: center\n .tag\n margin-right: 0.25rem\n margin-left: 0.25rem\n &.is-right\n justify-content: flex-end\n .tag\n &:not(:first-child)\n margin-left: 0.5rem\n &:not(:last-child)\n margin-right: 0\n &.has-addons\n .tag\n +ltr-property(\"margin\", 0)\n &:not(:first-child)\n +ltr-property(\"margin\", 0, false)\n +ltr\n border-top-left-radius: 0\n border-bottom-left-radius: 0\n +rtl\n border-top-right-radius: 0\n border-bottom-right-radius: 0\n &:not(:last-child)\n +ltr\n border-top-right-radius: 0\n border-bottom-right-radius: 0\n +rtl\n border-top-left-radius: 0\n border-bottom-left-radius: 0\n\n.tag:not(body)\n align-items: center\n background-color: $tag-background-color\n border-radius: $tag-radius\n color: $tag-color\n display: inline-flex\n font-size: $size-small\n height: 2em\n justify-content: center\n line-height: 1.5\n padding-left: 0.75em\n padding-right: 0.75em\n white-space: nowrap\n .delete\n +ltr-property(\"margin\", 0.25rem, false)\n +ltr-property(\"margin\", -0.375rem)\n // Colors\n @each $name, $pair in $tag-colors\n $color: nth($pair, 1)\n $color-invert: nth($pair, 2)\n &.is-#{$name}\n background-color: $color\n color: $color-invert\n // If a light and dark colors are provided\n @if length($pair) > 3\n $color-light: nth($pair, 3)\n $color-dark: nth($pair, 4)\n &.is-light\n background-color: $color-light\n color: $color-dark\n // Sizes\n &.is-normal\n font-size: $size-small\n &.is-medium\n font-size: $size-normal\n &.is-large\n font-size: $size-medium\n .icon\n &:first-child:not(:last-child)\n +ltr-property(\"margin\", -0.375em, false)\n +ltr-property(\"margin\", 0.1875em)\n &:last-child:not(:first-child)\n +ltr-property(\"margin\", 0.1875em, false)\n +ltr-property(\"margin\", -0.375em)\n &:first-child:last-child\n +ltr-property(\"margin\", -0.375em, false)\n +ltr-property(\"margin\", -0.375em)\n // Modifiers\n &.is-delete\n +ltr-property(\"margin\", $tag-delete-margin, false)\n padding: 0\n position: relative\n width: 2em\n &::before,\n &::after\n background-color: currentColor\n content: \"\"\n display: block\n left: 50%\n position: absolute\n top: 50%\n transform: translateX(-50%) translateY(-50%) rotate(45deg)\n transform-origin: center center\n &::before\n height: 1px\n width: 50%\n &::after\n height: 50%\n width: 1px\n &:hover,\n &:focus\n background-color: darken($tag-background-color, 5%)\n &:active\n background-color: darken($tag-background-color, 10%)\n &.is-rounded\n border-radius: $radius-rounded\n\na.tag\n &:hover\n text-decoration: underline\n","@import \"../utilities/mixins\"\n\n$title-color: $text-strong !default\n$title-family: false !default\n$title-size: $size-3 !default\n$title-weight: $weight-semibold !default\n$title-line-height: 1.125 !default\n$title-strong-color: inherit !default\n$title-strong-weight: inherit !default\n$title-sub-size: 0.75em !default\n$title-sup-size: 0.75em !default\n\n$subtitle-color: $text !default\n$subtitle-family: false !default\n$subtitle-size: $size-5 !default\n$subtitle-weight: $weight-normal !default\n$subtitle-line-height: 1.25 !default\n$subtitle-strong-color: $text-strong !default\n$subtitle-strong-weight: $weight-semibold !default\n$subtitle-negative-margin: -1.25rem !default\n\n.title,\n.subtitle\n @extend %block\n word-break: break-word\n em,\n span\n font-weight: inherit\n sub\n font-size: $title-sub-size\n sup\n font-size: $title-sup-size\n .tag\n vertical-align: middle\n\n.title\n color: $title-color\n @if $title-family\n font-family: $title-family\n font-size: $title-size\n font-weight: $title-weight\n line-height: $title-line-height\n strong\n color: $title-strong-color\n font-weight: $title-strong-weight\n &:not(.is-spaced) + .subtitle\n margin-top: $subtitle-negative-margin\n // Sizes\n @each $size in $sizes\n $i: index($sizes, $size)\n &.is-#{$i}\n font-size: $size\n\n.subtitle\n color: $subtitle-color\n @if $subtitle-family\n font-family: $subtitle-family\n font-size: $subtitle-size\n font-weight: $subtitle-weight\n line-height: $subtitle-line-height\n strong\n color: $subtitle-strong-color\n font-weight: $subtitle-strong-weight\n &:not(.is-spaced) + .title\n margin-top: $subtitle-negative-margin\n // Sizes\n @each $size in $sizes\n $i: index($sizes, $size)\n &.is-#{$i}\n font-size: $size\n","@import \"../utilities/mixins\"\n\n.block\n @extend %block\n\n.delete\n @extend %delete\n\n.heading\n display: block\n font-size: 11px\n letter-spacing: 1px\n margin-bottom: 5px\n text-transform: uppercase\n\n.loader\n @extend %loader\n\n.number\n align-items: center\n background-color: $background\n border-radius: $radius-rounded\n display: inline-flex\n font-size: $size-medium\n height: 2em\n justify-content: center\n margin-right: 1.5rem\n min-width: 2.5em\n padding: 0.25rem 0.5rem\n text-align: center\n vertical-align: top\n","@import \"../utilities/controls\"\n@import \"../utilities/mixins\"\n\n$form-colors: $colors !default\n\n$input-color: $text-strong !default\n$input-background-color: $scheme-main !default\n$input-border-color: $border !default\n$input-height: $control-height !default\n$input-shadow: inset 0 0.0625em 0.125em rgba($scheme-invert, 0.05) !default\n$input-placeholder-color: bulmaRgba($input-color, 0.3) !default\n\n$input-hover-color: $text-strong !default\n$input-hover-border-color: $border-hover !default\n\n$input-focus-color: $text-strong !default\n$input-focus-border-color: $link !default\n$input-focus-box-shadow-size: 0 0 0 0.125em !default\n$input-focus-box-shadow-color: bulmaRgba($link, 0.25) !default\n\n$input-disabled-color: $text-light !default\n$input-disabled-background-color: $background !default\n$input-disabled-border-color: $background !default\n$input-disabled-placeholder-color: bulmaRgba($input-disabled-color, 0.3) !default\n\n$input-arrow: $link !default\n\n$input-icon-color: $border !default\n$input-icon-active-color: $text !default\n\n$input-radius: $radius !default\n\n=input\n @extend %control\n background-color: $input-background-color\n border-color: $input-border-color\n border-radius: $input-radius\n color: $input-color\n +placeholder\n color: $input-placeholder-color\n &:hover,\n &.is-hovered\n border-color: $input-hover-border-color\n &:focus,\n &.is-focused,\n &:active,\n &.is-active\n border-color: $input-focus-border-color\n box-shadow: $input-focus-box-shadow-size $input-focus-box-shadow-color\n &[disabled],\n fieldset[disabled] &\n background-color: $input-disabled-background-color\n border-color: $input-disabled-border-color\n box-shadow: none\n color: $input-disabled-color\n +placeholder\n color: $input-disabled-placeholder-color\n\n%input\n +input\n","$textarea-padding: $control-padding-horizontal !default\n$textarea-max-height: 40em !default\n$textarea-min-height: 8em !default\n\n$textarea-colors: $form-colors !default\n\n%input-textarea\n @extend %input\n box-shadow: $input-shadow\n max-width: 100%\n width: 100%\n &[readonly]\n box-shadow: none\n // Colors\n @each $name, $pair in $textarea-colors\n $color: nth($pair, 1)\n &.is-#{$name}\n border-color: $color\n &:focus,\n &.is-focused,\n &:active,\n &.is-active\n box-shadow: $input-focus-box-shadow-size bulmaRgba($color, 0.25)\n // Sizes\n &.is-small\n +control-small\n &.is-medium\n +control-medium\n &.is-large\n +control-large\n // Modifiers\n &.is-fullwidth\n display: block\n width: 100%\n &.is-inline\n display: inline\n width: auto\n\n.input\n @extend %input-textarea\n &.is-rounded\n border-radius: $radius-rounded\n padding-left: calc(#{$control-padding-horizontal} + 0.375em)\n padding-right: calc(#{$control-padding-horizontal} + 0.375em)\n &.is-static\n background-color: transparent\n border-color: transparent\n box-shadow: none\n padding-left: 0\n padding-right: 0\n\n.textarea\n @extend %input-textarea\n display: block\n max-width: 100%\n min-width: 100%\n padding: $textarea-padding\n resize: vertical\n &:not([rows])\n max-height: $textarea-max-height\n min-height: $textarea-min-height\n &[rows]\n height: initial\n // Modifiers\n &.has-fixed-size\n resize: none\n","%checkbox-radio\n cursor: pointer\n display: inline-block\n line-height: 1.25\n position: relative\n input\n cursor: pointer\n &:hover\n color: $input-hover-color\n &[disabled],\n fieldset[disabled] &,\n input[disabled]\n color: $input-disabled-color\n cursor: not-allowed\n\n.checkbox\n @extend %checkbox-radio\n\n.radio\n @extend %checkbox-radio\n & + .radio\n +ltr-property(\"margin\", 0.5em, false)\n","$select-colors: $form-colors !default\n\n.select\n display: inline-block\n max-width: 100%\n position: relative\n vertical-align: top\n &:not(.is-multiple)\n height: $input-height\n &:not(.is-multiple):not(.is-loading)\n &::after\n @extend %arrow\n border-color: $input-arrow\n +ltr-position(1.125em)\n z-index: 4\n &.is-rounded\n select\n border-radius: $radius-rounded\n +ltr-property(\"padding\", 1em, false)\n select\n @extend %input\n cursor: pointer\n display: block\n font-size: 1em\n max-width: 100%\n outline: none\n &::-ms-expand\n display: none\n &[disabled]:hover,\n fieldset[disabled] &:hover\n border-color: $input-disabled-border-color\n &:not([multiple])\n +ltr-property(\"padding\", 2.5em)\n &[multiple]\n height: auto\n padding: 0\n option\n padding: 0.5em 1em\n // States\n &:not(.is-multiple):not(.is-loading):hover\n &::after\n border-color: $input-hover-color\n // Colors\n @each $name, $pair in $select-colors\n $color: nth($pair, 1)\n &.is-#{$name}\n &:not(:hover)::after\n border-color: $color\n select\n border-color: $color\n &:hover,\n &.is-hovered\n border-color: bulmaDarken($color, 5%)\n &:focus,\n &.is-focused,\n &:active,\n &.is-active\n box-shadow: $input-focus-box-shadow-size bulmaRgba($color, 0.25)\n // Sizes\n &.is-small\n +control-small\n &.is-medium\n +control-medium\n &.is-large\n +control-large\n // Modifiers\n &.is-disabled\n &::after\n border-color: $input-disabled-color !important\n opacity: 0.5\n &.is-fullwidth\n width: 100%\n select\n width: 100%\n &.is-loading\n &::after\n @extend %loader\n margin-top: 0\n position: absolute\n +ltr-position(0.625em)\n top: 0.625em\n transform: none\n &.is-small:after\n font-size: $size-small\n &.is-medium:after\n font-size: $size-medium\n &.is-large:after\n font-size: $size-large\n","$file-border-color: $border !default\n$file-radius: $radius !default\n\n$file-cta-background-color: $scheme-main-ter !default\n$file-cta-color: $text !default\n$file-cta-hover-color: $text-strong !default\n$file-cta-active-color: $text-strong !default\n\n$file-name-border-color: $border !default\n$file-name-border-style: solid !default\n$file-name-border-width: 1px 1px 1px 0 !default\n$file-name-max-width: 16em !default\n\n$file-colors: $form-colors !default\n\n.file\n @extend %unselectable\n align-items: stretch\n display: flex\n justify-content: flex-start\n position: relative\n // Colors\n @each $name, $pair in $file-colors\n $color: nth($pair, 1)\n $color-invert: nth($pair, 2)\n &.is-#{$name}\n .file-cta\n background-color: $color\n border-color: transparent\n color: $color-invert\n &:hover,\n &.is-hovered\n .file-cta\n background-color: bulmaDarken($color, 2.5%)\n border-color: transparent\n color: $color-invert\n &:focus,\n &.is-focused\n .file-cta\n border-color: transparent\n box-shadow: 0 0 0.5em bulmaRgba($color, 0.25)\n color: $color-invert\n &:active,\n &.is-active\n .file-cta\n background-color: bulmaDarken($color, 5%)\n border-color: transparent\n color: $color-invert\n // Sizes\n &.is-small\n font-size: $size-small\n &.is-normal\n font-size: $size-normal\n &.is-medium\n font-size: $size-medium\n .file-icon\n .fa\n font-size: 21px\n &.is-large\n font-size: $size-large\n .file-icon\n .fa\n font-size: 28px\n // Modifiers\n &.has-name\n .file-cta\n border-bottom-right-radius: 0\n border-top-right-radius: 0\n .file-name\n border-bottom-left-radius: 0\n border-top-left-radius: 0\n &.is-empty\n .file-cta\n border-radius: $file-radius\n .file-name\n display: none\n &.is-boxed\n .file-label\n flex-direction: column\n .file-cta\n flex-direction: column\n height: auto\n padding: 1em 3em\n .file-name\n border-width: 0 1px 1px\n .file-icon\n height: 1.5em\n width: 1.5em\n .fa\n font-size: 21px\n &.is-small\n .file-icon .fa\n font-size: 14px\n &.is-medium\n .file-icon .fa\n font-size: 28px\n &.is-large\n .file-icon .fa\n font-size: 35px\n &.has-name\n .file-cta\n border-radius: $file-radius $file-radius 0 0\n .file-name\n border-radius: 0 0 $file-radius $file-radius\n border-width: 0 1px 1px\n &.is-centered\n justify-content: center\n &.is-fullwidth\n .file-label\n width: 100%\n .file-name\n flex-grow: 1\n max-width: none\n &.is-right\n justify-content: flex-end\n .file-cta\n border-radius: 0 $file-radius $file-radius 0\n .file-name\n border-radius: $file-radius 0 0 $file-radius\n border-width: 1px 0 1px 1px\n order: -1\n\n.file-label\n align-items: stretch\n display: flex\n cursor: pointer\n justify-content: flex-start\n overflow: hidden\n position: relative\n &:hover\n .file-cta\n background-color: bulmaDarken($file-cta-background-color, 2.5%)\n color: $file-cta-hover-color\n .file-name\n border-color: bulmaDarken($file-name-border-color, 2.5%)\n &:active\n .file-cta\n background-color: bulmaDarken($file-cta-background-color, 5%)\n color: $file-cta-active-color\n .file-name\n border-color: bulmaDarken($file-name-border-color, 5%)\n\n.file-input\n height: 100%\n left: 0\n opacity: 0\n outline: none\n position: absolute\n top: 0\n width: 100%\n\n.file-cta,\n.file-name\n @extend %control\n border-color: $file-border-color\n border-radius: $file-radius\n font-size: 1em\n padding-left: 1em\n padding-right: 1em\n white-space: nowrap\n\n.file-cta\n background-color: $file-cta-background-color\n color: $file-cta-color\n\n.file-name\n border-color: $file-name-border-color\n border-style: $file-name-border-style\n border-width: $file-name-border-width\n display: block\n max-width: $file-name-max-width\n overflow: hidden\n text-align: inherit\n text-overflow: ellipsis\n\n.file-icon\n align-items: center\n display: flex\n height: 1em\n justify-content: center\n +ltr-property(\"margin\", 0.5em)\n width: 1em\n .fa\n font-size: 14px\n","$label-color: $text-strong !default\n$label-weight: $weight-bold !default\n\n$help-size: $size-small !default\n\n$label-colors: $form-colors !default\n\n.label\n color: $label-color\n display: block\n font-size: $size-normal\n font-weight: $label-weight\n &:not(:last-child)\n margin-bottom: 0.5em\n // Sizes\n &.is-small\n font-size: $size-small\n &.is-medium\n font-size: $size-medium\n &.is-large\n font-size: $size-large\n\n.help\n display: block\n font-size: $help-size\n margin-top: 0.25rem\n @each $name, $pair in $label-colors\n $color: nth($pair, 1)\n &.is-#{$name}\n color: $color\n\n// Containers\n\n.field\n &:not(:last-child)\n margin-bottom: 0.75rem\n // Modifiers\n &.has-addons\n display: flex\n justify-content: flex-start\n .control\n &:not(:last-child)\n +ltr-property(\"margin\", -1px)\n &:not(:first-child):not(:last-child)\n .button,\n .input,\n .select select\n border-radius: 0\n &:first-child:not(:only-child)\n .button,\n .input,\n .select select\n +ltr\n border-bottom-right-radius: 0\n border-top-right-radius: 0\n +rtl\n border-bottom-left-radius: 0\n border-top-left-radius: 0\n &:last-child:not(:only-child)\n .button,\n .input,\n .select select\n +ltr\n border-bottom-left-radius: 0\n border-top-left-radius: 0\n +rtl\n border-bottom-right-radius: 0\n border-top-right-radius: 0\n .button,\n .input,\n .select select\n &:not([disabled])\n &:hover,\n &.is-hovered\n z-index: 2\n &:focus,\n &.is-focused,\n &:active,\n &.is-active\n z-index: 3\n &:hover\n z-index: 4\n &.is-expanded\n flex-grow: 1\n flex-shrink: 1\n &.has-addons-centered\n justify-content: center\n &.has-addons-right\n justify-content: flex-end\n &.has-addons-fullwidth\n .control\n flex-grow: 1\n flex-shrink: 0\n &.is-grouped\n display: flex\n justify-content: flex-start\n & > .control\n flex-shrink: 0\n &:not(:last-child)\n margin-bottom: 0\n +ltr-property(\"margin\", 0.75rem)\n &.is-expanded\n flex-grow: 1\n flex-shrink: 1\n &.is-grouped-centered\n justify-content: center\n &.is-grouped-right\n justify-content: flex-end\n &.is-grouped-multiline\n flex-wrap: wrap\n & > .control\n &:last-child,\n &:not(:last-child)\n margin-bottom: 0.75rem\n &:last-child\n margin-bottom: -0.75rem\n &:not(:last-child)\n margin-bottom: 0\n &.is-horizontal\n +tablet\n display: flex\n\n.field-label\n .label\n font-size: inherit\n +mobile\n margin-bottom: 0.5rem\n +tablet\n flex-basis: 0\n flex-grow: 1\n flex-shrink: 0\n +ltr-property(\"margin\", 1.5rem)\n text-align: right\n &.is-small\n font-size: $size-small\n padding-top: 0.375em\n &.is-normal\n padding-top: 0.375em\n &.is-medium\n font-size: $size-medium\n padding-top: 0.375em\n &.is-large\n font-size: $size-large\n padding-top: 0.375em\n\n.field-body\n .field .field\n margin-bottom: 0\n +tablet\n display: flex\n flex-basis: 0\n flex-grow: 5\n flex-shrink: 1\n .field\n margin-bottom: 0\n & > .field\n flex-shrink: 1\n &:not(.is-narrow)\n flex-grow: 1\n &:not(:last-child)\n +ltr-property(\"margin\", 0.75rem)\n\n.control\n box-sizing: border-box\n clear: both\n font-size: $size-normal\n position: relative\n text-align: inherit\n // Modifiers\n &.has-icons-left,\n &.has-icons-right\n .input,\n .select\n &:focus\n & ~ .icon\n color: $input-icon-active-color\n &.is-small ~ .icon\n font-size: $size-small\n &.is-medium ~ .icon\n font-size: $size-medium\n &.is-large ~ .icon\n font-size: $size-large\n .icon\n color: $input-icon-color\n height: $input-height\n pointer-events: none\n position: absolute\n top: 0\n width: $input-height\n z-index: 4\n &.has-icons-left\n .input,\n .select select\n padding-left: $input-height\n .icon.is-left\n left: 0\n &.has-icons-right\n .input,\n .select select\n padding-right: $input-height\n .icon.is-right\n right: 0\n &.is-loading\n &::after\n @extend %loader\n position: absolute !important\n +ltr-position(0.625em)\n top: 0.625em\n z-index: 4\n &.is-small:after\n font-size: $size-small\n &.is-medium:after\n font-size: $size-medium\n &.is-large:after\n font-size: $size-large\n","@import \"../utilities/mixins\"\n\n$column-gap: 0.75rem !default\n\n.column\n display: block\n flex-basis: 0\n flex-grow: 1\n flex-shrink: 1\n padding: $column-gap\n .columns.is-mobile > &.is-narrow\n flex: none\n width: unset\n .columns.is-mobile > &.is-full\n flex: none\n width: 100%\n .columns.is-mobile > &.is-three-quarters\n flex: none\n width: 75%\n .columns.is-mobile > &.is-two-thirds\n flex: none\n width: 66.6666%\n .columns.is-mobile > &.is-half\n flex: none\n width: 50%\n .columns.is-mobile > &.is-one-third\n flex: none\n width: 33.3333%\n .columns.is-mobile > &.is-one-quarter\n flex: none\n width: 25%\n .columns.is-mobile > &.is-one-fifth\n flex: none\n width: 20%\n .columns.is-mobile > &.is-two-fifths\n flex: none\n width: 40%\n .columns.is-mobile > &.is-three-fifths\n flex: none\n width: 60%\n .columns.is-mobile > &.is-four-fifths\n flex: none\n width: 80%\n .columns.is-mobile > &.is-offset-three-quarters\n +ltr-property(\"margin\", 75%, false)\n .columns.is-mobile > &.is-offset-two-thirds\n +ltr-property(\"margin\", 66.6666%, false)\n .columns.is-mobile > &.is-offset-half\n +ltr-property(\"margin\", 50%, false)\n .columns.is-mobile > &.is-offset-one-third\n +ltr-property(\"margin\", 33.3333%, false)\n .columns.is-mobile > &.is-offset-one-quarter\n +ltr-property(\"margin\", 25%, false)\n .columns.is-mobile > &.is-offset-one-fifth\n +ltr-property(\"margin\", 20%, false)\n .columns.is-mobile > &.is-offset-two-fifths\n +ltr-property(\"margin\", 40%, false)\n .columns.is-mobile > &.is-offset-three-fifths\n +ltr-property(\"margin\", 60%, false)\n .columns.is-mobile > &.is-offset-four-fifths\n +ltr-property(\"margin\", 80%, false)\n @for $i from 0 through 12\n .columns.is-mobile > &.is-#{$i}\n flex: none\n width: percentage(divide($i, 12))\n .columns.is-mobile > &.is-offset-#{$i}\n +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n +mobile\n &.is-narrow-mobile\n flex: none\n width: unset\n &.is-full-mobile\n flex: none\n width: 100%\n &.is-three-quarters-mobile\n flex: none\n width: 75%\n &.is-two-thirds-mobile\n flex: none\n width: 66.6666%\n &.is-half-mobile\n flex: none\n width: 50%\n &.is-one-third-mobile\n flex: none\n width: 33.3333%\n &.is-one-quarter-mobile\n flex: none\n width: 25%\n &.is-one-fifth-mobile\n flex: none\n width: 20%\n &.is-two-fifths-mobile\n flex: none\n width: 40%\n &.is-three-fifths-mobile\n flex: none\n width: 60%\n &.is-four-fifths-mobile\n flex: none\n width: 80%\n &.is-offset-three-quarters-mobile\n +ltr-property(\"margin\", 75%, false)\n &.is-offset-two-thirds-mobile\n +ltr-property(\"margin\", 66.6666%, false)\n &.is-offset-half-mobile\n +ltr-property(\"margin\", 50%, false)\n &.is-offset-one-third-mobile\n +ltr-property(\"margin\", 33.3333%, false)\n &.is-offset-one-quarter-mobile\n +ltr-property(\"margin\", 25%, false)\n &.is-offset-one-fifth-mobile\n +ltr-property(\"margin\", 20%, false)\n &.is-offset-two-fifths-mobile\n +ltr-property(\"margin\", 40%, false)\n &.is-offset-three-fifths-mobile\n +ltr-property(\"margin\", 60%, false)\n &.is-offset-four-fifths-mobile\n +ltr-property(\"margin\", 80%, false)\n @for $i from 0 through 12\n &.is-#{$i}-mobile\n flex: none\n width: percentage(divide($i, 12))\n &.is-offset-#{$i}-mobile\n +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n +tablet\n &.is-narrow,\n &.is-narrow-tablet\n flex: none\n width: unset\n &.is-full,\n &.is-full-tablet\n flex: none\n width: 100%\n &.is-three-quarters,\n &.is-three-quarters-tablet\n flex: none\n width: 75%\n &.is-two-thirds,\n &.is-two-thirds-tablet\n flex: none\n width: 66.6666%\n &.is-half,\n &.is-half-tablet\n flex: none\n width: 50%\n &.is-one-third,\n &.is-one-third-tablet\n flex: none\n width: 33.3333%\n &.is-one-quarter,\n &.is-one-quarter-tablet\n flex: none\n width: 25%\n &.is-one-fifth,\n &.is-one-fifth-tablet\n flex: none\n width: 20%\n &.is-two-fifths,\n &.is-two-fifths-tablet\n flex: none\n width: 40%\n &.is-three-fifths,\n &.is-three-fifths-tablet\n flex: none\n width: 60%\n &.is-four-fifths,\n &.is-four-fifths-tablet\n flex: none\n width: 80%\n &.is-offset-three-quarters,\n &.is-offset-three-quarters-tablet\n +ltr-property(\"margin\", 75%, false)\n &.is-offset-two-thirds,\n &.is-offset-two-thirds-tablet\n +ltr-property(\"margin\", 66.6666%, false)\n &.is-offset-half,\n &.is-offset-half-tablet\n +ltr-property(\"margin\", 50%, false)\n &.is-offset-one-third,\n &.is-offset-one-third-tablet\n +ltr-property(\"margin\", 33.3333%, false)\n &.is-offset-one-quarter,\n &.is-offset-one-quarter-tablet\n +ltr-property(\"margin\", 25%, false)\n &.is-offset-one-fifth,\n &.is-offset-one-fifth-tablet\n +ltr-property(\"margin\", 20%, false)\n &.is-offset-two-fifths,\n &.is-offset-two-fifths-tablet\n +ltr-property(\"margin\", 40%, false)\n &.is-offset-three-fifths,\n &.is-offset-three-fifths-tablet\n +ltr-property(\"margin\", 60%, false)\n &.is-offset-four-fifths,\n &.is-offset-four-fifths-tablet\n +ltr-property(\"margin\", 80%, false)\n @for $i from 0 through 12\n &.is-#{$i},\n &.is-#{$i}-tablet\n flex: none\n width: percentage(divide($i, 12))\n &.is-offset-#{$i},\n &.is-offset-#{$i}-tablet\n +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n +touch\n &.is-narrow-touch\n flex: none\n width: unset\n &.is-full-touch\n flex: none\n width: 100%\n &.is-three-quarters-touch\n flex: none\n width: 75%\n &.is-two-thirds-touch\n flex: none\n width: 66.6666%\n &.is-half-touch\n flex: none\n width: 50%\n &.is-one-third-touch\n flex: none\n width: 33.3333%\n &.is-one-quarter-touch\n flex: none\n width: 25%\n &.is-one-fifth-touch\n flex: none\n width: 20%\n &.is-two-fifths-touch\n flex: none\n width: 40%\n &.is-three-fifths-touch\n flex: none\n width: 60%\n &.is-four-fifths-touch\n flex: none\n width: 80%\n &.is-offset-three-quarters-touch\n +ltr-property(\"margin\", 75%, false)\n &.is-offset-two-thirds-touch\n +ltr-property(\"margin\", 66.6666%, false)\n &.is-offset-half-touch\n +ltr-property(\"margin\", 50%, false)\n &.is-offset-one-third-touch\n +ltr-property(\"margin\", 33.3333%, false)\n &.is-offset-one-quarter-touch\n +ltr-property(\"margin\", 25%, false)\n &.is-offset-one-fifth-touch\n +ltr-property(\"margin\", 20%, false)\n &.is-offset-two-fifths-touch\n +ltr-property(\"margin\", 40%, false)\n &.is-offset-three-fifths-touch\n +ltr-property(\"margin\", 60%, false)\n &.is-offset-four-fifths-touch\n +ltr-property(\"margin\", 80%, false)\n @for $i from 0 through 12\n &.is-#{$i}-touch\n flex: none\n width: percentage(divide($i, 12))\n &.is-offset-#{$i}-touch\n +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n +desktop\n &.is-narrow-desktop\n flex: none\n width: unset\n &.is-full-desktop\n flex: none\n width: 100%\n &.is-three-quarters-desktop\n flex: none\n width: 75%\n &.is-two-thirds-desktop\n flex: none\n width: 66.6666%\n &.is-half-desktop\n flex: none\n width: 50%\n &.is-one-third-desktop\n flex: none\n width: 33.3333%\n &.is-one-quarter-desktop\n flex: none\n width: 25%\n &.is-one-fifth-desktop\n flex: none\n width: 20%\n &.is-two-fifths-desktop\n flex: none\n width: 40%\n &.is-three-fifths-desktop\n flex: none\n width: 60%\n &.is-four-fifths-desktop\n flex: none\n width: 80%\n &.is-offset-three-quarters-desktop\n +ltr-property(\"margin\", 75%, false)\n &.is-offset-two-thirds-desktop\n +ltr-property(\"margin\", 66.6666%, false)\n &.is-offset-half-desktop\n +ltr-property(\"margin\", 50%, false)\n &.is-offset-one-third-desktop\n +ltr-property(\"margin\", 33.3333%, false)\n &.is-offset-one-quarter-desktop\n +ltr-property(\"margin\", 25%, false)\n &.is-offset-one-fifth-desktop\n +ltr-property(\"margin\", 20%, false)\n &.is-offset-two-fifths-desktop\n +ltr-property(\"margin\", 40%, false)\n &.is-offset-three-fifths-desktop\n +ltr-property(\"margin\", 60%, false)\n &.is-offset-four-fifths-desktop\n +ltr-property(\"margin\", 80%, false)\n @for $i from 0 through 12\n &.is-#{$i}-desktop\n flex: none\n width: percentage(divide($i, 12))\n &.is-offset-#{$i}-desktop\n +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n +widescreen\n &.is-narrow-widescreen\n flex: none\n width: unset\n &.is-full-widescreen\n flex: none\n width: 100%\n &.is-three-quarters-widescreen\n flex: none\n width: 75%\n &.is-two-thirds-widescreen\n flex: none\n width: 66.6666%\n &.is-half-widescreen\n flex: none\n width: 50%\n &.is-one-third-widescreen\n flex: none\n width: 33.3333%\n &.is-one-quarter-widescreen\n flex: none\n width: 25%\n &.is-one-fifth-widescreen\n flex: none\n width: 20%\n &.is-two-fifths-widescreen\n flex: none\n width: 40%\n &.is-three-fifths-widescreen\n flex: none\n width: 60%\n &.is-four-fifths-widescreen\n flex: none\n width: 80%\n &.is-offset-three-quarters-widescreen\n +ltr-property(\"margin\", 75%, false)\n &.is-offset-two-thirds-widescreen\n +ltr-property(\"margin\", 66.6666%, false)\n &.is-offset-half-widescreen\n +ltr-property(\"margin\", 50%, false)\n &.is-offset-one-third-widescreen\n +ltr-property(\"margin\", 33.3333%, false)\n &.is-offset-one-quarter-widescreen\n +ltr-property(\"margin\", 25%, false)\n &.is-offset-one-fifth-widescreen\n +ltr-property(\"margin\", 20%, false)\n &.is-offset-two-fifths-widescreen\n +ltr-property(\"margin\", 40%, false)\n &.is-offset-three-fifths-widescreen\n +ltr-property(\"margin\", 60%, false)\n &.is-offset-four-fifths-widescreen\n +ltr-property(\"margin\", 80%, false)\n @for $i from 0 through 12\n &.is-#{$i}-widescreen\n flex: none\n width: percentage(divide($i, 12))\n &.is-offset-#{$i}-widescreen\n +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n +fullhd\n &.is-narrow-fullhd\n flex: none\n width: unset\n &.is-full-fullhd\n flex: none\n width: 100%\n &.is-three-quarters-fullhd\n flex: none\n width: 75%\n &.is-two-thirds-fullhd\n flex: none\n width: 66.6666%\n &.is-half-fullhd\n flex: none\n width: 50%\n &.is-one-third-fullhd\n flex: none\n width: 33.3333%\n &.is-one-quarter-fullhd\n flex: none\n width: 25%\n &.is-one-fifth-fullhd\n flex: none\n width: 20%\n &.is-two-fifths-fullhd\n flex: none\n width: 40%\n &.is-three-fifths-fullhd\n flex: none\n width: 60%\n &.is-four-fifths-fullhd\n flex: none\n width: 80%\n &.is-offset-three-quarters-fullhd\n +ltr-property(\"margin\", 75%, false)\n &.is-offset-two-thirds-fullhd\n +ltr-property(\"margin\", 66.6666%, false)\n &.is-offset-half-fullhd\n +ltr-property(\"margin\", 50%, false)\n &.is-offset-one-third-fullhd\n +ltr-property(\"margin\", 33.3333%, false)\n &.is-offset-one-quarter-fullhd\n +ltr-property(\"margin\", 25%, false)\n &.is-offset-one-fifth-fullhd\n +ltr-property(\"margin\", 20%, false)\n &.is-offset-two-fifths-fullhd\n +ltr-property(\"margin\", 40%, false)\n &.is-offset-three-fifths-fullhd\n +ltr-property(\"margin\", 60%, false)\n &.is-offset-four-fifths-fullhd\n +ltr-property(\"margin\", 80%, false)\n @for $i from 0 through 12\n &.is-#{$i}-fullhd\n flex: none\n width: percentage(divide($i, 12))\n &.is-offset-#{$i}-fullhd\n +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n\n.columns\n +ltr-property(\"margin\", (-$column-gap), false)\n +ltr-property(\"margin\", (-$column-gap))\n margin-top: (-$column-gap)\n &:last-child\n margin-bottom: (-$column-gap)\n &:not(:last-child)\n margin-bottom: calc(1.5rem - #{$column-gap})\n // Modifiers\n &.is-centered\n justify-content: center\n &.is-gapless\n +ltr-property(\"margin\", 0, false)\n +ltr-property(\"margin\", 0)\n margin-top: 0\n & > .column\n margin: 0\n padding: 0 !important\n &:not(:last-child)\n margin-bottom: 1.5rem\n &:last-child\n margin-bottom: 0\n &.is-mobile\n display: flex\n &.is-multiline\n flex-wrap: wrap\n &.is-vcentered\n align-items: center\n // Responsiveness\n +tablet\n &:not(.is-desktop)\n display: flex\n +desktop\n // Modifiers\n &.is-desktop\n display: flex\n\n@if $variable-columns\n .columns.is-variable\n --columnGap: 0.75rem\n +ltr-property(\"margin\", calc(-1 * var(--columnGap)), false)\n +ltr-property(\"margin\", calc(-1 * var(--columnGap)))\n > .column\n padding-left: var(--columnGap)\n padding-right: var(--columnGap)\n @for $i from 0 through 8\n &.is-#{$i}\n --columnGap: #{$i * 0.25rem}\n +mobile\n &.is-#{$i}-mobile\n --columnGap: #{$i * 0.25rem}\n +tablet\n &.is-#{$i}-tablet\n --columnGap: #{$i * 0.25rem}\n +tablet-only\n &.is-#{$i}-tablet-only\n --columnGap: #{$i * 0.25rem}\n +touch\n &.is-#{$i}-touch\n --columnGap: #{$i * 0.25rem}\n +desktop\n &.is-#{$i}-desktop\n --columnGap: #{$i * 0.25rem}\n +desktop-only\n &.is-#{$i}-desktop-only\n --columnGap: #{$i * 0.25rem}\n +widescreen\n &.is-#{$i}-widescreen\n --columnGap: #{$i * 0.25rem}\n +widescreen-only\n &.is-#{$i}-widescreen-only\n --columnGap: #{$i * 0.25rem}\n +fullhd\n &.is-#{$i}-fullhd\n --columnGap: #{$i * 0.25rem}\n","@import \"../utilities/mixins\"\n\n$tile-spacing: 0.75rem !default\n\n.tile\n align-items: stretch\n display: block\n flex-basis: 0\n flex-grow: 1\n flex-shrink: 1\n min-height: min-content\n // Modifiers\n &.is-ancestor\n margin-left: $tile-spacing * -1\n margin-right: $tile-spacing * -1\n margin-top: $tile-spacing * -1\n &:last-child\n margin-bottom: $tile-spacing * -1\n &:not(:last-child)\n margin-bottom: $tile-spacing\n &.is-child\n margin: 0 !important\n &.is-parent\n padding: $tile-spacing\n &.is-vertical\n flex-direction: column\n & > .tile.is-child:not(:last-child)\n margin-bottom: 1.5rem !important\n // Responsiveness\n +tablet\n &:not(.is-child)\n display: flex\n @for $i from 1 through 12\n &.is-#{$i}\n flex: none\n width: (divide($i, 12)) * 100%\n","@import \"../utilities/derived-variables\"\n\n@each $name, $pair in $colors\n $color: nth($pair, 1)\n .has-text-#{$name}\n color: $color !important\n a.has-text-#{$name}\n &:hover,\n &:focus\n color: bulmaDarken($color, 10%) !important\n .has-background-#{$name}\n background-color: $color !important\n @if length($pair) >= 4\n $color-light: nth($pair, 3)\n $color-dark: nth($pair, 4)\n // Light\n .has-text-#{$name}-light\n color: $color-light !important\n a.has-text-#{$name}-light\n &:hover,\n &:focus\n color: bulmaDarken($color-light, 10%) !important\n .has-background-#{$name}-light\n background-color: $color-light !important\n // Dark\n .has-text-#{$name}-dark\n color: $color-dark !important\n a.has-text-#{$name}-dark\n &:hover,\n &:focus\n color: bulmaLighten($color-dark, 10%) !important\n .has-background-#{$name}-dark\n background-color: $color-dark !important\n\n@each $name, $shade in $shades\n .has-text-#{$name}\n color: $shade !important\n .has-background-#{$name}\n background-color: $shade !important\n","$flex-direction-values: row, row-reverse, column, column-reverse\n@each $value in $flex-direction-values\n .is-flex-direction-#{$value}\n flex-direction: $value !important\n\n$flex-wrap-values: nowrap, wrap, wrap-reverse\n@each $value in $flex-wrap-values\n .is-flex-wrap-#{$value}\n flex-wrap: $value !important\n\n$justify-content-values: flex-start, flex-end, center, space-between, space-around, space-evenly, start, end, left, right\n@each $value in $justify-content-values\n .is-justify-content-#{$value}\n justify-content: $value !important\n\n$align-content-values: flex-start, flex-end, center, space-between, space-around, space-evenly, stretch, start, end, baseline\n@each $value in $align-content-values\n .is-align-content-#{$value}\n align-content: $value !important\n\n$align-items-values: stretch, flex-start, flex-end, center, baseline, start, end, self-start, self-end\n@each $value in $align-items-values\n .is-align-items-#{$value}\n align-items: $value !important\n\n$align-self-values: auto, flex-start, flex-end, center, baseline, stretch\n@each $value in $align-self-values\n .is-align-self-#{$value}\n align-self: $value !important\n\n$flex-operators: grow, shrink\n@each $operator in $flex-operators\n @for $i from 0 through 5\n .is-flex-#{$operator}-#{$i}\n flex-#{$operator}: $i !important\n","@import \"../utilities/mixins\"\n\n.is-clearfix\n +clearfix\n\n.is-pulled-left\n float: left !important\n\n.is-pulled-right\n float: right !important\n","@import \"../utilities/mixins\"\n\n.is-radiusless\n border-radius: 0 !important\n\n.is-shadowless\n box-shadow: none !important\n\n.is-clickable\n cursor: pointer !important\n pointer-events: all !important\n\n.is-unselectable\n @extend %unselectable\n",".is-clipped\n overflow: hidden !important\n","@import \"../utilities/mixins\"\n\n.is-overlay\n @extend %overlay\n\n.is-relative\n position: relative !important\n",".is-marginless\n margin: 0 !important\n\n.is-paddingless\n padding: 0 !important\n\n$spacing-shortcuts: (\"margin\": \"m\", \"padding\": \"p\") !default\n$spacing-directions: (\"top\": \"t\", \"right\": \"r\", \"bottom\": \"b\", \"left\": \"l\") !default\n$spacing-horizontal: \"x\" !default\n$spacing-vertical: \"y\" !default\n$spacing-values: (\"0\": 0, \"1\": 0.25rem, \"2\": 0.5rem, \"3\": 0.75rem, \"4\": 1rem, \"5\": 1.5rem, \"6\": 3rem, \"auto\": auto) !default\n\n@each $property, $shortcut in $spacing-shortcuts\n @each $name, $value in $spacing-values\n // All directions\n .#{$shortcut}-#{$name}\n #{$property}: $value !important\n // Cardinal directions\n @each $direction, $suffix in $spacing-directions\n .#{$shortcut}#{$suffix}-#{$name}\n #{$property}-#{$direction}: $value !important\n // Horizontal axis\n @if $spacing-horizontal != null\n .#{$shortcut}#{$spacing-horizontal}-#{$name}\n #{$property}-left: $value !important\n #{$property}-right: $value !important\n // Vertical axis\n @if $spacing-vertical != null\n .#{$shortcut}#{$spacing-vertical}-#{$name}\n #{$property}-top: $value !important\n #{$property}-bottom: $value !important\n","@import \"../utilities/mixins\"\n\n=typography-size($target:'')\n @each $size in $sizes\n $i: index($sizes, $size)\n .is-size-#{$i}#{if($target == '', '', '-' + $target)}\n font-size: $size !important\n\n+typography-size()\n\n+mobile\n +typography-size('mobile')\n\n+tablet\n +typography-size('tablet')\n\n+touch\n +typography-size('touch')\n\n+desktop\n +typography-size('desktop')\n\n+widescreen\n +typography-size('widescreen')\n\n+fullhd\n +typography-size('fullhd')\n\n$alignments: ('centered': 'center', 'justified': 'justify', 'left': 'left', 'right': 'right')\n\n@each $alignment, $text-align in $alignments\n .has-text-#{$alignment}\n text-align: #{$text-align} !important\n\n@each $alignment, $text-align in $alignments\n +mobile\n .has-text-#{$alignment}-mobile\n text-align: #{$text-align} !important\n +tablet\n .has-text-#{$alignment}-tablet\n text-align: #{$text-align} !important\n +tablet-only\n .has-text-#{$alignment}-tablet-only\n text-align: #{$text-align} !important\n +touch\n .has-text-#{$alignment}-touch\n text-align: #{$text-align} !important\n +desktop\n .has-text-#{$alignment}-desktop\n text-align: #{$text-align} !important\n +desktop-only\n .has-text-#{$alignment}-desktop-only\n text-align: #{$text-align} !important\n +widescreen\n .has-text-#{$alignment}-widescreen\n text-align: #{$text-align} !important\n +widescreen-only\n .has-text-#{$alignment}-widescreen-only\n text-align: #{$text-align} !important\n +fullhd\n .has-text-#{$alignment}-fullhd\n text-align: #{$text-align} !important\n\n.is-capitalized\n text-transform: capitalize !important\n\n.is-lowercase\n text-transform: lowercase !important\n\n.is-uppercase\n text-transform: uppercase !important\n\n.is-italic\n font-style: italic !important\n \n.is-underlined\n text-decoration: underline !important\n\n.has-text-weight-light\n font-weight: $weight-light !important\n.has-text-weight-normal\n font-weight: $weight-normal !important\n.has-text-weight-medium\n font-weight: $weight-medium !important\n.has-text-weight-semibold\n font-weight: $weight-semibold !important\n.has-text-weight-bold\n font-weight: $weight-bold !important\n\n.is-family-primary\n font-family: $family-primary !important\n\n.is-family-secondary\n font-family: $family-secondary !important\n\n.is-family-sans-serif\n font-family: $family-sans-serif !important\n\n.is-family-monospace\n font-family: $family-monospace !important\n\n.is-family-code\n font-family: $family-code !important\n","@import \"../utilities/mixins\"\n\n$displays: 'block' 'flex' 'inline' 'inline-block' 'inline-flex'\n\n@each $display in $displays\n .is-#{$display}\n display: #{$display} !important\n +mobile\n .is-#{$display}-mobile\n display: #{$display} !important\n +tablet\n .is-#{$display}-tablet\n display: #{$display} !important\n +tablet-only\n .is-#{$display}-tablet-only\n display: #{$display} !important\n +touch\n .is-#{$display}-touch\n display: #{$display} !important\n +desktop\n .is-#{$display}-desktop\n display: #{$display} !important\n +desktop-only\n .is-#{$display}-desktop-only\n display: #{$display} !important\n +widescreen\n .is-#{$display}-widescreen\n display: #{$display} !important\n +widescreen-only\n .is-#{$display}-widescreen-only\n display: #{$display} !important\n +fullhd\n .is-#{$display}-fullhd\n display: #{$display} !important\n\n.is-hidden\n display: none !important\n\n.is-sr-only\n border: none !important\n clip: rect(0, 0, 0, 0) !important\n height: 0.01em !important\n overflow: hidden !important\n padding: 0 !important\n position: absolute !important\n white-space: nowrap !important\n width: 0.01em !important\n\n+mobile\n .is-hidden-mobile\n display: none !important\n\n+tablet\n .is-hidden-tablet\n display: none !important\n\n+tablet-only\n .is-hidden-tablet-only\n display: none !important\n\n+touch\n .is-hidden-touch\n display: none !important\n\n+desktop\n .is-hidden-desktop\n display: none !important\n\n+desktop-only\n .is-hidden-desktop-only\n display: none !important\n\n+widescreen\n .is-hidden-widescreen\n display: none !important\n\n+widescreen-only\n .is-hidden-widescreen-only\n display: none !important\n\n+fullhd\n .is-hidden-fullhd\n display: none !important\n\n.is-invisible\n visibility: hidden !important\n\n+mobile\n .is-invisible-mobile\n visibility: hidden !important\n\n+tablet\n .is-invisible-tablet\n visibility: hidden !important\n\n+tablet-only\n .is-invisible-tablet-only\n visibility: hidden !important\n\n+touch\n .is-invisible-touch\n visibility: hidden !important\n\n+desktop\n .is-invisible-desktop\n visibility: hidden !important\n\n+desktop-only\n .is-invisible-desktop-only\n visibility: hidden !important\n\n+widescreen\n .is-invisible-widescreen\n visibility: hidden !important\n\n+widescreen-only\n .is-invisible-widescreen-only\n visibility: hidden !important\n\n+fullhd\n .is-invisible-fullhd\n visibility: hidden !important\n","@import \"../utilities/mixins\"\n\n$hero-body-padding: 3rem 1.5rem !default\n$hero-body-padding-tablet: 3rem 3rem !default\n$hero-body-padding-small: 1.5rem !default\n$hero-body-padding-medium: 9rem 4.5rem !default\n$hero-body-padding-large: 18rem 6rem !default\n\n$hero-colors: $colors !default\n\n// Main container\n.hero\n align-items: stretch\n display: flex\n flex-direction: column\n justify-content: space-between\n .navbar\n background: none\n .tabs\n ul\n border-bottom: none\n // Colors\n @each $name, $pair in $hero-colors\n $color: nth($pair, 1)\n $color-invert: nth($pair, 2)\n &.is-#{$name}\n background-color: $color\n color: $color-invert\n a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),\n strong\n color: inherit\n .title\n color: $color-invert\n .subtitle\n color: bulmaRgba($color-invert, 0.9)\n a:not(.button),\n strong\n color: $color-invert\n .navbar-menu\n +touch\n background-color: $color\n .navbar-item,\n .navbar-link\n color: bulmaRgba($color-invert, 0.7)\n a.navbar-item,\n .navbar-link\n &:hover,\n &.is-active\n background-color: bulmaDarken($color, 5%)\n color: $color-invert\n .tabs\n a\n color: $color-invert\n opacity: 0.9\n &:hover\n opacity: 1\n li\n &.is-active a\n color: $color !important\n opacity: 1\n &.is-boxed,\n &.is-toggle\n a\n color: $color-invert\n &:hover\n background-color: bulmaRgba($scheme-invert, 0.1)\n li.is-active a\n &,\n &:hover\n background-color: $color-invert\n border-color: $color-invert\n color: $color\n // Modifiers\n @if type-of($color) == 'color'\n &.is-bold\n $gradient-top-left: darken(saturate(adjust-hue($color, -10deg), 10%), 10%)\n $gradient-bottom-right: lighten(saturate(adjust-hue($color, 10deg), 5%), 5%)\n background-image: linear-gradient(141deg, $gradient-top-left 0%, $color 71%, $gradient-bottom-right 100%)\n +mobile\n .navbar-menu\n background-image: linear-gradient(141deg, $gradient-top-left 0%, $color 71%, $gradient-bottom-right 100%)\n // Sizes\n &.is-small\n .hero-body\n padding: $hero-body-padding-small\n &.is-medium\n +tablet\n .hero-body\n padding: $hero-body-padding-medium\n &.is-large\n +tablet\n .hero-body\n padding: $hero-body-padding-large\n &.is-halfheight,\n &.is-fullheight,\n &.is-fullheight-with-navbar\n .hero-body\n align-items: center\n display: flex\n & > .container\n flex-grow: 1\n flex-shrink: 1\n &.is-halfheight\n min-height: 50vh\n &.is-fullheight\n min-height: 100vh\n\n// Components\n\n.hero-video\n @extend %overlay\n overflow: hidden\n video\n left: 50%\n min-height: 100%\n min-width: 100%\n position: absolute\n top: 50%\n transform: translate3d(-50%, -50%, 0)\n // Modifiers\n &.is-transparent\n opacity: 0.3\n // Responsiveness\n +mobile\n display: none\n\n.hero-buttons\n margin-top: 1.5rem\n // Responsiveness\n +mobile\n .button\n display: flex\n &:not(:last-child)\n margin-bottom: 0.75rem\n +tablet\n display: flex\n justify-content: center\n .button:not(:last-child)\n +ltr-property(\"margin\", 1.5rem)\n\n// Containers\n\n.hero-head,\n.hero-foot\n flex-grow: 0\n flex-shrink: 0\n\n.hero-body\n flex-grow: 1\n flex-shrink: 0\n padding: $hero-body-padding\n +tablet\n padding: $hero-body-padding-tablet\n","@import \"../utilities/mixins\"\n\n$section-padding: 3rem 1.5rem !default\n$section-padding-desktop: 3rem 3rem !default\n$section-padding-medium: 9rem 4.5rem !default\n$section-padding-large: 18rem 6rem !default\n\n.section\n padding: $section-padding\n // Responsiveness\n +desktop\n padding: $section-padding-desktop\n // Sizes\n &.is-medium\n padding: $section-padding-medium\n &.is-large\n padding: $section-padding-large\n","@import \"../utilities/derived-variables\"\n\n$footer-background-color: $scheme-main-bis !default\n$footer-color: false !default\n$footer-padding: 3rem 1.5rem 6rem !default\n\n.footer\n background-color: $footer-background-color\n padding: $footer-padding\n @if $footer-color\n color: $footer-color\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/blog/5-ways-to-install-node-js/index.html b/blog/5-ways-to-install-node-js/index.html new file mode 100644 index 0000000..7b7c2bc --- /dev/null +++ b/blog/5-ways-to-install-node-js/index.html @@ -0,0 +1,663 @@ +5 Ways to install Node.js

5 Ways to install Node.js

Published by Luciano Mammino's profile picture Luciano Mammino on  Thu, 24 Dec 2020 18:30:00 GMT

In this article, we will explore some of the most common ways to install Node.js in your development system. We will see how to install Node.js using the official installer for various platforms, how to use a Node.js version manager such as n or nvm and, finally, we will also see how to compile and install Node.js from source. Along the way, we will try to disclose one or two tips to get you even more productive with Node.js!

Let's get started!

Which option should I pick?

There are many different ways to install Node.js and every one of them comes with its own perks and drawbacks. In this article, we will try to explore the most common ones and by the end of it, you should have a good understanding of which ones should be more suitable for you.

TLDR;

  • Use nvm or n if you develop with Node.js frequently and you expect to be needing to switch Node.js version while moving from one project to another or to debug potential compatibility issues in your project or library.
  • Use the system package manager like apt, brew or winget if you tend to install all your software this way and if you don't expect to be needing to switch or upgrade Node.js version too often.
  • Install Node.js from source if you are an advanced user and if you want to contribute back to Node.js itself.
  • Use the official Node.js installer if you don't fall in any of the previous options...

What other people seem to like

Before writing this article, I was actually curious to find out what are the options that most folks in my network prefer. For this reason, I run a poll on Twitter. In this poll I asked how you prefer to install Node.js and provided 4 options:

  • Official Installer
  • Version manager (nvm or n)
  • Package Manager (apt, brew, etc.)
  • From source

The results are quite interesting:

Install Node.js Twitter poll results

It seems quite obvious that people in my network, mostly fellow software engineers, prefer to use version managers such as nvm or n.

The second place (actually very tight with the third one) is the official installer, followed by a system package manager and, last one, installing Node.js from source.

LTS and stable releases

Before moving on and exploring all the different installation options, it is definitely worth spending few words to learn about the types of release the Node.js project maintains.

Node.js offers 2 main release lines:

  • Stable (or Current): every new major Node.js release is considered "Current" for the first 6 months after the publish date. The idea is to give library authors the time to test their compatibility with the new release and do any necessary change. After the 6 months period, all the odd release numbers (9, 11, 13, 15, etc.) move to the state of Unsupported, while even releases (10, 12, 14, etc.) are promoted to Long Term Support (or "LTS").
  • LTS: releases marked as "Long Term Support" get critical bug fixes for a total of 30 months since the initial publish date. This makes LTS releases particularly suitable for production deployments. The most recent LTS is also called Active LTS, while previous LTS versions (still under the 30 months support timeframe) are called Maintenance LTS.

Finally, the release coming from the current master branch is considered Unstable. This is generally a release dedicated to people maintaining Node.js or developers who want to explore new experimental features that haven't been yet included in any of the major releases.

Node.js publishes an official timeline of current and future releases. At the time of writing (December 2020), this how the timeline looks like:

Node.js release timeline

If you are still wondering which release should you use, going with the Active LTS is almost always the best choice, especially if you are building production applications.

Install Node.js using n

Since installing Node.js using a version manager seems to be the favourite option (and it's also my personal favourite!) let's start with it.

My favourite Node.js version manager is n by TJ Holowaychuk. The reason why I like it is because it is quite simple to install and use and it is generally up to date with the latest releases of Node.js. The main issue with it is that it does not support Windows, so if Windows is your operative system, this is not an option for you!

Let's see how to install n:

If you are on macOS and you have brew (Homebrew) installed, the simplest way to install n is to just do it with brew:

brew install n

Alternatively, you can use the custom install script:

curl -L https://git.io/n-install | bash

Note: if you are concerned about running a script downloaded from the web (as you should because curl | bash might be dangerous), you can always download the script first, READ IT, and then run it locally...

If all goes well, you should now be able to use the n executable from your shell.

These are some of the commands you can run:

# shows the version of `n` installed in your system
+n --version
+
+# installs the latest LTS release of Node.js
+n lts
+
+# lists all the versions of Node.js currently available
+n list
+
+# install the given version of Node.js and switch to it
+n <some_version>

Or you can simply run:

n

For an interactive prompt that will show you all the available versions, highlight the ones you have already installed and let you pick the version you want to switch to.

n Node.js version manager in action

In summary, this is where n shines or falls short:

  • 👎 No official support for Windows
  • 👍 Very easy to install on macOS and unix systems
  • 👍 Very easy to keep your Node.js install up to date and switch version on demand
  • 👍 It keeps all the installed versions cached, so you can switch quickly between versions (no full re-install)
  • 👍 Allows to keep the setup local to the user so you don't have to use admin permission to install global packages

Install Node.js using nvm

With more than 45 thousand stars on GitHub, nvm, which stands for "Node.js Version Manager" (no surprises!), is probably the most famous Node.js version manager currently available.

nvm works on any POSIX-compliant shell (sh, dash, ksh, zsh, bash, etc.) and it has been strongly tested against the following systems: unix, macOS, and windows WSL (if you are on Windows, you can also check out nvm-windows).

The easiest way to install nvm on your system is to use the official installer script:

VERSION=v0.37.2
+curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/${VERSION}/install.sh" | bash

Note: At the time of writing, version v0.37.2 is the latest version available. Make sure to check out if there is any new version available if you are installing nvm following this tutorial.

Once nvm is installed in your system, here are some examples showing what you can do with it:

# installs the latest version of Node.js
+nvm install node
+
+# installs the latest LTS version of Node.js
+nvm install --lts
+
+# installs a specific version of Node.js
+nvm install "10.10.0"
+
+# switch to a specific version of Node.js
+nvm use "8.9.1"
+
+# runs a specific script with a given version of Node.js (no switch)
+nvm exec "4.2" node somescript.js
+
+# shows the full path where a given version of Node.js was installed
+nvm which "4.2"
+
+# lists all the versions of Node.js available
+nvm ls

One great thing about nvm is that it allows to specify the Node.js version you want to use for a given project.

For instance, if you are working on a project that requires you to use Node.js 10.10 you can do the following (in the root folder of the project):

echo "10.10" > .nvmrc

Then every time you work on that project, you only need to run:

nvm use

Which should print something like this:

Found '/path/to/project/.nvmrc' with version <10.10>
+Now using node v10.10.1 (npm v6.7.3)
+

At this point, you can be sure that you working using the correct Node.js version for your project.

If you don't want to do manually, you can enable deeper shell integration to make this happen automatically when you cd into a folder that has a .nvmrc file.

PRO tip: You can also do that by using asdf, a meta version manager that offers a unified interface for various programming languages and version managers (including Node.js, of course).

Finally, here are some pros and cons of nvm:

  • 👍 Most popular version manager for Node.js with a large community of users.
  • 👍 Very easy to install on POSIX systems.
  • 👍 It allows for easy (and even automated) switch of Node.js version based on the project you are working on.
  • 👍 It keeps all the installed versions cached, so you can switch quicly between versions (no full re-install)
  • 👍 You can run once off commands on a given version of Node.js without having to switch the entire system to that version.
  • 👎 You might have to take a bit of time to go through the documentation and make sure you install it and use it correctly.

Note: if you like to use version managers like n or nvm, you can also check out volta.sh, another interesting alternative in this space, which defines itself as "The Hassle-Free JavaScript Tool Manager".

Install Node.js using the official installer

The second most common way to install Node.js is through one of the official installers or the pre-compiled binaries.

Official installers are available on the official Node.js website for Windows and macOS and they cover the latest Active LTS release and the latest Current release.

The installer for Windows is an executable .msi installer, while the one for macOS is a .pkg one.

These installers behave and look like most of the installers you see while installing software on Windows or macOS. You will be presented with clickable UI which will allow you to customise and install Node.js into your system.

Install Node.js using the official macOS installer

This is probably the easiest way to install Node.js as you don't need to be a POSIX expert or do any kind of manual configuration. The installer will suggest sensible defaults to you and allow you to customise the main parameters (e.g. installation path).

If you are running a unix system, there is no official graphical installer available, but the official Node.js download page offers a set of pre-compiled binaries for most architectures (32-bit, 64-bit, ARMv7 and ARMv8) for Linux, Windows and macOS.

Install Node.js using the official macOS installer

With the binary distribution, it is up to you to copy the necessary files in the right place. A version manager tool such as nvm and n makes things simple, because it takes care of downloading the correct binary release for the desired version (and for your system), then it places the files in the correct folder as expected by your operative system. If you choose to download the binaries manually, all the wiring is up to you.

While installing Node.js using the official installers is probably the simplest option, doing it using the binaries is a lot more complicated and definitely more complicated than using a version manager.

If you still want to go down this path, make sure to check out the official tutorial for installing from Node.js pre-compiled binaries.

It is definitely worth mentioning that the official installer is not the only option. NodeSource maintains alternative installers for Debian, Red Hat, macOS and Windows. If you are interested in this approach checkout NodeSource Node.js Binary distributions page.

To summarise, these are the main pros and cons of Node.js installers and binary distributions:

  • 👍 Installers are quite easy to use and they don't require specific POSIX experience.
  • 👎 Hard to switch between version or upgrade. If you want to do that, you basically have to download the specific installer for the desired version and run through the full process again.
  • 👎 Installer often will install Node.js as admin, which means that you can't install global packages unless you do that as admin.
  • 👎 Binary packages require you to manually manage all the files and configuration.

Install Node.js using a package manager

If you are the kind of person that loves to install and manage all the software in your device using system package managers such as apt (Debian / Ubuntu), brew (macOS), or winget (Windows), installing Node.js through a package manager is definitely an option.

A word of warning though, the various Node.js packages in every package repository are not officially maintained by the Node.js core team, so your mileage might vary quite a lot. This also means that you might not have fresh releases available straight away in your package manager of choice.

The Node.js core team has compiled an official documentation page on how to install Node.js using the most common system package managers.

Let's see here a summary for the most common options:

# Homebrew (macOS)
+brew install node
+
+# Arch Linux
+pacman -S nodejs npm
+
+# CentOS, Fedora and Red Hat Enterprise Linux
+dnf module list nodejs
+
+# Debian and Ubuntu based Linux distributions
+apt-get install -y nodejs
+
+# FreeBSD
+pkg install node
+
+# Gentoo
+emerge nodejs
+
+# Winget (Windows)
+winget install -e --id OpenJS.Nodejs
+
+# Chocolatey (Windows)
+cinst nodejs.install
+
+# Scoop (Windows)
+scoop install nodejs

In short, this is "the good" and "the bad" of following this approach:

  • 👍 Familiar approach if you install software often using your system package manager.
  • 👎 Latest Node.js versions might not be immediately available in your package manager of choice. Some versions might not be available at all.
  • 👎 In most cases, Node.js is installed as super user, which makes it harder to install global packages with npm.

Install Node.js from source

If you are brave enough to be willing to build and install Node.js from source, your first stop should be the official documentation on how to build Node.js from source.

Here is a brief summary of all the steps involved:

  1. Install the necessary build dependencies (C++ compiler and build toolchains) for your target system.
  2. Install Python (used by the build process).
  3. Download the source code from the official repository.
  4. Launch ./configure and then make.
  5. Test your compiled version with make test.
  6. Install it with make install.

If all went well, you should have the node binary available on your system and be able to run:

node --version

Finally, here is the usual summary of pros and cons:

  • 👍 You can install any version of Node.js, including master or even work in progress from a dev branch or a PR. You can even play around with custom changes and get to the point where you might decide to contribute back to Node.js.
  • 👍 You have full control on how to compile and install Node.js and don't have to follow pre-defined structures.
  • 👎 You might need to install a bunch of additional build requirements (compilers, build tools, etc.) before you can even start with the process.
  • 👎 Definitely the most complicated and the slowest way to get Node.js in your machine.

Node.js with Docker

If you just want to "play" a bit with a Node.js REPL, you don't need to install Node.js in your system. If you have docker installed in your system, running a Node.js REPL in a container is as easy as running:

docker run -it node

Here's a super quick demo:

Running a Node.js REPL using Docker

If you want to run a shell in a container with Node.js and npm installed, then you can do the following:

docker run -it node bash

This way you can install third-party modules using npm, create your own scripts and run them with node. When you close the session the container and all the generated files will be destroyed.

This is the perfect environment for quick and dirty experiments.

Note, that you can also use Docker as a complete environment for development and not just for quick tests. Docker is actually great for keeping different Node.js version and other dependencies isolated on a per-project basis. Exploring this setup goes beyond the scope of this article, but there is ton of reference on the web about how you might use Docker for Node.js development.

Node.js online

But what if you don't have docker installed and still want to have an environment where you can write and run some Node.js code?

Well, there is no shortage of platforms online that will give you a Node.js environment and an IDE that you can use to write and run JavaScript online.

These environments often offer delightful additional features like collaborative edit and the possibility to host and share your applications.

Here's a non-exhaustive list of services that you might want to try if you just need a quick way to write and share some Node.js examples:

Most of these services offer a quite generous free plan, so you only need to sign up to start coding!

Conclusion

This concludes our list of ways to install Node.js. At this point, I hope you feel comfortable enough picking one of the options suggested here and that along the way you learned a trick or two.

If you enjoyed this article please consider sharing it and don't hesitate to reach out to me on Twitter. I am quite curious to find out what is your favourite way to install Node.js and why!

Until next time!

Credits

This article was possible only thanks to the great support and feedback of some amazing engineers. Here are some of the names that helped me (and sorry if I am forgetting someone): @_Don_Quijote_, @GiuseppeMorelli, @oliverturner, @aetheon, @dottorblaster, @bcomnes & @wa7son.

Node.js Design Patterns

Get the FREE chapter!

With this 54 pages long chapter you will learn how to implement and leverage some of the most well known behavioural design patterns in the context of Node.js: the Strategy pattern, the State pattern, the Template pattern, the Iterator pattern, the Middleware pattern, and the Command pattern.

    Node.js Design Patterns chapter 9 behavioral design patterns
    \ No newline at end of file diff --git a/blog/5-ways-to-install-node-js/og_installing-node-js.jpg b/blog/5-ways-to-install-node-js/og_installing-node-js.jpg new file mode 100644 index 0000000..8029742 Binary files /dev/null and b/blog/5-ways-to-install-node-js/og_installing-node-js.jpg differ diff --git a/blog/index.html b/blog/index.html new file mode 100644 index 0000000..8fe6793 --- /dev/null +++ b/blog/index.html @@ -0,0 +1,28 @@ +Node.js Design Patterns Blog

    Node.js Design Patterns Blog

    From the authors of Node.js Design Patterns, useful bits to enrich your Node.js knowledge

    Subscribe
    \ No newline at end of file diff --git a/blog/javascript-async-iterators/index.html b/blog/javascript-async-iterators/index.html new file mode 100644 index 0000000..3cc6c5b --- /dev/null +++ b/blog/javascript-async-iterators/index.html @@ -0,0 +1,904 @@ +JavaScript async iterators

    JavaScript async iterators

    Published by Luciano Mammino's profile picture Luciano Mammino on  Tue, 04 May 2021 13:10:00 GMT

    Did you know that JavaScript offers a few protocols to allow iteration over certain objects? Of course, we know we can easily iterate over arrays, but with these protocols, you can make your own custom objects iterable as well.

    When you have an iterable object representing a collection, you can use the for...of syntax to iterate over every single item of the collection.

    But what if an object abstracts data generated asynchronously? For instance, think of an abstraction that allows us to fetch data from a paginated API, or think about some records consumed in batches from a database, or something as simple as a countdown timer. Well in these cases you can use the for await...of syntax!

    In this article, we will learn more about the iterator and the iterable protocol (and their async counterparts) and we will see how to create custom objects that can expose their internal data in an ergonomic and idiomatic way.

    JavaScript iteration with for...of

    With ECMAScript 2015, JavaScript got the for...of syntax. This syntax provides a very easy way to iterate over collections, such as arrays, string, sets, and maps.

    If you have never seen this syntax in action here are some examples:

    const judokas = [
    +  'Driulis Gonzalez Morales',
    +  'Ilias Iliadis',
    +  'Tadahiro Nomura',
    +  'Anton Geesink',
    +  'Teddy Riner',
    +  'Ryoko Tani'
    +]
    +
    +for (const judoka of judokas) {
    +  console.log(judoka)
    +}

    In the example above, we are iterating over an array using the for...of syntax. If we run this code, this is what we will get as output:

    Driulis Gonzalez Morales
    +Ilias Iliadis
    +Tadahiro Nomura
    +Anton Geesink
    +Teddy Riner
    +Ryoko Tani

    The same syntax works also for iterating over the characters of a string:

    const judoka = 'Ryoko Tani'
    +
    +for (const char of judoka) {
    +  console.log(char)
    +}

    The above will print:

    R
    +y
    +o
    +k
    +o
    +
    +T
    +a
    +n
    +i

    And we can even use this for Set and Map:

    const medals = new Set(['gold', 'silver', 'bronze'])
    +
    +for (const medal of medals) {
    +  console.log(medal)
    +}

    Which is going to output:

    gold
    +silver
    +bronze

    Map is especially interesting because we can use destructuring to iterate over key-value pairs:

    const medallists = new Map([
    +  ['Teddy Riner', 33],
    +  ['Driulis Gonzalez Morales', 16],
    +  ['Ryoko Tani', 16],
    +  ['Ilias Iliadis', 15]
    +])
    +
    +for (const [judoka, medals] of medallists) {
    +  console.log(`${judoka} has won ${medals} medals`)
    +}

    The above example will output:

    Teddy Riner has won 33 medals
    +Driulis Gonzalez Morales has won 16 medals
    +Ryoko Tani has won 16 medals
    +Ilias Iliadis has won 15 medals

    Finally, if you want to iterate over the key-value pairs of an object literal using the for...of syntax, we can do that by using the helper Object.entries:

    const medallists = {
    +  'Teddy Riner': 33,
    +  'Driulis Gonzalez Morales': 16,
    +  'Ryoko Tani': 16,
    +  'Ilias Iliadis': 15
    +}
    +
    +for (const [judoka, medals] of Object.entries(medallists)) {
    +  console.log(`${judoka} has won ${medals} medals`)
    +}

    The code snippet above will produce the same output as the previous example.

    What's interesting here is that, if we try to use the for...of syntax directly on the object medallists (without Object.entries), we get the following error:

    for (const [judoka, medals] of medallists) {
    +                               ^
    +
    +TypeError: medallists is not iterable
    +    at Object. (.../05-for-of-object.js:8:32)
    +    at Module._compile (node:internal/modules/cjs/loader:1108:14)
    +    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)
    +    at Module.load (node:internal/modules/cjs/loader:988:32)
    +    at Function.Module._load (node:internal/modules/cjs/loader:828:14)
    +    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)
    +    at node:internal/main/run_main_module:17:47

    Let's read this error once again: medallists is not iterable!

    Yeah, the error is clear: a regular JavaScript object is not iterable, while arrays, strings, maps, and sets are!

    But what does it mean for an object to be iterable?

    During the rest of this article, we will learn how JavaScript knows if a given object is iterable and how we can make our own custom iterable objects.

    But first let's quickly take a look at how we can use async iterators.

    JavaScript iteration with for await...of

    ECMAScript 2018 introduced a new syntax called for await...of. This syntax is somewhat similar to for...of but it allows us to iterate over asynchronous collections where data becomes available over time in an asynchronous fashion.

    A good use case for this syntax is reading data from a remote source like a database.

    Here's an example that uses AWS DynamoDB and the for await...of syntax to list all the tables available in our account:

    import { DynamoDBClient, paginateListTables } from '@aws-sdk/client-dynamodb'
    +
    +const client = new DynamoDBClient({}); 
    +
    +for await (const page of paginateListTables({ client }, {})) {
    +  // page.TableNames is an array of table names
    +  for (const tableName of page.TableNames) {
    +    console.log(tableName)
    +  }
    +}

    In the example above, paginateListTables will produce pages over time, and every page will contain a portion of the data (information about all the available tables).

    This approach allows us to list hundreds or even thousands of table names efficiently. In fact, the data can be printed as soon as it is available and we don't have to wait for the entire data set to be received.

    Note how we are combining here for await...of and for...of. Pages become available over time asynchronously, so we need to use for await...of to iterate over this data. Every page contains an array of table names, so in this case, to iterate over every single table name we can simply use for...of.

    In general, we can use the for await...of syntax with objects that are async iterable.

    In the next few sections, we will see how JavaScript classifies a given object as async iterable and how we can build our custom async iterable objects.

    The JavaScript iterator protocol

    JavaScript defines a number of protocols that are used to make objects iterable (or async iterable).

    The first one we are going to start with is the iterator protocol.

    In JavaScript, an object is an iterator if it has a next() method. Every time you call it, it returns an object with the keys done (boolean) and value.

    Let's see an example. Let's say we want to build a countdown. This countdown is initialized with a positive integer and it will produce all the numbers from that value down to 0:

    function createCountdown (from) {
    +  let nextVal = from
    +  return {
    +    next () {
    +      if (nextVal < 0) {
    +        return { done: true }
    +      }
    +
    +      return { 
    +        done: false,
    +        value: nextVal--
    +      }
    +    }
    +  }
    +}

    In this example, createCountdown is a simple factory function. From this function, we return an iterator object. In fact, the object implements the iterator protocol defined above. Note that the returned object implements a next() method and that this method returns either {done: true} or {done: false, value: someNumber}.

    Let's see now how can we use this object to extract all the values:

    const countdown = createCountdown(3)
    +console.log(countdown.next()) // { done: false, value: 3 }
    +console.log(countdown.next()) // { done: false, value: 2 }
    +console.log(countdown.next()) // { done: false, value: 1 }
    +console.log(countdown.next()) // { done: false, value: 0 }
    +console.log(countdown.next()) // { done: true }

    Or if we want to use this object with a more generic loop:

    const countdown = createCountdown(3)
    +let result = countdown.next()
    +while (!result.done) {
    +  console.log(result.value)
    +  result = countdown.next()
    +}

    The code above will produce the following output:

    3
    +2
    +1
    +0

    This is not the most intuitive or ergonomic approach, but the iterator protocol is the basic building block for the iterable protocol which enables the for...of syntax.

    The JavaScript iterable protocol

    As we said, the iterable protocol builds on top of the iterator protocol that we just explored. Let's see how:

    An object is iterable if it implements the @@iterator method, a zero-argument function that returns an iterator.

    Note that with @@iterator we indicate a symbol that is accessed with the global value Symbol.iterator.

    Can we make our countdown example iterable? We certainly can!

    function createCountdown (from) {
    +  let nextVal = from
    +  return {
    +    [Symbol.iterator]: () => ({
    +      next () {
    +        if (nextVal < 0) {
    +          return { done: true }
    +        }
    +
    +        return { done: false, value: nextVal-- }
    +      }
    +    })
    +  }
    +}

    In this new example, our factory function returns an iterable object. This object in fact has a method referenced with Symbol.iterator that returns an iterator object.

    At this point, once we have an instance of a countdown, we can use the for..of syntax to iterate over all the numbers from the countdown:

    const countdown = createCountdown(3)
    +
    +for (const value of countdown) {
    +  console.log(value)
    +}

    The example above will output:

    3
    +2
    +1
    +0

    Hooray! Now we know how to make iterators and iterable objects. If you find the two terms confusing, don't worry, that's quite common!

    One good way to try to remember and distinguish these 2 concepts is the following:

    • An iterator is a lower-level object that allows us to iterate over some data set using next()
    • An iterable is an object on which we can iterate over using the for...of syntax.

    Using JavaScript generator functions

    An interesting detail is that JavaScript generator functions produce iterators.

    This allows us to simplify the way we can implement both the iterator and the iterable protocols.

    Let's see how can we rewrite our countdown logic using a generator function:

    function * createCountdown (from) {
    +  for (let i = from; i >= 0; i--) {
    +    yield i
    +  }
    +}

    If we call createCountdown(3) we get an iterator. So this is perfectly compatible with our previous iterator implementation:

    const countdown = createCountdown(3)
    +console.log(countdown.next()) // { value: 3, done: false }
    +console.log(countdown.next()) // { value: 2, done: false }
    +console.log(countdown.next()) // { value: 1, done: false }
    +console.log(countdown.next()) // { value: 0, done: false }
    +console.log(countdown.next()) // { value: undefined, done: true }

    Similarly, we can use generators to implement the iterable protocol:

    function createCountdown (from) {
    +  return {
    +    [Symbol.iterator]: function * () {
    +      for (let i = from; i >= 0; i--) {
    +        yield i
    +      }
    +    }
    +  }
    +}

    And this factory will produce iterable objects, exactly as before:

    const countdown = createCountdown(3)
    +
    +for (const value of countdown) {
    +  console.log(value)
    +}

    In general, generators can be considered great syntactic sugars to write iterators.

    The spread syntax for iterable objects

    Another interesting detail is that all iterable objects can be used with the spread syntax.

    The spread syntax looks like ...someIterable and it basically allows us to apply every single element from the iterable to a given context.

    The most common use cases are found with array literals and function calls.

    Let's see a couple of examples:

    const countdown = createCountdown(3)
    +const from5to0 = [5, 4, ...countdown]
    +console.log(from5to0) // [ 5, 4, 3, 2, 1, 0 ]
    +
    +const countdown2 = createCountdown(6)
    +console.log('countdown2 data:', ...countdown2)
    +// countdown2 data: 6 5 4 3 2 1 0

    This is something we see most often with arrays, but it's important to note that any iterable object can be used with the spread syntax.

    The JavaScript async iterator protocol

    Ok, so far we have explored only synchronous iteration protocols. What about async?

    Unsurprisingly, both the iterator protocol and the iterable protocol have their async counterparts!

    Let's start with the async iterator protocol:

    An object is an async iterator if it has a next() method. Every time you call it, it returns a promise that resolves to an object with the keys done (boolean) and value.

    Note how this is quite similar to the synchronous version of the iterator protocol. The main difference here is that the next() function won't return an object straight away. Instead, it will return a promise that will eventually resolve to an object.

    Let's now revisit our countdown example and let's say we want some time to pass before numbers are produced:

    import { setTimeout } from 'timers/promises'
    +
    +function createAsyncCountdown (from, delay = 1000) {
    +  let nextVal = from
    +  return {
    +    async next () {
    +      await setTimeout(delay)
    +      if (nextVal < 0) {
    +        return { done: true }
    +      }
    +
    +      return { done: false, value: nextVal-- }
    +    }
    +  }
    +}

    Note that this time we are using an async function to implement next(). This will make this method immediately return a promise, that will later resolve when we run one of the return statements from within the async function.

    Also, note that here we are using setTimeout from timers/promises, a new core module available from Node.js 16.

    Ok, now we are ready to use this iterator:

    const countdown = createAsyncCountdown(3)
    +console.log(await countdown.next()) // { done: false, value: 3 }
    +console.log(await countdown.next()) // { done: false, value: 2 }
    +console.log(await countdown.next()) // { done: false, value: 1 }
    +console.log(await countdown.next()) // { done: false, value: 0 }
    +console.log(await countdown.next()) // { done: true }

    This works very similarly to its synchronous counterpart with two notable exceptions:

    • We need to use await to wait for the next element to be produced.
    • Between one element and another about 1 second will pass, so this iteration is much slower.

    An example of JavaScript async iterator

    Of course, here we can use generators as well as a nice syntactic sugar:

    import { setTimeout } from 'timers/promises'
    +
    +async function * createAsyncCountdown (from, delay = 1000) {
    +  for (let i = from; i >= 0; i--) {
    +    await setTimeout(delay)
    +    yield i
    +  }
    +}

    This code is more concise and probably more readable, at least to those accustomed to async functions and generator functions.

    The JavaScript async iterable protocol

    Let's now discuss the last iteration protocol: the async iterable protocol!

    An object is an async iterable if it implements the @@asyncIterator method, a zero-argument function that returns an async iterator.

    Note that with @@asyncIterator we indicate a symbol that can be accessed with the global value Symbol.asyncIterator.

    Once again, this definition is quite similar to its synchronous counterpart. The main difference is that this type we have to use Symbol.asyncIterator and that it must return an async iterator.

    Let's revisit our async countdown example:

    import { setTimeout } from 'timers/promises'
    +
    +function createAsyncCountdown (from, delay = 1000) {
    +  return {
    +    [Symbol.asyncIterator]: async function * () {
    +      for (let i = from; i >= 0; i--) {
    +        await setTimeout(delay)
    +        yield i
    +      }
    +    }
    +  }
    +}

    At this point, our createAsyncCountdown returns a valid async iterator, so we can finally use the for await...of syntax:

    const countdown = createAsyncCountdown(3)
    +
    +for await (const value of countdown) {
    +  console.log(value)
    +}

    As you might expect, this code will produce 3, 2, 1 and 0 with a delay:

    An example of JavaScript async iterator

    Great!

    At this point, we know how the JavaScript iteration protocol work and how to create iterator and iterable objects in a synchronous and asynchronous fashion!

    Combining iterator and iterable

    Can an object be both an iterator and an iterable at the same time?

    Yes! Nothing is stopping us from implementing both protocols for a given object. This basically means that @@iterator or @@asyncIterator will have to return the same object as in the following example:

    const iterableIterator = {
    +  next() {
    +    return { done: false, value: "hello" }
    +  },
    +  [Symbol.iterator]() {
    +    return this
    +  }
    +}
    +
    +for (const value of iterableIterator) {
    +  console.log(value) // "hello"
    +}

    The example above will print "hello" endlessly.

    What's even cooler is that generator functions are also iterable. This means that we can greatly simplify our countdown examples.

    Let's see how the syncrhonous countdown would look like:

    function * createCountdown (from) {
    +  for (let i = from; i >= 0; i--) {
    +    yield i
    +  }
    +}

    We don't even need to bother with Symbol.iterator!

    The same goes for the asynchronous version of our countdown:

    import { setTimeout } from 'timers/promises'
    +
    +async function * createAsyncCountdown (from, delay = 1000) {
    +  for (let i = from; i >= 0; i--) {
    +    await setTimeout(delay)
    +    yield i
    +  }
    +}

    And here we don't have to explicitly use Symbol.asyncIterator, in fact, an async generator function is already an async iterable!

    If we decide to use generators, this will help us to write even more concise iterator and iterable objects.

    Using JavaScript iteration protocols with Node.js

    Everything we have been discussing so far is part of the JavaScript specification, but what about Node.js?

    Actually, support for these features looks quite good in Node.js!

    Synchronous iteration protocols have been supported in Node.js for a long time (since Node.js 0.12).

    Recent versions of Node.js (Node.js 10.3) introduced support for async iterators and the for await...of syntax.

    Synchronous iterable objects and the for...of syntax are quite widespread, so in the next sections, we will focus on providing some examples of how you can take advantage of its asynchronous counterpart and the for await...of syntax.

    Node.js readable streams and async iterators

    One interesting detail that needs a bit more visibility is that Node.js Readable streams are async iterable objects since Node.js 11.14.

    This basically means that we can consume data from a Readable stream using for await...of.

    Let's see a simple example of a CLI utility that allows us to read the content of a given file and count the number of bytes:

    import { createReadStream } from 'fs'
    +
    +const sourcePath = process.argv[2]
    +const sourceStream = createReadStream(sourcePath)
    +
    +let bytes = 0
    +for await (const chunk of sourceStream) {
    +  bytes += chunk.length
    +}
    +
    +console.log(`${process.argv[2]}: ${bytes} bytes`)

    The interesting thing is that when we are using this approach the stream is consumed in non-flowing (or paused) mode which can help us to handle backpressure in a very simple way.

    Let's say that we want to write every chunk to a very slow transform stream (that we are going to identify with SlowTransform), this is how we can handle backpressure:

    import { createReadStream } from 'fs'
    +import { once } from 'events'
    +
    +const sourcePath = process.argv[2]
    +const sourceStream = createReadStream(sourcePath)
    +const destStream = new SlowTransform()
    +
    +for await (const chunk of sourceStream) {
    +  const canContinue = destStream.write(chunk)
    +  if (!canContinue) {
    +    // backpressure, now we stop and we need to wait for drain
    +    await once(destStream, 'drain')
    +    // ok now it's safe to resume writing
    +  }
    +}

    Note that having an await inside the for await...of block will effectively pause the iteration. This will stop consuming data from the source stream until the destination stream is drained.

    Converting a Node.js event emitter to an async iterable

    Another interesting use case for async iteration in Node.js is when dealing with repeated events happening over time.

    Events are generally fired by an event emitter and, since version 12.16, Node.js offers an interesting utility to convert a sequence of events into an async iterable.

    We can see a simple example by using the third party module glob which allows us to find files matching a specific glob expression.

    In this example we will find and print all the JavaScript files (.js extension) in the current folder (and subfolders):

    import { on } from 'events'
    +import glob from 'glob'
    +
    +const matcher = glob('**/*.js')
    +
    +for await (const [filePath] of on(matcher, 'match')) {
    +  console.log(filePath)
    +}

    As you can see, we are using on(matcher, 'match') to create an async iterable that will produce a new value every time the matcher instance fires a match event.

    Note that the value produced by this async iterable at every iteration is an array containing all the values contained in the original match event. This is the reason why we need to use destructuring to extract the filePath.

    At this point you might ask: "wait a second, but how do we know, with this approach, when there are no more events to process?"

    And that's a great question... we don't!

    In fact, we are only listening for match events and we don't really have a way to stop the loop.

    If we put any code just after the for await...of loop, that code will never be executed.

    One solution to this problem is the AbortController, which allows us to create an Async Iterable that can be aborted.

    With that, we could listen for the end event on our matcher instance and, once that happens, we can use the AbortController to stop the iteration.

    Let's see some code:

    import { on } from 'events'
    +import glob from 'glob'
    +
    +const matcher = glob('**/*.js')
    +const ac = new global.AbortController()
    +
    +matcher.once('end', () => ac.abort())
    +
    +try {
    +  for await (const [filePath] of on(matcher, 'match', { signal: ac.signal })) {
    +    console.log(`./${filePath}`)
    +  }
    +} catch (err) {
    +  if (!ac.signal.aborted) {
    +    console.error(err)
    +    process.exit(1)
    +  }
    +}
    +
    +console.log('NOW WE GETTING HERE! :)')

    In the code example above, you can see that we are creating a new instance of AbortController by using new global.AbortController().

    Then, we listen for the end event on our matcher and when that happens we invoke abort() on our AbortController instance.

    The last step is to pass the AbortController instance to the on() function. We do that by passing an options object and using the signal option.

    You might have noticed that we also added a try/catch block. This is actually very important. When we stop the iteration using an AbortController this will not simply stop the iteration, but it will raise an exception.

    In this case the exception is expected, so we handle it gracefully. We also want to distinguish the abort exception from other unintended exceptions, so we make sure to check wheter our abort signal was raised, otherwise we exit the program with an error.

    Note that this is a lot of work, so this pattern, while it's cute, might not always give you great benefits compared to simply handling events using regular listeners.

    Consuming paginated data with async iterators

    As we mentioned before with the DynamoDB examples, another great use case for async iteration is when we need to fetch data from a remote paginated dataset. Even more so when we cannot determine how to access the next page until we have fetched the previous one. This is a typical example of asynchronous sequential iteration and it's probably the most adequate use case for async iterators.

    Just to present a very simple example, let's use a free and open-source Star Wars API (happy May 4th everyone!) which allows us to access all the Star Wars characters in a paginated fashion.

    To get data from this API, we can make a GET request to the following endpoint:

    https://swapi.dev/api/people

    This request will respond with a JSON message that looks like this:

    {
    +	"count": 82,
    +	"next": "http://swapi.dev/api/people/?page=2",
    +	"results": [
    +		{
    +			"name": "Sly Moore",
    +			"height": "178",
    +      "...": "more fields...",
    +    },
    +    {
    +      "name": "Another character",
    +			"height": "whatever",
    +      "...": "more fields...",
    +    },
    +    {
    +      "...": "more characters"
    +    }
    +  ]
    +}

    Note that the next field contains the URL that we can use to fetch the data from the following page. All the records for the current page are presented in the results field.

    With these details in mind, this is how we can create a custom client that allows us to fetch all the characters using the for await...of syntax:

    import axios from 'axios'
    +
    +async function * starWarsCharacters () {
    +  let nextUrl = 'https://swapi.dev/api/people'
    +  while (nextUrl) {
    +    const response = await axios.get(nextUrl)
    +    nextUrl = response.data.next
    +    yield response.data.results
    +  }
    +}

    Now we can use this function as follows:

    for await (const page of starWarsCharacters()) {
    +  for (const char of page) {
    +    console.log(char.name)
    +  }
    +}

    If we run this code we should see the following output:

    Luke Skywalker
    +C-3PO
    +R2-D2
    +Darth Vader
    +Leia Organa
    +[... other 77 names]

    Wrapping up

    This concludes our exploration of JavaScript iteration protocols. At this point, you should feel comfortable understanding what the various protocols are and how to use for...of and for await...of effectively in both JavaScript and Node.js.

    These techniques are often ideal to implement synchronous and asynchronous sequential iteration patterns, which makes them very effective tools in our toolbelt.

    If you are interested in learning more patterns and interesting Node.js techniques, consider checking out Node.js Design Patterns. You can grab a free chapter for free by filling the form at the end of this page. Among other things, this free chapter contains some other examples of iteration and async iterators!

    See you at the next post!

    CIAO 👋

    P.S. All the examples presented in this article are available on GitHub at lmammino/javascript-iteration-protocols.

    Thanks to Mario Casciaro for the kind review of this article and to Kelvin Omereshone for finding and fixing a few typos.

    Node.js Design Patterns

    Get the FREE chapter!

    With this 54 pages long chapter you will learn how to implement and leverage some of the most well known behavioural design patterns in the context of Node.js: the Strategy pattern, the State pattern, the Template pattern, the Iterator pattern, the Middleware pattern, and the Command pattern.

      Node.js Design Patterns chapter 9 behavioral design patterns
      \ No newline at end of file diff --git a/blog/javascript-async-iterators/og_javascript-async-iterators.jpg b/blog/javascript-async-iterators/og_javascript-async-iterators.jpg new file mode 100644 index 0000000..784c277 Binary files /dev/null and b/blog/javascript-async-iterators/og_javascript-async-iterators.jpg differ diff --git a/blog/node-js-development-with-docker-and-docker-compose/index.html b/blog/node-js-development-with-docker-and-docker-compose/index.html new file mode 100644 index 0000000..0315724 --- /dev/null +++ b/blog/node-js-development-with-docker-and-docker-compose/index.html @@ -0,0 +1,737 @@ +Node.js development with Docker and Docker Compose

      Node.js development with Docker and Docker Compose

      Published by Giuseppe Morelli's profile picture Giuseppe Morelli on  Wed, 07 Apr 2021 18:00:00 GMT

      In this article, we are going to learn how to use Docker and Docker Compose for Node.js development. We will discuss the main benefits of this approach and explore some interesting examples. While doing that we will also learn what Docker is and why you should consider it, especially if you are developing multiple projects or if you are working in a team.

      What is a Docker container in simple terms?

      To explain what a Docker container is, let's just start by imagining a virtual machine or a virtual server provisioned to run a specific program.

      Virtual machines are great because they allow you to run some useful software in an isolated way and they are easy to distribute. You can create an image and run it in different environments. You can even run it in your own desktop machine or share it with your own colleagues. It's a consistent medium to develop and deploy software.

      In many ways, Docker containers, are very similar to virtual machines. Docker containers are another way to package (or "containerise") software and run it across different environments.

      But there's a fundamental difference with virtual machines. With Docker containers, you don't have the overhead of having to include an entire operating system as part of your image, but only the basic dependencies needed to run some programs.

      Docker VS virtual machines

      From the image above you can see that the Container Engine runs on top of the operative system and that you don't have to include a guest operative system into your container images. For this reason, containers are much more lightweight than traditional virtual machines, while still providing the benefits of isolation and portability.

      But how can we create a new Docker image?

      This is generally done by using a special configuration file called Dockerfile. Let's not indulge with more theory and let's see an example instead!

      Before getting started let's make sure that you have Docker installed in your machine. You can get docker for most platform using the installers available on the official Docker website.

      Ok, if you are ready, let's create our first Dockerfile:

      touch Dockerfile

      What do we want to put into our Dockerfile? Let's keep things simple for now and let's say that we want to create a container that just run npm. To achieve this, this is what we can write into our Dockerfile:

      FROM debian:buster
      +RUN apt-get update && apt-get install nodejs npm -y
      +CMD ['npm']

      Ok, let's stop for a second... there's a lot to unpack here!

      The first thing that we can see is that a Dockerfile is a file with a specific syntax. It is somewhat similar to a Bash script, but there's also something different about it...

      The order of lines is important and every line starts with a keyword that defines the type of instruction: in our example, FROM, RUN and CMD.

      • FROM is generally the first line of any Dockerfile and it is used to define which base image do we want to extend. A base image provides a sort of a starting point that can make our life easier. For instance, here by specifying debian:buster, we are inheriting all the binaries and libraries that can be found in the Buster version of Debian. This includes the package manager apt that we can use to install additional software.
      • RUN is used to run some scripting. This is generally done to install additional software, create configuration files, generate keys, download remote resources, etc. You can see RUN instructions as provisioning steps, in other words, instructions you need to run to configure your image. It is quite common to have multiple RUN instructions in a Dockerfile.
      • CMD generally appears only once per Dockerfile and it indicates which command should be executed when the container runs. In our case, we are using it to specify that we want npm to be executed.

      There are other commands like ENV (define environment variables) and COPY (copy a file from your system into the image), but providing a comprehensive guide on how to write a Dockerfile goes beyond the scope of this article. If you want to find out more, check out the official Dockerfile reference.

      Ok, now that we have defined our Dockerfile, how do we build a container image?

      That's easy, we just need to execute docker build . (where . means "look for a Dockerfile in the current folder). We can also give a name to our image by using tags, so the final command we want to execute is actually going to look like this:

      docker build -t demo-docker/npmdemo .

      With this command we are "tagging" our image with the name demo-docker/npmdemo. We can use this name later to run our container.

      So ok, let's run the container then!

      docker run demo-docker/npmdemo

      If everything went as expected we should see the following output:

      Usage: npm 
      +
      +where  is one of:
      +    access, adduser, audit, bin, bugs, c, cache, ci, cit,
      +    clean-install, clean-install-test, completion, config,
      +    create, ddp, dedupe, deprecate, dist-tag, docs, doctor,
      +    edit, explore, fund, get, help, help-search, hook, i, init,
      +    install, install-ci-test, install-test, it, link, list, ln,
      +    login, logout, ls, org, outdated, owner, pack, ping, prefix,
      +    profile, prune, publish, rb, rebuild, repo, restart, root,
      +    run, run-script, s, se, search, set, shrinkwrap, star,
      +    stars, start, stop, t, team, test, token, tst, un,
      +    uninstall, unpublish, unstar, up, update, v, version, view,
      +    whoami
      +
      +npm  -h  quick help on 
      +npm -l            display full usage info
      +npm help    search for help on 
      +npm help npm      involved overview
      +
      +...

      After showing this output, the container is stopped (simply because npm exits after showing the help message) and we are back to our prompt.

      Remember that we specified in our CMD instruction that we just wanted to run npm at container start? This is why we are seeing the npm help message when we start the container. What if we want to run the container again but this time we want to run a different command? Can we override the default command? We can easily do that! For instance, we can run the following:

      docker run demo-docker/npmdemo npm --version

      As you might expect, this is going to print something like this:

      6.14.11

      I know this is not super useful just yet! But hold on for few other minutes so we can discuss why you might want to use Docker in your team. After that, we will actually go building a Docker image for a Node.js HTTP server! I promise that will be interesting :)

      Why Docker and Docker Compose can help you and your team

      Any active project will be constantly updated with new features, bug fixes or security patches. This is true for our project as well for the dependencies of our project. Of course, it is desirable to keep the dependencies of our project as much up to date as possible. But, inevitably, different projects might end up using different versions of the same dependency.

      Keep in mind that, when we talk about dependencies, we don't mean only libraries but also other systems the project relies on, like a database. Let's take for example MySQL. In 2021 it is common to see project using MySQL 5.6, MySQL 5.7 or MySQL 8.

      Now, if we are working on multiple projects that require different versions of MySQL, how can we manage different versions of MySQL in our development machines? Is it always possible to switch from one version to another without messing up our configuration or corrupting the data?

      With Docker you can avoid to spend hours to find a solution to this problem and run your entire stack on containers. Different projects can run locally by spinning up a different MySQL container and since every container is isolated, you won't have any issue when switching from one project to another: you won't accidentally corrupt MySQL configuration or its data.

      So, the idea is to use Docker to manage every single "process" that is needed to run your application. Every process will run in its own container. For instance, in a regular web application, you might have a container for the web server process and a container for the database.

      To manage and integrate many containers together, Docker offers a utility called Docker Compose. Docker Compose looks for a file called docker-compose.yml where you can define all the necessary configuration and containers you want to run for your project.

      If you work in a team, you don't have to agree in advance on every single configuration detail with your teammates or make sure that everyone has exactly the same setup. In fact, with Docker compose, you can just share the docker-compose.yml configuration as part of your project and Docker will take care to spin up the same containers in every machine.

      At any point in time, if someone on the team decides to update a dependency or introduce a new dependency, they will do so by updating the docker-compose.yml and everyone else will easily be able to get the latest changes.

      Even if you have a new joiner in the team, they don't have to understand and install all the necessary dependencies one by one, they can simply install Docker rely on the latest version of docker-compose.yml to run the project on their development machine.

      This is one of the main advantages that you can get by using Docker and Docker Compose for development.

      Let's now see an example of a docker-compose.yml:

      version: '3'
      +
      +################################################################################
      +# SERVICES
      +################################################################################
      +services:
      +
      +  # ------------------------------------------------------------
      +  # MySQL Database
      +  # ------------------------------------------------------------
      +  mysqlserver:
      +    container_name: myproject_mysql
      +    image: mysql:5.7
      +
      +    environment:
      +      MYSQL_ROOT_PASSWORD: "docker"
      +      MYSQL_USER: "local"
      +      MYSQL_PASSWORD: "local"
      +
      +    volumes:
      +      # ---- Format: ----
      +      # HOST-DIRECTORY : DOCKER-DIRECTORY
      +      - ${MYSQL_BACKUP_FOLDER}:/backup/
      +
      +    networks:
      +      app_net:
      +        ipv4_address: ${IP_MYSQL_SERVER}
      +
      +################################################################################
      +# NETWORK
      +################################################################################
      +networks:
      +  app_net:
      +    driver: bridge
      +    ipam:
      +      driver: default
      +      config:
      +        -   subnet: ${IP_SUBNET}

      What is inside this this file?

      • services : this is the list of the containers (and their settings) that we want to run in our project
      • mysqlserver : the name of the service. This name is used when you use docker-compose command and you want to refer to this specific service (container).
      • container_name : overrides the default alias of the container. It's a good practice to use something like <name of the project>_<name of the service> to avoid conflict with other projects.
      • image : the name of the Docker image that we want to use (as defined in the Docker registry).
      • environment : environment variables used inside the container.
      • volumes : shared folder between your computer and the container. You can crate a mapping of your destination folder even if doesn't exist. In our example we map our backup folder with /backup folder inside the container. Very useful if we want to crate a standard script for restoring the data into the database (e.g. standard seed data shared across devs).
      • networks : add specific ipv4 IP to our container. Setting up a specific IP for your container (service) is very useful when you have to work with different projects or you want to create local DNS aliases for your project.
      • networks → app_net → driver: bridge this is a default configuration to allow the container to connect to the internet through the host system. The name app_net is a the name we want to give to the Docker network, so you can name it wathever makes sense to you. Refer to the official documentation on custom networks for more information.

      Now let's see our .env file that we are going to use to store the enviornment variables needed for this project:

      ################################################################################
      +### MySQL Settings
      +################################################################################
      +MYSQL_BACKUP_FOLDER=/home/user/backup_db/project_x/
      +
      +################################################################################
      +### IP Settings
      +################################################################################
      +IP_SUBNET=172.16.250.0/24
      +IP_LOCAL_COMPUTER=172.16.250.1
      +IP_MYSQL_SERVER=172.16.250.11

      It is not always necessary to use an .env file, especially in simple examples like this one. It is a good practice to use one, though. The main advantage is when you have to share the docker-compose.yml and the .env with the team.

      Generally the .env file is not committed to the repository. The idea is to use it to provide "user specific" configuration, so every member of the team can choose your own range of IPs or reference their own work folders.

      Let's see an example. If we add in docker-compose a service for Node.js we can add some "link" to other services. (official docker documentation: https://docs.docker.com/compose/compose-file/compose-file-v3/#extra_hosts)

      # ...
      +    
      +    # ------------------------------------------------------------
      +    # Node.js Server
      +    # ------------------------------------------------------------
      +    nodejs:
      +        # ...
      +
      +        extra_hosts:
      +            - mysql-server-service:${IP_MYSQL_SERVER}
      +
      +# ...

      In extra_hosts we can add all entries that are stored in our /etc/hosts file so we can create "custom domain" via local configuration without have a real domain.

      In this specific case in our Node.js app we can refer to mysql server not via IP but via "mysql-server-service" name.

      Let's go back to our docker-compose file. At this point we can run docker-compose to start all the docker services specified in our project:

      docker-compose up -d

      Note that the -d option will start all the services in background.

      After a few seconds, all our services should be running. So let's see, for example, how we can access the MySQL shell and run a query:

      docker-compose exec mysqlserver /bin/bash
      +root@2f88599b3b1f:/# mysql -u root -pdocker (...enter root password)
      +mysql> SHOW DATABASES;

      First of all: we need to "open" a bash shell inside the container so we can run any command. We can do that with: docker-compose exec mysqlserver /bin/bash.

      These are the arguments of this docker-compose command:

      • exec : Docker searches for a running container (with the name mysqlserver) to run a given command. If you don't run docker-compose up -d before, this command will fail.
      • /bin/bash : is the name of the command we want to run inside the container.

      Once we are "inside" the container we can do anything we want. In this case we want open a mysql terminal, so we run mysql -u root -pdocker.

      Note that the root password is defined by environment variable MYSQL_ROOT_PASSWORD.

      Create and configure a Node.js project with Docker

      Based on the complexity of the project we might decide to use Docker directly one or more containers or to rely on Docker Compose to orchestrate multiple containers.

      Let's take a look at this simple "Hello World" web server (file app.js).

      const http = require('http');
      +
      +const hostname = '0.0.0.0';
      +const port = 4040;
      +
      +const server = http.createServer((req, res) => {
      +  res.statusCode = 200;
      +  res.setHeader('Content-Type', 'text/plain');
      +  res.end('Hello World');
      +});
      +
      +server.listen(port, hostname, () => {
      +  console.log(`Server running at http://${hostname}:${port}/`);
      +});

      Nothing particularly complicated here, we are simply creating a web server that responds to every request with an "Hello World!".

      But pay special attention to hostname! With docker you can have connectivity problems if you use "localhost" or "127.0.0.1", because it refers to the docker container and not to your local machine, therefore the container will only listen for connections from inside the container itself. It's better to use use "0.0.0.0" or directly the ip associated to the container so that we can issue requests from our local machine or from other containers.

      Work with Docker directly (no configuration file)

      To start this application with Docker, we don't necessarily need to create a Dockerfile or a docker-compose.yml. We can simply run (in the same folder of app.js) the following command:

      docker run \
      +  --rm \
      +  -p "4040:4040" \
      +  -v ${PWD}:/home/node/project \
      +  node:14 \
      +  node /home/node/project/app.js

      At this point we should be able to connect to our web server using a browser by simply visiting http://127.0.01:4040.

      A simple Node.js web server seen from the browser

      Let's discuss in more detail what's happening when we run the command above:

      • --rm means "destroy the container once it is stopped".
      • -p maps a port of your pc to a port in the container (in this case we are saying map the local port 4040 to the port 4040 inside the container).
      • -v stands for "volume" and it is used to map the folder ($PWD) in the development machine to the /home/node/project folder inside the container.
      • node:14 is the name and version of the docker image we want to use (this comes from the official Docker Registry)
      • node /home/node/project/app.js is the command we want to run once the container starts. We are simply executing node and running our app file.

      Note that /home/node/project/app.js refers to the mapping of the local folder to volume in the container. Remember that Docker runs isolated processes, so by default they don't have access to our filesystem and we can only share files by explicitly defining and attaching volumes to running containers.

      Work with docker-compose.yml and the docker-compose command

      Let's now look at doing the same thing but with a different approach. By using an .env file, a docker-compose.yml file and docker-compose, we can define all the necessary settings there so we can keep our command line as lean as possible.

      This is how we can define our docker-compose.yml:

      version: '3'
      +services:
      +    nodejs:
      +        container_name: ${COMPOSE_PROJECT_NAME}_${NODEJS_SERVER_NAME}
      +        image: ${NODEJS_CONTAINER_IMAGE}
      +        user: ${NODEJS_USER}
      +        command: ${NODEJS_COMMAND}
      +        ports:
      +            - "4040:4040"
      +        environment:
      +            NODE_ENV: ${NODEJS_ENV}
      +        volumes:
      +            - ${PWD}:/home/node/project
      +        working_dir: /home/node/project
      +        networks:
      +            app_net:
      +                ipv4_address: ${IP_NODE_SERVER}
      +networks:
      +    app_net:
      +        driver: bridge
      +        ipam:
      +            driver: default
      +            config:
      +                -   subnet: ${IP_SUBNET}

      And this is how we can define our .env file:

      COMPOSE_PROJECT_NAME=nodetest
      +NODEJS_CONTAINER_IMAGE=node:14
      +NODEJS_SERVER_NAME=nodejs
      +NODEJS_USER=node
      +NODEJS_ENV=development
      +NODEJS_COMMAND=node app.js
      +IP_SUBNET=172.16.250.0/24
      +IP_LOCAL_COMPUTER=172.16.250.1
      +IP_NODE_SERVER=172.16.250.10

      At this point we just need to run docker-compose up and we should have the same result as the command described before.

      If you want to run in background mode (so you can close the terminal) just add -d to the command like docker-compose up -d.

      If you want to change something like node version, just edit the .env file in the same folder and done!

      Once you are finished working on the project and you want to stop the containers, you can simply run:

      docker-compose down

      Pros and cons of using Docker with Node.js

      It might seem that Docker is like magic for your project but, as with many things, "all that glitters is not gold". Of course, Docker is great, but it can have its fair share of unexpected "surprises" that can be tedious to resolve.

      Let's conclude this article, by discussing some of the main pros and cons of using Docker for Node.js development.

      PROS

      • You can manage multiple version of Node.js without having to install all of them or needing complicated setup to be able to switch version on demand.
      • You can use different version of Node.js at the same time. Imagine you have a microservice-oriented architecture and 2 different services need to use 2 different versions of Node.js!
      • It is easier to share a consistent setup with the members of your team. Docker becomes the only shared dependency that needs to be pre-installed.

      CONS

      • For small projects running only one monolithic service setting up Docker and Docker Compose can be a bit of over-engineering.
      • With more advanced projects, you might need to setup some bash scripts to run some Docker command (because they might be long and difficult to remember).
      • For Mac OS and Windows you can have some setup problems and degraded performances because Docker is not running natively in these platforms.

      That's all! Hopefully you found this article interesting and you will decide to give Docker a shot for your Node.js development.

      You can check out the code examples in the following repository: https://github.com/giuseppemorelli/docker-node-example. If you liked this article, please consider giving it a star, everyone needs some vanity metrics, after all! :)

      About the author

      Giuseppe Morelli is the guest author of this post. This is Giuseppe's bio.

      Since I was a baby I came to love technology and programming. Today I am a remote worker for Italian and European companies. I like to work with Agile and Time-Material practices. I have been a PHP Developer by trade since 2006, and I am particularly experienced with e-commerce development. Every day I try to learn something new by being a part of the open-source community. In 2020 I've started to study Node.js by reading Mario and Luciano's book :)

      If you want to connect with me, check out my personal website or my Twitter account.

      Node.js Design Patterns

      Get the FREE chapter!

      With this 54 pages long chapter you will learn how to implement and leverage some of the most well known behavioural design patterns in the context of Node.js: the Strategy pattern, the State pattern, the Template pattern, the Iterator pattern, the Middleware pattern, and the Command pattern.

        Node.js Design Patterns chapter 9 behavioral design patterns
        \ No newline at end of file diff --git a/blog/node-js-development-with-docker-and-docker-compose/og_node-js-development-with-docker-and-docker-compose.jpg b/blog/node-js-development-with-docker-and-docker-compose/og_node-js-development-with-docker-and-docker-compose.jpg new file mode 100644 index 0000000..a1c0c57 Binary files /dev/null and b/blog/node-js-development-with-docker-and-docker-compose/og_node-js-development-with-docker-and-docker-compose.jpg differ diff --git a/blog/node-js-race-conditions/index.html b/blog/node-js-race-conditions/index.html new file mode 100644 index 0000000..eaab075 --- /dev/null +++ b/blog/node-js-race-conditions/index.html @@ -0,0 +1,801 @@ +Node.js race conditions

        Node.js race conditions

        Published by Luciano Mammino's profile picture Luciano Mammino on  Sun, 24 Jan 2021 18:35:00 GMT

        A single-threaded event loop like the one used by JavaScript and Node.js, makes it somewhat harder to have race conditions, but, SPOILER ALERT: race conditions are still possible!

        In this article, we will explore the topic of race conditions in Node.js. We will discuss some examples and present a few different solutions that can help us to make our code race condition free.

        What is a race condition?

        First of all, let's try to clarify what a race condition actually is.

        A race condition is a type of programming error that can occur when multiple processes or threads are accessing the same shared resource, for instance, a file on a file system or a record in a database, and at least one of them is trying to modify the resource.

        Let's try to present an example. Imagine that while a thread is trying to rename a file, another thread is trying to delete the same file. In this case, the second thread will receive an error because, when it's trying to delete the file, the file has already been renamed. Or, the other way around, while one thread is trying to rename the file, the file was already deleted by the other thread and it's not available on the filesystem anymore.

        In other cases, race conditions can be more subtle, because they wouldn't result in the program crashing, but they might just be the source of an incorrect or inconsistent behaviour. In these cases, since there is no explicit error and no stack trace, the issue is generally much harder to troubleshoot and fix.

        A classic example is when 2 threads are trying to update the same data source and the new information is the result of a function applied to the current value.

        Let's pretend we are building a Roman Empire simulation game in which we can manage some cash flow and we have a global balance in aureus (a currency used in the Roman Empire around 100 B.C.E.). Now, let's say that our initial balance is 0 aurei and that there are two independent game components (possibly running on separate threads) that are trying to increase the balance by 50 aurei each, we should expect that in the end, the balance is 100 aurei, right?

        0 + 50 + 50 = 100 đŸ€‘

        If we implement this in a naive way, we might have the two components performing three distinct operations each:

        1. Read the current value for balance
        2. Add 50 aurei to it
        3. Save the resulting value into balance

        Since the two components are running in parallel, without any synchronisation mechanism, the following case could happen:

        A race condition example showing 2 processes trying to update a balance

        In the picture above you can see that Component 2 ends up having a stale view of the balance: the balance gets changed by Component 1 after Component 2 has read the balance. For this reason, when Component 2 performs its own update, it is effectively overriding any change previously made by Component 1. This is why we have a race condition: the two components are effectively racing to complete their own tasks and they might end up stepping onto each other's toes! This doesn't make Julius happy I am afraid...

        One way to solve this problem is to isolate the 2 concurrent operations into transactions and make sure that there is only one transaction running at a given time. This idea might look like this:

        Fixing a race condition using a transaction

        In the last picture, we are using transactions to make sure that all the steps of Component 1 happen in order before all the steps of Component 2. This prevents any stale read and makes sure that every component always has an up to date view of the world before doing any change. You can stop holding your breath now, Julius!

        In the rest of this article, we will zoom in more on race conditions in the context of Node.js and we will see some other approaches to deal with them.

        Can we have race conditions in Node.js?

        It is a common misconception to think that Node.js does not have race conditions because of its single-threaded nature. While it is true that in Node.js you would not have multiple threads competing for resources, you might still end up with tasks belonging to different logical transactions being executed in an order that might result in stale reads and generate a race condition.

        In the example that we illustrated above, we intentionally represented the various tasks (read, increase and save) as discrete units. Note how the system is never executing more than one task at the same time. This is a simple but accurate representation of how the Node.js event loop processes tasks on a single thread. Nonetheless, you can see that there might be situations where multiple logical transactions (e.g. multiple deposits) are scheduled concurrently on the event loop and the discrete tasks might end up being intermingled, which results in a race condition.

        So... Yes, we can have race conditions in Node.js!

        A Node.js example with a race condition

        Now, let's talk some code! Let's try to re-create the Roman Empire simulation game example that we discussed above.

        In ancient Rome, Romans used to export olives and grapes. No wonder Italy is still famous worldwide for olive oil and wine! In our game, we want to be able to harvest olives and grapes and then sell them as a means to acquire more aurei.

        We are going to have two functions that can increase the balance by 50 aurei which we are going to call sellOlives() and sellGrapes(). We will also assume that every time the balance is changed, it is persisted to a data storage of sort (e.g. a database). For the sake of this example, we won't be using a real data storage, but we will just simulate some random asynchronous delay before reading or modifying a global value. This will be enough to illustrate how we can end up with a race condition.

        For starts, let's see what a buggy implementation might look like:

        // Utility function to simulate some delay (e.g. reading from or writing to a database).
        +// It will take from 0 to 50ms in a random fashion.
        +const randomDelay = () => new Promise(resolve =>
        +  setTimeout(resolve, Math.random() * 100)
        +)
        +
        +// Our global balance.
        +// In a more complete implementation, this will live in the persistent data storage.
        +let balance = 0
        +
        +async function loadBalance () {
        +  // simulates random delay to retrieve data from data storage
        +  await randomDelay()
        +  return balance
        +}
        +
        +async function saveBalance (value) {
        +  // simulates random delay to write the data to the data storage
        +  await randomDelay()
        +  balance = value
        +}
        +
        +async function sellGrapes () {
        +  const balance = await loadBalance()
        +  console.log(`sellGrapes - balance loaded: ${balance}`)
        +  const newBalance = balance + 50
        +  await saveBalance(newBalance)
        +  console.log(`sellGrapes - balance updated: ${newBalance}`)
        +}
        +
        +async function sellOlives () {
        +  const balance = await loadBalance()
        +  console.log(`sellOlives - balance loaded: ${balance}`)
        +  const newBalance = balance + 50
        +  await saveBalance(newBalance)
        +  console.log(`sellOlives - balance updated: ${newBalance}`)
        +}
        +
        +async function main () {
        +  const transaction1 = sellGrapes() // NOTE: no `await`
        +  const transaction2 = sellOlives() // NOTE: no `await`
        +  await transaction1 // NOTE: awaiting here does not stop `transaction2` 
        +                     // from being scheduled before transaction 1 is completed
        +  await transaction2
        +  const balance = await loadBalance()
        +  console.log(`Final balance: ${balance}`)
        +}
        +
        +main()

        If we execute this code we might end up with different results. In one case we might get the correct outcome:

        sellOlives - balance loaded: 0
        +sellOlives - balance updated: 50
        +sellGrapes - balance loaded: 50
        +sellGrapes - balance updated: 100
        +Final balance: 100

        But in other cases we might end up in a bad state:

        sellGrapes - balance loaded: 0
        +sellOlives - balance loaded: 0
        +sellGrapes - balance updated: 50
        +sellOlives - balance updated: 50
        +Final balance: 50

        Note how in this last case, sellOlives is essentially a stale read and therefore it will end up overriding the balance disregarding any work already done by sellGrapes. Yes, we do have a race condition, unfortunately!

        Now, this example is simple ad it is not too hard to pinpoint exactly where the race condition has originated by just looking at the code.

        Take a minute or two to read the code again. Check out the output from the 2 cases as well. Pay attention to the notes and the log messages and try to imagine how the Node.js runtime might execute this code in the 2 different scenarios.

        Ok, now that you have done that, let's discuss together what happens.

        In our main function, when we execute sellGrapes() and sellOlives(), since we are not awaiting the two operations independently, we are essentially scheduling both operations onto the event loop.

        We only await the two transactions after they have been already scheduled, which means that they will work concurrently. After the two transactions have been scheduled, we wait for transaction1 to complete and only then we wait for transaction2 to complete. Note that transaction2 might complete even before transaction1. In other words, awaiting for transaction1 doesn't block transaction2 in any way.

        This approach is similar to writing the following code:

        await Promise.all([sellGrapes(), sellOlives()])

        Using Promise.all() is a more commonly used way to schedule different tasks to run concurrently.

        Note that with Promise.all(), the resulting promise will reject as soon as any of the promises rejects. In our previous example, since we await the two promises independently, we will always catch errors in transaction1 before transaction2.

        But let's not digress too much into this. Now that we understand the problem, how do we fix the race condition?

        Well, it turns out that in this simple case, we might make things right quite easily:

        async function main () {
        +  await sellGrapes() // <- schedule the first transaction and wait for completion
        +  await sellOlives() // <- when it's completed, we start the second transaction 
        +                     //    and wait for completion
        +  const balance = await loadBalance()
        +  console.log(`Final balance: ${balance}`)
        +}

        This implementation will consistently produce the following output:

        sellGrapes - balance loaded: 0
        +sellGrapes - balance updated: 50
        +sellOlives - balance loaded: 50
        +sellOlives - balance updated: 100
        +Final balance: 100

        As we can observe, sellGrapes is always started and completed before we start sellOlives. This makes the two logical transactions isolated and makes sure their tasks won't end up being mixed together in random order.

        Problem solved... vade in pacem dear race condition!

        Using a mutex in Node.js

        OK, the previous example was illustrative, but if we are building a real game, chances are things will end up being a lot more complicated. We will probably end up having many different actions that might cause a change of balance. Those actions might be the result of a particular sequence of events and it might become hard to track down the discrete logical transactions that we have to serialize in order to avoid race conditions.

        Ideally, we don't want to think in terms of transactions, we just need to make sure that we never read the balance if there is another concurrent operation that is ready to change its value.

        To be able to do this we need two things:

        1. Have a way to identify when we are about to change the balance
        2. Let other events wait in line until the change is completed before reading the balance

        We could say that when we are about to change the balance we enter a critical path and that we don't want to intermingle events from different logical transactions in a critical path.

        One way to achieve this is by using a Mutex (which stands for mutual exclusion).

        A mutex is a mechanism that allows synchronising access to a shared resource.

        We can see a mutex as a shared object that allows us to mark when the code execution is entering and exiting from a critical path. In addition to that, a mutex can help us to queue other logical transactions that want to access the same critical path while one transaction is being processed.

        Before talking code, be aware that using a mutex might have a performance impact in your application and that this solution won't work if you use a distributed or a multi-process setup. More details on this later.

        Using async-mutex

        A very useful library that we can useg is async-mutex. This library provides a promise-based implementation of the mutex pattern.

        You can install this library from npm:

        npm install --save async-mutex

        Now, here's an example of how we could use this library to mark the beginning and the end of a critical path:

        import { Mutex } from 'async-mutex'
        +
        +const mutex = new Mutex() // creates a shared mutex instance
        +
        +async function doingSomethingCritical() {
        +  const release = await mutex.acquire() // acquires access to the critical path
        +  try {
        +    // ... do stuff on the critical path
        +  } finally {
        +    release() // completes the work on the critical path
        +  }
        +}

        In this example, we are using a global mutex instance to mark the beginning and the end of a critical path which happens inside our doingSomethingCritical() function.

        When we call mutex.acquire(), this method will return a promise. If no other concurrent operation is currently on the same critical path, the promise resolves to a function that we call release. In this situation, we are essentially granted exclusive access to the critical path. If some concurrent operation is on the critical path already, the promise won't resolve until the concurrent operation already on the critical path has completed. This is how concurrent operations wait in line for our exclusive access to the critical path.

        The release function must be invoked to mark the completion of the work on the critical path. It effectively releases the exclusive access to the critical path and makes it available to the next task in line. Note that we are using a try/finally block here to make sure that release is called even in case of an exception. It is very important to do so. In fact, failing to call release, will leave all the other events waiting in line forever!

        Now let's try to use async-mutex to avoid race conditions in our game:

        import { Mutex } from 'async-mutex'
        +
        +const randomDelay = () => {/* ... */}
        +
        +let balance = 0
        +const mutex = new Mutex() // global mutex instance
        +
        +async function loadBalance () {/* ... */}
        +async function saveBalance (value) {/* ... */}
        +
        +async function sellGrapes () {
        +  // this code will need exclusive access to the balance
        +  // so we consider this to be a critical path
        +  const release = await mutex.acquire() // get access to the critical path (or wait in line)
        +  try {
        +    const balance = await loadBalance()
        +    console.log(`sellGrapes - balance loaded: ${balance}`)
        +    const newBalance = balance + 50
        +    await saveBalance(newBalance)
        +    console.log(`sellGrapes - balance updated: ${newBalance}`)
        +  } finally {
        +    release() // completes work on the critical path
        +  }
        +}
        +
        +async function sellOlives () {
        +  // similar to `sellGrapes` this is a critical path because
        +  // it needs exclusive access to balance
        +  const release = await mutex.acquire()
        +  try {
        +    const balance = await loadBalance()
        +    console.log(`sellOlives - balance loaded: ${balance}`)
        +    const newBalance = balance + 50
        +    await saveBalance(newBalance)
        +    console.log(`sellOlives - balance updated: ${newBalance}`)
        +  } finally {
        +    release()
        +  }
        +}
        +
        +async function main () {
        +  // Here we can call many events safely, the mutex will guarantee that the
        +  // competing events are executed in the right order!
        +  await Promise.all([
        +    sellGrapes(),
        +    sellOlives(),
        +    sellGrapes(),
        +    sellOlives(),
        +    sellGrapes(),
        +    sellOlives()
        +  ])
        +  const balance = await loadBalance()
        +  console.log(`Final balance: ${balance}`)
        +}
        +
        +main()

        The code above will consistently produce the following output:

        sellGrapes - balance loaded: 0
        +sellGrapes - balance updated: 50
        +sellOlives - balance loaded: 50
        +sellOlives - balance updated: 100
        +sellGrapes - balance loaded: 100
        +sellGrapes - balance updated: 150
        +sellOlives - balance loaded: 150
        +sellOlives - balance updated: 200
        +sellGrapes - balance loaded: 200
        +sellGrapes - balance updated: 250
        +sellOlives - balance loaded: 250
        +sellOlives - balance updated: 300
        +Final balance: 300

        Some of the code has been truncated for simplicity. You can find all the examples in this repository.

        From the example above, you can see how mutexes can provide a convenient way of thinking about exclusive access and how they can help to avoid race conditions. We are intentionally triggering multiple calls to sellGrapes() and sellOlives() concurrently, to make obvious that we don't have to think about potential race conditions at the calling point. This means that, as our game grows more complicated, we can keep invoking these functions without having to worry about generating new race conditions.

        Let's implement a mutex

        But what if we are dealing with a race condition only in one place in our entire application? Is it worth to include and manage an external dependecy just because of that? Can we come up with a simpler alternative that does not require us to install a new dependency?

        It turns out that we can easily do that! Let's see how we can implement our own mutex.

        Note that the solution we are going to present here is effectively a variation of the sequential execution pattern using promises that is presented in Chapter 5 of Node.js Design Patterns.

        The idea is to inizialize our global mutex as an instance of a resolved promise:

        let mutex = Promise.resolve()

        Then in our critical path we can do something like this:

        async function doingSomethingCritical() {
        +  mutex = mutex.then(() => {
        +    // ... do stuff on the critical path
        +  })
        +  .catch(() => {
        +    // ... manage errors on the critical path
        +  })
        +  return mutex
        +}

        The idea is that every time we are invoking the function doingSomethingCritical() we are effectively "queueing" the execution of the code on the critical path using mutex.then(). If this is the first call, our initial instance of the mutex promise is a resolved promise, so the code on the critical path will be executed straight away on the next cycle of the event loop.

        Calling .then() on a promise returns a new promise instance that is used to replace the original mutex instance and it's also returned by the doingSomethingCritical() function.

        This allows us to have concurrent calls to doingSomethingCritical() being queued to be executed sequentially.

        Note that we also specify a mutex.catch(). This allows us to catch and react to specific errors, but it also allows us not to break the chain of sequential execution in case an operation fails.

        Ok, now that we have explored this idea, let's apply it to our example.

        This is how our code is going to look like:

        const randomDelay = () => {/* ... */}
        +
        +let balance = 0
        +let mutex = Promise.resolve() // global mutex instance
        +
        +async function loadBalance () {/* ... */}
        +async function saveBalance (value) {/* ... */}
        +
        +async function sellGrapes () {
        +  mutex = mutex.then(async () => {
        +    const balance = await loadBalance()
        +    console.log(`sellGrapes - balance loaded: ${balance}`)
        +    const newBalance = balance + 50
        +    await saveBalance(newBalance)
        +    console.log(`sellGrapes - balance updated: ${newBalance}`)
        +  }).catch(() => {})
        +  return mutex
        +}
        +
        +async function sellOlives () {
        +  mutex = mutex.then(async () => {
        +    const balance = await loadBalance()
        +    console.log(`sellOlives - balance loaded: ${balance}`)
        +    const newBalance = balance + 50
        +    await saveBalance(newBalance)
        +    console.log(`sellOlives - balance updated: ${newBalance}`)
        +  }).catch(() => {})
        +  return mutex
        +}
        +
        +async function main () {
        +  await Promise.all([
        +    sellGrapes(),
        +    sellOlives(),
        +    sellGrapes(),
        +    sellOlives(),
        +    sellGrapes(),
        +    sellOlives()
        +  ])
        +  const balance = await loadBalance()
        +  console.log(`Final balance: ${balance}`)
        +}
        +
        +main()

        If you try to run this code, you will see that it consistently prints the same output as per our previous implementation using async-mutex!

        So, here we have it, a simple mutex implementation in just few lines of code leveraging promise chainability!

        Mutex with multiple processes

        It is important to mention that the solutions presented in this article only work in a Node.js application running on a single process.

        If you are running your application on multiple processes (for instance, by using the cluster module, worker threads or a multi-process runner like pm2) using a mutex within our code is not going to solve race conditions across processes. This is also the case if you are running your application on multiple servers.

        In these cases you have to rely on more complicated solutions like distributed locks or, if you are using a central database, you can rely on solutions provided by your own database systems. We will discuss a simple example in the next section.

        Mutex performance

        We already mentioned that using a mutex might have a relevant performance impact in your application.

        To try to visualize why a mutex has a performance impact in your application let's try to think about the case when an operation is trying to acquire a lock on a mutex but the mutex is already locked. In this case, our operation is simply waiting without doing nothing, while for instance it could be doing some IO operation like connecting to the database or sending a query. It will probably take the event loop several spins before the lock is released and the operation that is waiting in line can acquire the lock. This get worse with a high number of operations waiting in line.

        With a mutex we are effectively serializing tasks, making sure that executed in sequence and non-concurrently. If you abuse this pattern, you might end up in a situation where you could effectively eliminate all concurrency from your application.

        Measuring how a mutex might impact your specific application is not something that can be done holistically and we recommend you to run your own benchmarks to find out what is the effect of introducing one or more mutex instances in your application.

        Our general recommendation is to use a mutex only when you are sure you have to protect your code from a race condition and to try to make the critical path as short as possible.

        Be aware that a mutex is not the only solution to race conditions. For instance, in our example, if we were to use a real relational database as a data storage, we could have avoided any race condition (at the application level) by letting the database itself do the increment using a SQL query:

        UPDATE game SET aurei = aurei + 50;

        With this approach, we are trusting the database to do the right thing and we are not slowing down our application.

        And there are other alternative approches. Just to name one, optimistic locks might provide a great alternative if race conditions are possible but they actually happen only in rare occasions.

        Conclusion

        In this article, we have explored race conditions and learned why they can be harmful. We showed how race conditions can happen in Node.js and several techniques to address them including the adoptopm of a mutex.

        This is an interesting topic which often gets explored in the context of multi-threaded languages. The theory isn't much different but there are some important differences when dealing with concurrent, single-threaded languages like Node.js.

        If you are curious to understand better the difference between Parallelism and Concurrency I strongly recommend you to read this great essay titled parallelism and concurrency need different tools. You can also watch this wonderful talk by Steve Klabnik called Rust's Journey to Async/Await (yes, it's not only about Rust, trust me).

        I really hope you enjoyed this article. Make sure to reach out to me on Twitter and let me know what you think!

        Bye 😋

        Credits

        Thanks to Jack Barry for the inspiration for this post on the Node.js Design Patterns discussion board. Thanks to Peter Caulfield, Stefano Abalsamo, Roberto Gambuzzi and Mario Casciaro for kindly reviewing this post.

        Node.js Design Patterns

        Get the FREE chapter!

        With this 54 pages long chapter you will learn how to implement and leverage some of the most well known behavioural design patterns in the context of Node.js: the Strategy pattern, the State pattern, the Template pattern, the Iterator pattern, the Middleware pattern, and the Command pattern.

          Node.js Design Patterns chapter 9 behavioral design patterns
          \ No newline at end of file diff --git a/blog/node-js-race-conditions/og_node-js-race-conditions.jpg b/blog/node-js-race-conditions/og_node-js-race-conditions.jpg new file mode 100644 index 0000000..d6491da Binary files /dev/null and b/blog/node-js-race-conditions/og_node-js-race-conditions.jpg differ diff --git a/blog/node-js-stream-consumer/index.html b/blog/node-js-stream-consumer/index.html new file mode 100644 index 0000000..8df87bb --- /dev/null +++ b/blog/node-js-stream-consumer/index.html @@ -0,0 +1,685 @@ +Node.js stream consumer utilities

          Node.js stream consumer utilities

          Published by Luciano Mammino's profile picture Luciano Mammino on  Fri, 11 Mar 2022 12:30:00 GMT

          How many times did you need to read the entire content of a Readable stream into memory and ended up writing something like this?

          const chunks = []
          +someReadableStream.on('data', (chunk) => chunks.push(chunk))
          +someReadableStream.on('end', () => {
          +  const data = Buffer.concat(chunks)
          +  // do something with `data`
          +})

          Or using async iterators:

          const chunks = []
          +for await (const chunk of someReadableStream) {
          +  chunks.push(chunk)
          +}
          +const data = Buffer.concat(chunks)
          +// do something with `data`

          This is a bit of a boilerplate-heavy solution for just consuming an entire readable stream. Consider that here we are not even handling errors, trying to do that (as we should!) will add even more boilerplate!

          If you wish there was an easier way, well keep reading, this article is for you!

          The stream/consumers module

          Since Node.js version 16, there is a new built in stream utility library called stream/consumers which offers a bunch of useful utilities to consume the entire content of a ReadableStream.

          At the time of writing this article, stream/consumers does not even appear in the official Node.js documentation, so it's still of a hidden gem. Hopefully this article will help to spread the word a little bit.

          UPDATE: It turns out that this module is documented under the Web Streams API section and in fact these utilities are both compatible with Node.js streams and web streams.

          Without further ado, let's see what's inside this module:

          import consumers from 'stream/consumers'
          +console.log(consumers)

          If we run this code, we will see the following output:

          {
          +  arrayBuffer: [AsyncFunction: arrayBuffer],
          +  blob: [AsyncFunction: blob],
          +  buffer: [AsyncFunction: buffer],
          +  text: [AsyncFunction: text],
          +  json: [AsyncFunction: json]
          +}

          So, what we can tell is that the stream/consumers module exposes some async function that seem to be helpful to consume Readable streams in different ways:

          • As binary data (ArrayBuffer, Blob, Buffer)
          • As text
          • As JSON

          In the next sections we will see some examples on how to use these functions.

          Reading a binary file from a Readable stream

          Ok, let's say that we have to do some processing on a picture and, in order to do that, we need to load the entire binary content representing the picture from a file to memory.

          We could easily use the buffer function from the stream/consumers library to do that:

          import path from 'path'
          +import { createReadStream } from 'fs'
          +import consumers from 'stream/consumers'
          +
          +const __dirname = new URL('.', import.meta.url).pathname;
          +const readable = createReadStream(path.join(__dirname, 'picture.png'))
          +const data = await consumers.buffer(readable)
          +console.log(data)

          If we execute this code, we will see the following output:

          (node:7685) ExperimentalWarning: buffer.Blob is an experimental feature. This feature could change at any time
          +(Use `node --trace-warnings ...` to show where the warning was created)
          +<Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 02 80 00 00 01 c1 08 02 00 00 00 76 43 9d 20 00 00 00 01 73 52 47 42 00 ae ce 1c e9 00 00 00 04 ... 347824 more bytes>

          You can see that all the binary data (~300Kb) was loaded in the buffer, but also that this feature (as of v17.7.1) is still experimental and therefore we get a warning. You will get a similar warning also when trying to use consumers.arrayBuffer and consumers.blob. This will be the case until buffer.Blob is stabilised.

          Reading a JSON object from a Readable stream

          Similarly to what we just saw in the previous section, we can use the stream/consumers library to consume the entire content of a ReadableStream as a JSON encoded string. For instance, we could use this to process the response body from an HTTP request:

          import { get } from 'https'
          +import consumers from 'stream/consumers'
          +
          +
          +const url = 'https://rickandmortyapi.com/api/character/639'
          +get(url, async (response) => {
          +  const data = await consumers.json(response)
          +  console.log(data)
          +})

          Here we are using the awesome (and free) The Rick and Morty API. If we run this code we should see the following output:

          {
          +  id: 639,
          +  name: 'Uncle Nibbles',
          +  status: 'Dead',
          +  species: 'Alien',
          +  type: 'Soulless Puppet',
          +  gender: 'Male',
          +  origin: {
          +    name: 'Tickets Please Guy Nightmare',
          +    url: 'https://rickandmortyapi.com/api/location/98'
          +  },
          +  location: {
          +    name: 'Tickets Please Guy Nightmare',
          +    url: 'https://rickandmortyapi.com/api/location/98'
          +  },
          +  image: 'https://rickandmortyapi.com/api/character/avatar/639.jpeg',
          +  episode: [ 'https://rickandmortyapi.com/api/episode/37' ],
          +  url: 'https://rickandmortyapi.com/api/character/639',
          +  created: '2020-08-06T16:51:23.084Z'
          +}

          It's also worth mentioning that consumers.json does not produce any warning, so this feature can be considered stable in Node.js 16.

          Reading a text from a Readable stream

          Let's discuss one more example. Let's try to consume an entire readable stream as text, which means that we will be consuming the stream assuming it's a valid UTF-8 encoded string and save the result into a string variable.

          One simple example could be to try to read a string from the standard input in a CLI application:

          import consumers from 'stream/consumers'
          +
          +const input = await consumers.text(process.stdin)
          +console.log(input)

          If we try to run the script as follows:

          cat mobydick.txt | node stdin.js

          We should see something like this in the output:

          CHAPTER 1
          +
          +Loomings.
          +
          +
          +Call me Ishmael.  Some years ago--never mind how long
          +precisely--having little or no money in my purse, and nothing
          +particular to interest me on shore, I thought I would sail about a
          +little and see the watery part of the world.  It is a way I have of
          +driving off the spleen and regulating the circulation.  Whenever I
          +find myself growing grim about the mouth; whenever it is a damp,
          +drizzly November in my soul; whenever I find myself involuntarily
          +pausing before coffin warehouses, and bringing up the rear of every
          +funeral I meet; and especially whenever my hypos get such an upper
          +hand of me, that it requires a strong moral principle to prevent me
          +from deliberately stepping into the street, and methodically knocking
          +people's hats off--then, I account it high time to get to sea as soon
          +as I can.  This is my substitute for pistol and ball.  With a
          +philosophical flourish Cato throws himself upon his sword; I quietly
          +take to the ship.  There is nothing surprising in this.  If they but
          +knew it, almost all men in their degree, some time or other, cherish
          +very nearly the same feelings towards the ocean with me.
          +
          +[...]

          Again, no warnings, so this feature is stable in Node.js v16.

          Is this even a good idea?

          Now that you know how to use this utility, it's worth mentioning that as with all good things it should be used with moderation.

          In fact, accumulating all the content of a stream in memory is something that should not be done lightly.

          Streams are an abstraction that has been built into Node.js to allow developers to handle arbitrary amounts of data (even infinite streams) and process such data as soon as possible, while still keeping the memory footprint low.

          By processing the data in chunks, we can keep the amount of memory being allocated at any given time low and have our processing logic run efficiently.

          When we accumulate an entire stream we are effectively defeating all the advantages of Node.js streams, so this is something that is recommended only when you are absolutely certain you are dealing with small amounts of data.

          Wapping up

          This is all for this article, feel free to reach out to me on Twitter if you found this article interesting and if you think you learned something useful.

          If you are curious, you can also read the code of the stream/consumers module, it's actually a really thin layer (less than 100 lines) and you can learn a trick or two by doing that.

          See you in the next article!

          Node.js Design Patterns

          Get the FREE chapter!

          With this 54 pages long chapter you will learn how to implement and leverage some of the most well known behavioural design patterns in the context of Node.js: the Strategy pattern, the State pattern, the Template pattern, the Iterator pattern, the Middleware pattern, and the Command pattern.

            Node.js Design Patterns chapter 9 behavioral design patterns
            \ No newline at end of file diff --git a/blog/node-js-stream-consumer/og_nodejs-stream-consumers-utilities.jpg b/blog/node-js-stream-consumer/og_nodejs-stream-consumers-utilities.jpg new file mode 100644 index 0000000..bc587cc Binary files /dev/null and b/blog/node-js-stream-consumer/og_nodejs-stream-consumers-utilities.jpg differ diff --git a/blog/og_blog.html.jpg b/blog/og_blog.html.jpg new file mode 100644 index 0000000..77f8842 Binary files /dev/null and b/blog/og_blog.html.jpg differ diff --git a/blog/rss.xml b/blog/rss.xml new file mode 100644 index 0000000..e7f935d --- /dev/null +++ b/blog/rss.xml @@ -0,0 +1,1543 @@ + + + Node.js Design Patterns Blog + From the authors of Node.js Design Patterns, useful bits to enrich your Node.js knowledge + + + 2022-03-11T12:30:00Z + https://www.nodejsdesignpatterns.com/blog + + Luciano Mammino and Mario Casciaro + authors@nodejsdesignpatterns.com + + + + 5 Ways to install Node.js + + 2020-12-24T18:30:00Z + https://www.nodejsdesignpatterns.com/blog/5-ways-to-install-node-js/ + <p>In this article, we will explore some of the most common ways to install Node.js in your development system. We will see how to install Node.js using the official installer for various platforms, how to use a Node.js version manager such as <code>n</code> or <code>nvm</code> and, finally, we will also see how to compile and install Node.js from source. Along the way, we will try to disclose one or two tips to get you even more productive with Node.js!</p> +<p>Let's get started!</p> +<h2 id="which-option-should-i-pick%3F" tabindex="-1" class="title is-2">Which option should I pick?</h2> +<p>There are many different ways to install Node.js and every one of them comes with its own perks and drawbacks. In this article, we will try to explore the most common ones and by the end of it, you should have a good understanding of which ones should be more suitable for you.</p> +<h3 id="tldr%3B" tabindex="-1" class="title is-3">TLDR;</h3> +<ul> +<li>Use <code>nvm</code> or <code>n</code> if you develop with Node.js frequently and you expect to be needing to switch Node.js version while moving from one project to another or to debug potential compatibility issues in your project or library.</li> +<li>Use the system package manager like <code>apt</code>, <code>brew</code> or <code>winget</code> if you tend to install all your software this way and if you don't expect to be needing to switch or upgrade Node.js version too often.</li> +<li>Install Node.js from source if you are an advanced user and if you want to contribute back to Node.js itself.</li> +<li>Use the official Node.js installer if you don't fall in any of the previous options...</li> +</ul> +<h3 id="what-other-people-seem-to-like" tabindex="-1" class="title is-3">What other people seem to like</h3> +<p>Before writing this article, I was actually curious to find out what are the options that most folks in my network prefer. For this reason, I run a <a href="https://twitter.com/loige/status/1340999569807712257">poll on Twitter</a>. In this poll I asked how you prefer to install Node.js and provided 4 options:</p> +<ul> +<li>Official Installer</li> +<li>Version manager (<code>nvm</code> or <code>n</code>)</li> +<li>Package Manager (<code>apt</code>, <code>brew</code>, etc.)</li> +<li>From source</li> +</ul> +<p>The results are quite interesting:</p> +<a href="https://twitter.com/loige/status/1340999569807712257" rel="nofollow noreferrer"> +<span style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 593px; "> +<picture> + <source type="image/png" srcset="https://www.nodejsdesignpatterns.com/img/poll-results-M4yStsOXME-64.png 64w, https://www.nodejsdesignpatterns.com/img/poll-results-M4yStsOXME-128.png 128w, https://www.nodejsdesignpatterns.com/img/poll-results-M4yStsOXME-256.png 256w, https://www.nodejsdesignpatterns.com/img/poll-results-M4yStsOXME-512.png 512w, https://www.nodejsdesignpatterns.com/img/poll-results-M4yStsOXME-633.png 633w" sizes="(max-width: 593px) 100vw, 593px" /> +<source type="image/webp" srcset="https://www.nodejsdesignpatterns.com/img/poll-results-M4yStsOXME-64.webp 64w, https://www.nodejsdesignpatterns.com/img/poll-results-M4yStsOXME-128.webp 128w, https://www.nodejsdesignpatterns.com/img/poll-results-M4yStsOXME-256.webp 256w, https://www.nodejsdesignpatterns.com/img/poll-results-M4yStsOXME-512.webp 512w, https://www.nodejsdesignpatterns.com/img/poll-results-M4yStsOXME-633.webp 633w" sizes="(max-width: 593px) 100vw, 593px" /> +<img loading="lazy" decoding="async" style="max-width: 100%; width: 100%; margin: 0px; vertical-align: middle;" alt="Install Node.js Twitter poll results" src="https://www.nodejsdesignpatterns.com/img/poll-results-M4yStsOXME-64.png" width="64" height="45" /> +</picture> +</span> +</a> +<p>It seems quite obvious that people in my network, mostly fellow software engineers, prefer to use version managers such as <code>nvm</code> or <code>n</code>.</p> +<p>The second place (actually very tight with the third one) is the official installer, followed by a system package manager and, last one, installing Node.js from source.</p> +<h3 id="lts-and-stable-releases" tabindex="-1" class="title is-3">LTS and stable releases</h3> +<p>Before moving on and exploring all the different installation options, it is definitely worth spending few words to learn about the types of release the Node.js project maintains.</p> +<p>Node.js offers 2 main release lines:</p> +<ul> +<li><strong>Stable</strong> (or <em>Current</em>): every new major Node.js release is considered &quot;Current&quot; for the first 6 months after the publish date. The idea is to give library authors the time to test their compatibility with the new release and do any necessary change. After the 6 months period, all the odd release numbers (9, 11, 13, 15, etc.) move to the state of <em>Unsupported</em>, while even releases (10, 12, 14, etc.) are promoted to <em>Long Term Support</em> (or &quot;LTS&quot;).</li> +<li><strong>LTS</strong>: releases marked as &quot;Long Term Support&quot; get critical bug fixes for a total of 30 months since the initial publish date. This makes LTS releases particularly suitable for production deployments. The most recent LTS is also called <em>Active LTS</em>, while previous LTS versions (still under the 30 months support timeframe) are called <em>Maintenance LTS</em>.</li> +</ul> +<p>Finally, the release coming from the current <em>master</em> branch is considered <strong>Unstable</strong>. This is generally a release dedicated to people maintaining Node.js or developers who want to explore new experimental features that haven't been yet included in any of the major releases.</p> +<p>Node.js publishes an <a href="https://nodejs.org/en/about/releases/">official timeline of current and future releases</a>. At the time of writing (December 2020), this how the timeline looks like:</p> +<a href="https://nodejs.org/en/about/releases/" target="_blank" rel="noreferrer noopener"> +<p style="text-align: center"><img loading="lazy" decoding="async" style="max-width: 100%; margin: 0px; vertical-align: middle;" alt="Node.js release timeline" src="https://www.nodejsdesignpatterns.com/img/nodejs-release-schedule_9b4bf060.svg" width="760" height="396" /></p> +</a> +<p>If you are still wondering which release should you use, going with the <em>Active LTS</em> is almost always the best choice, especially if you are building production applications.</p> +<h2 id="install-node.js-using-n" tabindex="-1" class="title is-2">Install Node.js using n</h2> +<p>Since installing Node.js using a version manager seems to be the favourite option (and it's also my personal favourite!) let's start with it.</p> +<p>My favourite Node.js version manager is <a href="https://github.com/tj/n"><code>n</code> by TJ Holowaychuk</a>. The reason why I like it is because it is quite simple to install and use and it is generally up to date with the latest releases of Node.js. +The main issue with it is that it does not support Windows, so if Windows is your operative system, this is not an option for you!</p> +<p>Let's see how to install <code>n</code>:</p> +<p>If you are on macOS and you have <code>brew</code> (Homebrew) installed, the simplest way to install <code>n</code> is to just do it with <code>brew</code>:</p> +<pre class="language-bash"><code class="language-bash">brew <span class="token function">install</span> n</code></pre> +<p>Alternatively, you can use the custom install script:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> <span class="token parameter variable">-L</span> https://git.io/n-install <span class="token operator">|</span> <span class="token function">bash</span></code></pre> +<p><strong>Note:</strong> if you are concerned about running a script downloaded from the web (as you should because <a href="https://www.idontplaydarts.com/2016/04/detecting-curl-pipe-bash-server-side/"><code>curl | bash</code> might be dangerous</a>), you can always download the script first, READ IT, and then run it locally...</p> +<p>If all goes well, you should now be able to use the <code>n</code> executable from your shell.</p> +<p>These are some of the commands you can run:</p> +<pre class="language-bash"><code class="language-bash"><span class="token comment"># shows the version of `n` installed in your system</span> +n <span class="token parameter variable">--version</span> + +<span class="token comment"># installs the latest LTS release of Node.js</span> +n lts + +<span class="token comment"># lists all the versions of Node.js currently available</span> +n list + +<span class="token comment"># install the given version of Node.js and switch to it</span> +n <span class="token operator">&lt;</span>some_version<span class="token operator">></span></code></pre> +<p>Or you can simply run:</p> +<pre class="language-bash"><code class="language-bash">n</code></pre> +<p>For an interactive prompt that will show you all the available versions, highlight the ones you have already installed and let you pick the version you want to switch to.</p> +<p style="text-align: center"><img loading="lazy" decoding="async" style="max-width: 100%; margin: 0px; vertical-align: middle;" alt="n Node.js version manager in action" src="https://www.nodejsdesignpatterns.com/img/n_ac172e26.gif" width="640" height="428" /></p> +<p>In summary, this is where <code>n</code> shines or falls short:</p> +<ul> +<li>👎 No official support for Windows</li> +<li>👍 Very easy to install on macOS and unix systems</li> +<li>👍 Very easy to keep your Node.js install up to date and switch version on demand</li> +<li>👍 It keeps all the installed versions cached, so you can switch quickly between versions (no full re-install)</li> +<li>👍 Allows to keep the setup local to the user so you don't have to use admin permission to install global packages</li> +</ul> +<h2 id="install-node.js-using-nvm" tabindex="-1" class="title is-2">Install Node.js using nvm</h2> +<p>With more than 45 thousand stars on GitHub, <a href="https://github.com/nvm-sh/nvm"><code>nvm</code></a>, which stands for &quot;Node.js Version Manager&quot; (no surprises!), is probably the most famous Node.js version manager currently available.</p> +<p><code>nvm</code> works on any POSIX-compliant shell (<code>sh</code>, <code>dash</code>, <code>ksh</code>, <code>zsh</code>, <code>bash</code>, etc.) and it has been strongly tested against the following systems: unix, macOS, and windows WSL (if you are on Windows, you can also check out <a href="https://github.com/coreybutler/nvm-windows"><code>nvm-windows</code></a>).</p> +<p>The easiest way to install <code>nvm</code> on your system is to use the official installer script:</p> +<pre class="language-bash"><code class="language-bash"><span class="token assign-left variable">VERSION</span><span class="token operator">=</span>v0.37.2 +<span class="token function">curl</span> -o- <span class="token string">"https://raw.githubusercontent.com/nvm-sh/nvm/<span class="token variable">${VERSION}</span>/install.sh"</span> <span class="token operator">|</span> <span class="token function">bash</span></code></pre> +<p><strong>Note</strong>: At the time of writing, version <code>v0.37.2</code> is the latest version available. Make sure to check out if there is any new version available if you are installing <code>nvm</code> following this tutorial.</p> +<p>Once <code>nvm</code> is installed in your system, here are some examples showing what you can do with it:</p> +<pre class="language-bash"><code class="language-bash"><span class="token comment"># installs the latest version of Node.js</span> +nvm <span class="token function">install</span> <span class="token function">node</span> + +<span class="token comment"># installs the latest LTS version of Node.js</span> +nvm <span class="token function">install</span> <span class="token parameter variable">--lts</span> + +<span class="token comment"># installs a specific version of Node.js</span> +nvm <span class="token function">install</span> <span class="token string">"10.10.0"</span> + +<span class="token comment"># switch to a specific version of Node.js</span> +nvm use <span class="token string">"8.9.1"</span> + +<span class="token comment"># runs a specific script with a given version of Node.js (no switch)</span> +nvm <span class="token builtin class-name">exec</span> <span class="token string">"4.2"</span> <span class="token function">node</span> somescript.js + +<span class="token comment"># shows the full path where a given version of Node.js was installed</span> +nvm <span class="token function">which</span> <span class="token string">"4.2"</span> + +<span class="token comment"># lists all the versions of Node.js available</span> +nvm <span class="token function">ls</span></code></pre> +<p>One great thing about <code>nvm</code> is that it allows to specify the Node.js version you want to use for a given project.</p> +<p>For instance, if you are working on a project that requires you to use Node.js <code>10.10</code> you can do the following (in the root folder of the project):</p> +<pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">echo</span> <span class="token string">"10.10"</span> <span class="token operator">></span> .nvmrc</code></pre> +<p>Then every time you work on that project, you only need to run:</p> +<pre class="language-bash"><code class="language-bash">nvm use</code></pre> +<p>Which should print something like this:</p> +<pre><code>Found '/path/to/project/.nvmrc' with version &lt;10.10&gt; +Now using node v10.10.1 (npm v6.7.3) +</code></pre> +<p>At this point, you can be sure that you working using the correct Node.js version for your project.</p> +<p>If you don't want to do manually, you can enable <a href="https://github.com/nvm-sh/nvm#deeper-shell-integration">deeper shell integration</a> to make this happen automatically when you <code>cd</code> into a folder that has a <code>.nvmrc</code> file.</p> +<p><strong>PRO tip</strong>: You can also do that by using <a href="https://asdf-vm.com/"><code>asdf</code></a>, a <em>meta</em> version manager that offers a unified interface for various programming languages and version managers (including Node.js, of course).</p> +<p>Finally, here are some pros and cons of <code>nvm</code>:</p> +<ul> +<li>👍 Most popular version manager for Node.js with a large community of users.</li> +<li>👍 Very easy to install on POSIX systems.</li> +<li>👍 It allows for easy (and even automated) switch of Node.js version based on the project you are working on.</li> +<li>👍 It keeps all the installed versions cached, so you can switch quicly between versions (no full re-install)</li> +<li>👍 You can run once off commands on a given version of Node.js without having to switch the entire system to that version.</li> +<li>👎 You might have to take a bit of time to go through the documentation and make sure you install it and use it correctly.</li> +</ul> +<p><strong>Note</strong>: if you like to use version managers like <code>n</code> or <code>nvm</code>, you can also check out <a href="https://volta.sh/"><code>volta.sh</code></a>, another interesting alternative in this space, which defines itself as <em>&quot;The Hassle-Free JavaScript Tool Manager&quot;</em>.</p> +<h2 id="install-node.js-using-the-official-installer" tabindex="-1" class="title is-2">Install Node.js using the official installer</h2> +<p>The second most common way to install Node.js is through one of the official installers or the pre-compiled binaries.</p> +<p><a href="https://nodejs.org/en/download/">Official installers</a> are available on the official Node.js website for Windows and macOS and they cover the latest <em>Active LTS</em> release and the latest <em>Current</em> release.</p> +<p>The installer for Windows is an executable <em>.msi</em> installer, while the one for macOS is a <em>.pkg</em> one.</p> +<p>These installers behave and look like most of the installers you see while installing software on Windows or macOS. You will be presented with clickable UI which will allow you to customise and install Node.js into your system.</p> +<span style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 732px; "> +<picture> + <source type="image/png" srcset="https://www.nodejsdesignpatterns.com/img/node-js-macos-installer-screenshot-ceJZLISO1x-64.png 64w, https://www.nodejsdesignpatterns.com/img/node-js-macos-installer-screenshot-ceJZLISO1x-128.png 128w, https://www.nodejsdesignpatterns.com/img/node-js-macos-installer-screenshot-ceJZLISO1x-256.png 256w, https://www.nodejsdesignpatterns.com/img/node-js-macos-installer-screenshot-ceJZLISO1x-512.png 512w, https://www.nodejsdesignpatterns.com/img/node-js-macos-installer-screenshot-ceJZLISO1x-732.png 732w" sizes="(max-width: 732px) 100vw, 732px" /> +<source type="image/webp" srcset="https://www.nodejsdesignpatterns.com/img/node-js-macos-installer-screenshot-ceJZLISO1x-64.webp 64w, https://www.nodejsdesignpatterns.com/img/node-js-macos-installer-screenshot-ceJZLISO1x-128.webp 128w, https://www.nodejsdesignpatterns.com/img/node-js-macos-installer-screenshot-ceJZLISO1x-256.webp 256w, https://www.nodejsdesignpatterns.com/img/node-js-macos-installer-screenshot-ceJZLISO1x-512.webp 512w, https://www.nodejsdesignpatterns.com/img/node-js-macos-installer-screenshot-ceJZLISO1x-732.webp 732w" sizes="(max-width: 732px) 100vw, 732px" /> +<img loading="lazy" decoding="async" style="max-width: 100%; width: 100%; margin: 0px; vertical-align: middle;" alt="Install Node.js using the official macOS installer" src="https://www.nodejsdesignpatterns.com/img/node-js-macos-installer-screenshot-ceJZLISO1x-64.png" width="64" height="48" /> +</picture> +</span> +<p>This is probably the easiest way to install Node.js as you don't need to be a POSIX expert or do any kind of manual configuration. The installer will suggest sensible defaults to you and allow you to customise the main parameters (e.g. installation path).</p> +<p>If you are running a unix system, there is no official graphical installer available, but the <a href="https://nodejs.org/dist/">official Node.js download page</a> offers a set of pre-compiled binaries for most architectures (32-bit, 64-bit, ARMv7 and ARMv8) for Linux, Windows and macOS.</p> +<span style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 700px; "> +<picture> + <source type="image/png" srcset="https://www.nodejsdesignpatterns.com/img/node-js-macos-precompiled-binary-XVV50zv_zZ-64.png 64w, https://www.nodejsdesignpatterns.com/img/node-js-macos-precompiled-binary-XVV50zv_zZ-128.png 128w, https://www.nodejsdesignpatterns.com/img/node-js-macos-precompiled-binary-XVV50zv_zZ-256.png 256w, https://www.nodejsdesignpatterns.com/img/node-js-macos-precompiled-binary-XVV50zv_zZ-512.png 512w, https://www.nodejsdesignpatterns.com/img/node-js-macos-precompiled-binary-XVV50zv_zZ-1077.png 1077w" sizes="(max-width: 700px) 100vw, 700px" /> +<source type="image/webp" srcset="https://www.nodejsdesignpatterns.com/img/node-js-macos-precompiled-binary-XVV50zv_zZ-64.webp 64w, https://www.nodejsdesignpatterns.com/img/node-js-macos-precompiled-binary-XVV50zv_zZ-128.webp 128w, https://www.nodejsdesignpatterns.com/img/node-js-macos-precompiled-binary-XVV50zv_zZ-256.webp 256w, https://www.nodejsdesignpatterns.com/img/node-js-macos-precompiled-binary-XVV50zv_zZ-512.webp 512w, https://www.nodejsdesignpatterns.com/img/node-js-macos-precompiled-binary-XVV50zv_zZ-1077.webp 1077w" sizes="(max-width: 700px) 100vw, 700px" /> +<img loading="lazy" decoding="async" style="max-width: 100%; width: 100%; margin: 0px; vertical-align: middle;" alt="Install Node.js using the official macOS installer" src="https://www.nodejsdesignpatterns.com/img/node-js-macos-precompiled-binary-XVV50zv_zZ-64.png" width="64" height="26" /> +</picture> +</span> +<p>With the binary distribution, it is up to you to copy the necessary files in the right place. A version manager tool such as <code>nvm</code> and <code>n</code> makes things simple, because it takes care of downloading the correct binary release for the desired version (and for your system), then it places the files in the correct folder as expected by your operative system. If you choose to download the binaries manually, all the wiring is up to you.</p> +<p>While installing Node.js using the official installers is probably the simplest option, doing it using the binaries is a lot more complicated and definitely more complicated than using a version manager.</p> +<p>If you still want to go down this path, make sure to check out the <a href="https://github.com/nodejs/help/wiki/Installation">official tutorial for installing from Node.js pre-compiled binaries</a>.</p> +<p>It is definitely worth mentioning that the official installer is not the only option. <a href="https://nodesource.com/">NodeSource</a> maintains alternative installers for Debian, Red Hat, macOS and Windows. If you are interested in this approach checkout <a href="https://node.dev/node-binary">NodeSource Node.js Binary distributions page</a>.</p> +<p>To summarise, these are the main pros and cons of Node.js installers and binary distributions:</p> +<ul> +<li>👍 Installers are quite easy to use and they don't require specific POSIX experience.</li> +<li>👎 Hard to switch between version or upgrade. If you want to do that, you basically have to download the specific installer for the desired version and run through the full process again.</li> +<li>👎 Installer often will install Node.js as admin, which means that you can't install global packages unless you do that as admin.</li> +<li>👎 Binary packages require you to manually manage all the files and configuration.</li> +</ul> +<h2 id="install-node.js-using-a-package-manager" tabindex="-1" class="title is-2">Install Node.js using a package manager</h2> +<p>If you are the kind of person that loves to install and manage all the software in your device using system package managers such as <code>apt</code> (Debian / Ubuntu), <code>brew</code> (macOS), or <code>winget</code> (Windows), installing Node.js through a package manager is definitely an option.</p> +<p>A word of warning though, the various Node.js packages in every package repository are not officially maintained by the Node.js core team, so your mileage might vary quite a lot. This also means that you might not have fresh releases available straight away in your package manager of choice.</p> +<p>The Node.js core team has compiled an official documentation page on <a href="https://nodejs.org/en/download/package-manager/">how to install Node.js using the most common system package managers</a>.</p> +<p>Let's see here a summary for the most common options:</p> +<pre class="language-bash"><code class="language-bash"><span class="token comment"># Homebrew (macOS)</span> +brew <span class="token function">install</span> <span class="token function">node</span> + +<span class="token comment"># Arch Linux</span> +pacman <span class="token parameter variable">-S</span> nodejs <span class="token function">npm</span> + +<span class="token comment"># CentOS, Fedora and Red Hat Enterprise Linux</span> +dnf module list nodejs + +<span class="token comment"># Debian and Ubuntu based Linux distributions</span> +<span class="token function">apt-get</span> <span class="token function">install</span> <span class="token parameter variable">-y</span> nodejs + +<span class="token comment"># FreeBSD</span> +pkg <span class="token function">install</span> <span class="token function">node</span> + +<span class="token comment"># Gentoo</span> +emerge nodejs + +<span class="token comment"># Winget (Windows)</span> +winget <span class="token function">install</span> <span class="token parameter variable">-e</span> <span class="token parameter variable">--id</span> OpenJS.Nodejs + +<span class="token comment"># Chocolatey (Windows)</span> +cinst nodejs.install + +<span class="token comment"># Scoop (Windows)</span> +scoop <span class="token function">install</span> nodejs</code></pre> +<p>In short, this is &quot;the good&quot; and &quot;the bad&quot; of following this approach:</p> +<ul> +<li>👍 Familiar approach if you install software often using your system package manager.</li> +<li>👎 Latest Node.js versions might not be immediately available in your package manager of choice. Some versions might not be available at all.</li> +<li>👎 In most cases, Node.js is installed as super user, which makes it harder to install global packages with <code>npm</code>.</li> +</ul> +<h2 id="install-node.js-from-source" tabindex="-1" class="title is-2">Install Node.js from source</h2> +<p>If you are brave enough to be willing to build and install Node.js from source, your first stop should be the <a href="https://github.com/nodejs/node/blob/main/BUILDING.md">official documentation on how to build Node.js from source</a>.</p> +<p>Here is a brief summary of all the steps involved:</p> +<ol> +<li>Install the necessary build dependencies (C++ compiler and build toolchains) for your target system.</li> +<li>Install Python (used by the build process).</li> +<li>Download the source code from the <a href="https://github.com/nodejs/node">official repository</a>.</li> +<li>Launch <code>./configure</code> and then <code>make</code>.</li> +<li>Test your compiled version with <code>make test</code>.</li> +<li>Install it with <code>make install</code>.</li> +</ol> +<p>If all went well, you should have the <code>node</code> binary available on your system and be able to run:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">node</span> <span class="token parameter variable">--version</span></code></pre> +<p>Finally, here is the usual summary of pros and cons:</p> +<ul> +<li>👍 You can install any version of Node.js, including master or even work in progress from a dev branch or a PR. You can even play around with custom changes and get to the point where you might decide to contribute back to Node.js.</li> +<li>👍 You have full control on how to compile and install Node.js and don't have to follow pre-defined structures.</li> +<li>👎 You might need to install a bunch of additional build requirements (compilers, build tools, etc.) before you can even start with the process.</li> +<li>👎 Definitely the most complicated and the slowest way to get Node.js in your machine.</li> +</ul> +<h2 id="node.js-with-docker" tabindex="-1" class="title is-2">Node.js with Docker</h2> +<p>If you just want to &quot;play&quot; a bit with a Node.js REPL, you don't need to install Node.js in your system. If you have <code>docker</code> installed in your system, running a Node.js REPL in a container is as easy as running:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run <span class="token parameter variable">-it</span> <span class="token function">node</span></code></pre> +<p>Here's a super quick demo:</p> +<p style="text-align: center"><img loading="lazy" decoding="async" style="max-width: 100%; margin: 0px; vertical-align: middle;" alt="Running a Node.js REPL using Docker" src="https://www.nodejsdesignpatterns.com/img/node-repl-with-docker_edc66675.gif" width="750" height="482" /></p> +<p>If you want to run a shell in a container with Node.js and <code>npm</code> installed, then you can do the following:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run <span class="token parameter variable">-it</span> <span class="token function">node</span> <span class="token function">bash</span></code></pre> +<p>This way you can install third-party modules using <code>npm</code>, create your own scripts and run them with <code>node</code>. When you close the session the container and all the generated files will be destroyed.</p> +<p>This is the perfect environment for quick and dirty experiments.</p> +<p>Note, that you can also use Docker as a complete environment for development and not just for quick tests. Docker is actually great for keeping different Node.js version and other dependencies isolated on a per-project basis. Exploring this setup goes beyond the scope of this article, but there is ton of reference on the web about how you might use Docker for Node.js development.</p> +<h2 id="node.js-online" tabindex="-1" class="title is-2">Node.js online</h2> +<p>But what if you don't have docker installed and still want to have an environment where you can write and run some Node.js code?</p> +<p>Well, there is no shortage of platforms online that will give you a Node.js environment and an IDE that you can use to write and run JavaScript online.</p> +<p>These environments often offer delightful additional features like collaborative edit and the possibility to host and share your applications.</p> +<p>Here's a non-exhaustive list of services that you might want to try if you just need a quick way to write and share some Node.js examples:</p> +<ul> +<li><a href="https://codesandbox.io/">CodeSandbox</a></li> +<li><a href="https://repl.it/">Repl.it</a></li> +<li><a href="https://glitch.com/">Glitch</a></li> +<li><a href="https://stackblitz.com/">Stackblitz</a></li> +</ul> +<p>Most of these services offer a quite generous free plan, so you only need to sign up to start coding!</p> +<h2 id="conclusion" tabindex="-1" class="title is-2">Conclusion</h2> +<p>This concludes our list of ways to install Node.js. At this point, I hope you feel comfortable enough picking one of the options suggested here and that along the way you learned a trick or two.</p> +<p>If you enjoyed this article please consider sharing it and don't hesitate to reach out to me <a href="https://twitter.com/loige">on Twitter</a>. I am quite curious to find out what is your favourite way to install Node.js and why!</p> +<p>Until next time!</p> +<h3 id="credits" tabindex="-1" class="title is-3">Credits</h3> +<p>This article was possible only thanks to the great support and feedback of some amazing engineers. Here are some of the names that helped me (and sorry if I am forgetting someone): <a href="https://twitter.com/_Don_Quijote_">@_Don_Quijote_</a>, <a href="https://twitter.com/giuseppemorelli">@GiuseppeMorelli</a>, <a href="https://twitter.com/oliverturner">@oliverturner</a>, <a href="https://twitter.com/aetheon">@aetheon</a>, <a href="https://twitter.com/dottorblaster">@dottorblaster</a>, <a href="https://twitter.com/bcomnes">@bcomnes</a> &amp; <a href="https://twitter.com/wa7son">@wa7son</a>.</p> + + + + + Node.js race conditions + + 2021-01-24T18:35:00Z + https://www.nodejsdesignpatterns.com/blog/node-js-race-conditions/ + <p>A single-threaded event loop like the one used by JavaScript and Node.js, makes it somewhat harder to have race conditions, but, SPOILER ALERT: race conditions are still possible!</p> +<p>In this article, we will explore the topic of race conditions in Node.js. We will discuss some examples and present a few different solutions that can help us to make our code <em>race condition free</em>.</p> +<h2 id="what-is-a-race-condition%3F" tabindex="-1" class="title is-2">What is a race condition?</h2> +<p>First of all, let's try to clarify what a <em>race condition</em> actually is.</p> +<p>A race condition is a type of <em>programming error</em> that can occur when multiple processes or threads are accessing the same shared resource, for instance, a file on a file system or a record in a database, and at least one of them is trying to modify the resource.</p> +<p>Let's try to present an example. Imagine that while a thread is trying to rename a file, another thread is trying to delete the same file. In this case, the second thread will receive an error because, when it's trying to delete the file, the file has already been renamed. Or, the other way around, while one thread is trying to rename the file, the file was already deleted by the other thread and it's not available on the filesystem anymore.</p> +<p>In other cases, race conditions can be more subtle, because they wouldn't result in the program crashing, but they might just be the source of an incorrect or inconsistent behaviour. In these cases, since there is no explicit error and no stack trace, the issue is generally much harder to troubleshoot and fix.</p> +<p>A classic example is when 2 threads are trying to update the same data source and the new information is the result of a function applied to the current value.</p> +<p>Let's pretend we are building a Roman Empire simulation game in which we can manage some cash flow and we have a global balance in <a href="https://en.wiktionary.org/wiki/aureus"><em>aureus</em></a> (a currency used in the Roman Empire around 100 B.C.E.). Now, let's say that our initial balance is <code>0</code> <em>aurei</em> and that there are two independent game components (possibly running on separate threads) that are trying to increase the balance by <code>50</code> <em>aurei</em> each, we should expect that in the end, the balance is <code>100</code> <em>aurei</em>, right?</p> +<pre class="language-text"><code class="language-text">0 + 50 + 50 = 100 đŸ€‘</code></pre> +<p>If we implement this in a naive way, we might have the two components performing three distinct operations each:</p> +<ol> +<li>Read the current value for <code>balance</code></li> +<li>Add <code>50</code> <em>aurei</em> to it</li> +<li>Save the resulting value into <code>balance</code></li> +</ol> +<p>Since the two components are running in parallel, without any synchronisation mechanism, the following case could happen:</p> +<span style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 848px; "> +<picture> + <source type="image/png" srcset="https://www.nodejsdesignpatterns.com/img/aurei-race-condition-node-js-Fngk3b5IPO-64.png 64w, https://www.nodejsdesignpatterns.com/img/aurei-race-condition-node-js-Fngk3b5IPO-128.png 128w, https://www.nodejsdesignpatterns.com/img/aurei-race-condition-node-js-Fngk3b5IPO-256.png 256w, https://www.nodejsdesignpatterns.com/img/aurei-race-condition-node-js-Fngk3b5IPO-512.png 512w, https://www.nodejsdesignpatterns.com/img/aurei-race-condition-node-js-Fngk3b5IPO-2002.png 2002w" sizes="(max-width: 848px) 100vw, 848px" /> +<source type="image/webp" srcset="https://www.nodejsdesignpatterns.com/img/aurei-race-condition-node-js-Fngk3b5IPO-64.webp 64w, https://www.nodejsdesignpatterns.com/img/aurei-race-condition-node-js-Fngk3b5IPO-128.webp 128w, https://www.nodejsdesignpatterns.com/img/aurei-race-condition-node-js-Fngk3b5IPO-256.webp 256w, https://www.nodejsdesignpatterns.com/img/aurei-race-condition-node-js-Fngk3b5IPO-512.webp 512w, https://www.nodejsdesignpatterns.com/img/aurei-race-condition-node-js-Fngk3b5IPO-2002.webp 2002w" sizes="(max-width: 848px) 100vw, 848px" /> +<img loading="lazy" decoding="async" style="max-width: 100%; width: 100%; margin: 0px; vertical-align: middle;" alt="A race condition example showing 2 processes trying to update a balance" src="https://www.nodejsdesignpatterns.com/img/aurei-race-condition-node-js-Fngk3b5IPO-64.png" width="64" height="30" /> +</picture> +</span> +<p>In the picture above you can see that <strong>Component 2</strong> ends up having a <em>stale</em> view of the balance: the balance gets changed by <strong>Component 1</strong> after <strong>Component 2</strong> has read the balance. For this reason, when <strong>Component 2</strong> performs its own update, it is effectively overriding any change previously made by <strong>Component 1</strong>. This is why we have a race condition: the two components are effectively racing to complete their own tasks and they might end up stepping onto each other's toes! This doesn't make <em>Julius</em> happy I am afraid...</p> +<p>One way to solve this problem is to isolate the 2 concurrent operations into <em>transactions</em> and make sure that there is only one transaction running at a given time. This idea might look like this:</p> +<span style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 848px; "> +<picture> + <source type="image/png" srcset="https://www.nodejsdesignpatterns.com/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-64.png 64w, https://www.nodejsdesignpatterns.com/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-128.png 128w, https://www.nodejsdesignpatterns.com/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-256.png 256w, https://www.nodejsdesignpatterns.com/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-512.png 512w, https://www.nodejsdesignpatterns.com/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-2022.png 2022w" sizes="(max-width: 848px) 100vw, 848px" /> +<source type="image/webp" srcset="https://www.nodejsdesignpatterns.com/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-64.webp 64w, https://www.nodejsdesignpatterns.com/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-128.webp 128w, https://www.nodejsdesignpatterns.com/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-256.webp 256w, https://www.nodejsdesignpatterns.com/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-512.webp 512w, https://www.nodejsdesignpatterns.com/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-2022.webp 2022w" sizes="(max-width: 848px) 100vw, 848px" /> +<img loading="lazy" decoding="async" style="max-width: 100%; width: 100%; margin: 0px; vertical-align: middle;" alt="Fixing a race condition using a transaction" src="https://www.nodejsdesignpatterns.com/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-64.png" width="64" height="31" /> +</picture> +</span> +<p>In the last picture, we are using transactions to make sure that all the steps of <strong>Component 1</strong> happen in order before all the steps of <strong>Component 2</strong>. This prevents any <em>stale read</em> and makes sure that every component always has an up to date view of the world before doing any change. You can stop holding your breath now, Julius!</p> +<p>In the rest of this article, we will zoom in more on race conditions in the context of Node.js and we will see some other approaches to deal with them.</p> +<h2 id="can-we-have-race-conditions-in-node.js%3F" tabindex="-1" class="title is-2">Can we have race conditions in Node.js?</h2> +<p>It is a common misconception to think that Node.js does not have race conditions because of its single-threaded nature. While it is true that in Node.js you would not have multiple threads competing for resources, you might still end up with tasks belonging to different logical transactions being executed in an order that might result in <em>stale reads</em> and generate a race condition.</p> +<p>In the example that we illustrated above, we intentionally represented the various tasks (<em>read</em>, <em>increase</em> and <em>save</em>) as discrete units. Note how the system is never executing more than one task at the same time. This is a simple but accurate representation of how the Node.js event loop processes tasks on a single thread. Nonetheless, you can see that there might be situations where multiple logical transactions (e.g. multiple deposits) are scheduled concurrently on the event loop and the discrete tasks might end up being intermingled, which results in a race condition.</p> +<p>So... <strong>Yes</strong>, we can have race conditions in Node.js!</p> +<h2 id="a-node.js-example-with-a-race-condition" tabindex="-1" class="title is-2">A Node.js example with a race condition</h2> +<p>Now, let's talk some code! Let's try to re-create the Roman Empire simulation game example that we discussed above.</p> +<p>In ancient Rome, Romans used to export olives and grapes. No wonder Italy is still famous worldwide for olive oil and wine! In our game, we want to be able to harvest olives and grapes and then sell them as a means to acquire more <em>aurei</em>.</p> +<p>We are going to have two functions that can increase the balance by <code>50</code> <em>aurei</em> which we are going to call <code>sellOlives()</code> and <code>sellGrapes()</code>. We will also assume that every time the balance is changed, it is persisted to a data storage of sort (e.g. a database). For the sake of this example, we won't be using a real data storage, but we will just simulate some random asynchronous delay before reading or modifying a global value. This will be enough to illustrate how we can end up with a race condition.</p> +<p>For starts, let's see what a buggy implementation might look like:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token comment">// Utility function to simulate some delay (e.g. reading from or writing to a database).</span> +<span class="token comment">// It will take from 0 to 50ms in a random fashion.</span> +<span class="token keyword">const</span> <span class="token function-variable function">randomDelay</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token parameter">resolve</span> <span class="token operator">=></span> + <span class="token function">setTimeout</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">100</span><span class="token punctuation">)</span> +<span class="token punctuation">)</span> + +<span class="token comment">// Our global balance.</span> +<span class="token comment">// In a more complete implementation, this will live in the persistent data storage.</span> +<span class="token keyword">let</span> balance <span class="token operator">=</span> <span class="token number">0</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">loadBalance</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token comment">// simulates random delay to retrieve data from data storage</span> + <span class="token keyword">await</span> <span class="token function">randomDelay</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + <span class="token keyword">return</span> balance +<span class="token punctuation">}</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">saveBalance</span> <span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token comment">// simulates random delay to write the data to the data storage</span> + <span class="token keyword">await</span> <span class="token function">randomDelay</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + balance <span class="token operator">=</span> value +<span class="token punctuation">}</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">sellGrapes</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">const</span> balance <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">loadBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">sellGrapes - balance loaded: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>balance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> + <span class="token keyword">const</span> newBalance <span class="token operator">=</span> balance <span class="token operator">+</span> <span class="token number">50</span> + <span class="token keyword">await</span> <span class="token function">saveBalance</span><span class="token punctuation">(</span>newBalance<span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">sellGrapes - balance updated: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>newBalance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> +<span class="token punctuation">}</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">sellOlives</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">const</span> balance <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">loadBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">sellOlives - balance loaded: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>balance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> + <span class="token keyword">const</span> newBalance <span class="token operator">=</span> balance <span class="token operator">+</span> <span class="token number">50</span> + <span class="token keyword">await</span> <span class="token function">saveBalance</span><span class="token punctuation">(</span>newBalance<span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">sellOlives - balance updated: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>newBalance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> +<span class="token punctuation">}</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">const</span> transaction1 <span class="token operator">=</span> <span class="token function">sellGrapes</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// NOTE: no `await`</span> + <span class="token keyword">const</span> transaction2 <span class="token operator">=</span> <span class="token function">sellOlives</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// NOTE: no `await`</span> + <span class="token keyword">await</span> transaction1 <span class="token comment">// NOTE: awaiting here does not stop `transaction2` </span> + <span class="token comment">// from being scheduled before transaction 1 is completed</span> + <span class="token keyword">await</span> transaction2 + <span class="token keyword">const</span> balance <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">loadBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Final balance: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>balance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> +<span class="token punctuation">}</span> + +<span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre> +<p>If we execute this code we might end up with different results. In one case we might get the correct outcome:</p> +<pre class="language-text"><code class="language-text">sellOlives - balance loaded: 0 +sellOlives - balance updated: 50 +sellGrapes - balance loaded: 50 +sellGrapes - balance updated: 100 +Final balance: 100</code></pre> +<p>But in other cases we might end up in a bad state:</p> +<pre class="language-text"><code class="language-text">sellGrapes - balance loaded: 0 +sellOlives - balance loaded: 0 +sellGrapes - balance updated: 50 +sellOlives - balance updated: 50 +Final balance: 50</code></pre> +<p>Note how in this last case, <code>sellOlives</code> is essentially a stale read and therefore it will end up overriding the balance disregarding any work already done by <code>sellGrapes</code>. Yes, we do have a race condition, unfortunately!</p> +<p>Now, this example is simple ad it is not too hard to pinpoint exactly where the race condition has originated by just looking at the code.</p> +<p>Take a minute or two to read the code again. Check out the output from the 2 cases as well. Pay attention to the notes and the log messages and try to imagine how the Node.js runtime might execute this code in the 2 different scenarios.</p> +<p>Ok, now that you have done that, let's discuss together what happens.</p> +<p>In our <code>main</code> function, when we execute <code>sellGrapes()</code> and <code>sellOlives()</code>, since we are not awaiting the two operations independently, we are essentially scheduling both operations onto the event loop.</p> +<p>We only await the two transactions after they have been already scheduled, which means that they will work concurrently. After the two transactions have been scheduled, we wait for <code>transaction1</code> to complete and only then we wait for <code>transaction2</code> to complete. Note that <code>transaction2</code> might complete even before <code>transaction1</code>. In other words, awaiting for <code>transaction1</code> doesn't block <code>transaction2</code> in any way.</p> +<p>This approach is similar to writing the following code:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token function">sellGrapes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">sellOlives</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span></code></pre> +<p>Using <code>Promise.all()</code> is a more commonly used way to schedule different tasks to run concurrently.</p> +<p>Note that with <code>Promise.all()</code>, the resulting promise will reject as soon as any of the promises rejects. In our previous example, since we await the two promises independently, we will always catch errors in <code>transaction1</code> before <code>transaction2</code>.</p> +<p>But let's not digress too much into this. Now that we understand the problem, how do we fix the race condition?</p> +<p>Well, it turns out that in this simple case, we might make things right quite easily:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">await</span> <span class="token function">sellGrapes</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// &lt;- schedule the first transaction and wait for completion</span> + <span class="token keyword">await</span> <span class="token function">sellOlives</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// &lt;- when it's completed, we start the second transaction </span> + <span class="token comment">// and wait for completion</span> + <span class="token keyword">const</span> balance <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">loadBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Final balance: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>balance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> +<span class="token punctuation">}</span></code></pre> +<p>This implementation will consistently produce the following output:</p> +<pre class="language-text"><code class="language-text">sellGrapes - balance loaded: 0 +sellGrapes - balance updated: 50 +sellOlives - balance loaded: 50 +sellOlives - balance updated: 100 +Final balance: 100</code></pre> +<p>As we can observe, <code>sellGrapes</code> is always started and completed <em>before</em> we start <code>sellOlives</code>. This makes the two logical transactions isolated and makes sure their tasks won't end up being mixed together in random order.</p> +<p>Problem solved... <em>vade in pacem</em> dear race condition!</p> +<h2 id="using-a-mutex-in-node.js" tabindex="-1" class="title is-2">Using a mutex in Node.js</h2> +<p>OK, the previous example was illustrative, but if we are building a real game, chances are things will end up being a lot more complicated. We will probably end up having many different actions that might cause a change of balance. Those actions might be the result of a particular sequence of events and it might become hard to track down the discrete logical transactions that we have to <em>serialize</em> in order to avoid race conditions.</p> +<p>Ideally, we don't want to think in terms of transactions, we just need to make sure that we never read the balance if there is another concurrent operation that is ready to change its value.</p> +<p>To be able to do this we need two things:</p> +<ol> +<li>Have a way to identify when <em>we are about to change</em> the balance</li> +<li>Let other events wait in line until the change is completed before reading the balance</li> +</ol> +<p>We could say that when <em>we are about to change</em> the balance we enter a <em>critical path</em> and that we don't want to intermingle events from different logical transactions in a critical path.</p> +<p>One way to achieve this is by using a <em>Mutex</em> (which stands for <a href="https://en.wikipedia.org/wiki/Mutual_exclusion"><strong>mut</strong>ual <strong>ex</strong>clusion</a>).</p> +<p>A mutex is a mechanism that allows synchronising access to a shared resource.</p> +<p>We can see a mutex as a shared object that allows us to mark when the code execution is entering and exiting from a critical path. In addition to that, a mutex can help us to queue other logical transactions that want to access the same critical path while one transaction is being processed.</p> +<p>Before talking code, be aware that using a mutex might have a performance impact in your application and that this solution won't work if you use a distributed or a multi-process setup. More details on this later.</p> +<h2 id="using-async-mutex" tabindex="-1" class="title is-2">Using <code>async-mutex</code></h2> +<p>A very useful library that we can useg is <a href="https://npm.im/async-mutex"><code>async-mutex</code></a>. This library provides a promise-based implementation of the mutex pattern.</p> +<p>You can install this library from <code>npm</code>:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> <span class="token parameter variable">--save</span> async-mutex</code></pre> +<p>Now, here's an example of how we could use this library to mark the beginning and the end of a critical path:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Mutex <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'async-mutex'</span> + +<span class="token keyword">const</span> mutex <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Mutex</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// creates a shared mutex instance</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">doingSomethingCritical</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">const</span> release <span class="token operator">=</span> <span class="token keyword">await</span> mutex<span class="token punctuation">.</span><span class="token function">acquire</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// acquires access to the critical path</span> + <span class="token keyword">try</span> <span class="token punctuation">{</span> + <span class="token comment">// ... do stuff on the critical path</span> + <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> + <span class="token function">release</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// completes the work on the critical path</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>In this example, we are using a global mutex instance to mark the beginning and the end of a critical path which happens inside our <code>doingSomethingCritical()</code> function.</p> +<p>When we call <code>mutex.acquire()</code>, this method will return a promise. If no other concurrent operation is currently on the same critical path, the promise resolves to a function that we call <code>release</code>. In this situation, we are essentially granted exclusive access to the critical path. If some concurrent operation is on the critical path already, the promise won't resolve until the concurrent operation already on the critical path has completed. This is how concurrent operations <em>wait in line</em> for our exclusive access to the critical path.</p> +<p>The <code>release</code> function must be invoked to mark the completion of the work on the critical path. It effectively <em>releases</em> the exclusive access to the critical path and makes it available to the next task in line. Note that we are using a <code>try</code>/<code>finally</code> block here to make sure that <code>release</code> is called even in case of an exception. It is very important to do so. In fact, failing to call <code>release</code>, will leave all the other events waiting in line forever!</p> +<p>Now let's try to use <code>async-mutex</code> to avoid race conditions in our game:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Mutex <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'async-mutex'</span> + +<span class="token keyword">const</span> <span class="token function-variable function">randomDelay</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token comment">/* ... */</span><span class="token punctuation">}</span> + +<span class="token keyword">let</span> balance <span class="token operator">=</span> <span class="token number">0</span> +<span class="token keyword">const</span> mutex <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Mutex</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// global mutex instance</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">loadBalance</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment">/* ... */</span><span class="token punctuation">}</span> +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">saveBalance</span> <span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment">/* ... */</span><span class="token punctuation">}</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">sellGrapes</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token comment">// this code will need exclusive access to the balance</span> + <span class="token comment">// so we consider this to be a critical path</span> + <span class="token keyword">const</span> release <span class="token operator">=</span> <span class="token keyword">await</span> mutex<span class="token punctuation">.</span><span class="token function">acquire</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// get access to the critical path (or wait in line)</span> + <span class="token keyword">try</span> <span class="token punctuation">{</span> + <span class="token keyword">const</span> balance <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">loadBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">sellGrapes - balance loaded: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>balance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> + <span class="token keyword">const</span> newBalance <span class="token operator">=</span> balance <span class="token operator">+</span> <span class="token number">50</span> + <span class="token keyword">await</span> <span class="token function">saveBalance</span><span class="token punctuation">(</span>newBalance<span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">sellGrapes - balance updated: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>newBalance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> + <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> + <span class="token function">release</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// completes work on the critical path</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">sellOlives</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token comment">// similar to `sellGrapes` this is a critical path because</span> + <span class="token comment">// it needs exclusive access to balance</span> + <span class="token keyword">const</span> release <span class="token operator">=</span> <span class="token keyword">await</span> mutex<span class="token punctuation">.</span><span class="token function">acquire</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + <span class="token keyword">try</span> <span class="token punctuation">{</span> + <span class="token keyword">const</span> balance <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">loadBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">sellOlives - balance loaded: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>balance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> + <span class="token keyword">const</span> newBalance <span class="token operator">=</span> balance <span class="token operator">+</span> <span class="token number">50</span> + <span class="token keyword">await</span> <span class="token function">saveBalance</span><span class="token punctuation">(</span>newBalance<span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">sellOlives - balance updated: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>newBalance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> + <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> + <span class="token function">release</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token comment">// Here we can call many events safely, the mutex will guarantee that the</span> + <span class="token comment">// competing events are executed in the right order!</span> + <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span> + <span class="token function">sellGrapes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> + <span class="token function">sellOlives</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> + <span class="token function">sellGrapes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> + <span class="token function">sellOlives</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> + <span class="token function">sellGrapes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> + <span class="token function">sellOlives</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + <span class="token punctuation">]</span><span class="token punctuation">)</span> + <span class="token keyword">const</span> balance <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">loadBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Final balance: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>balance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> +<span class="token punctuation">}</span> + +<span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre> +<p>The code above will consistently produce the following output:</p> +<pre class="language-text"><code class="language-text">sellGrapes - balance loaded: 0 +sellGrapes - balance updated: 50 +sellOlives - balance loaded: 50 +sellOlives - balance updated: 100 +sellGrapes - balance loaded: 100 +sellGrapes - balance updated: 150 +sellOlives - balance loaded: 150 +sellOlives - balance updated: 200 +sellGrapes - balance loaded: 200 +sellGrapes - balance updated: 250 +sellOlives - balance loaded: 250 +sellOlives - balance updated: 300 +Final balance: 300</code></pre> +<p>Some of the code has been truncated for simplicity. You can find all the examples in <a href="https://github.com/lmammino/node-js-race-conditions">this repository</a>.</p> +<p>From the example above, you can see how mutexes can provide a convenient way of thinking about exclusive access and how they can help to avoid race conditions. We are intentionally triggering multiple calls to <code>sellGrapes()</code> and <code>sellOlives()</code> concurrently, to make obvious that we don't have to think about potential race conditions at the <em>calling point</em>. This means that, as our game grows more complicated, we can keep invoking these functions without having to worry about generating new race conditions.</p> +<h2 id="let's-implement-a-mutex" tabindex="-1" class="title is-2">Let's implement a mutex</h2> +<p>But what if we are dealing with a race condition only in one place in our entire application? Is it worth to include and manage an external dependecy just because of that? Can we come up with a simpler alternative that does not require us to install a new dependency?</p> +<p>It turns out that we can easily do that! Let's see how we can implement our own mutex.</p> +<p>Note that the solution we are going to present here is effectively a variation of the <strong>sequential execution pattern</strong> using promises that is presented in <em>Chapter 5</em> of <a href="https://www.nodejsdesignpatterns.com/">Node.js Design Patterns</a>.</p> +<p>The idea is to inizialize our global mutex as an instance of a resolved promise:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">let</span> mutex <span class="token operator">=</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre> +<p>Then in our critical path we can do something like this:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">doingSomethingCritical</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + mutex <span class="token operator">=</span> mutex<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> + <span class="token comment">// ... do stuff on the critical path</span> + <span class="token punctuation">}</span><span class="token punctuation">)</span> + <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> + <span class="token comment">// ... manage errors on the critical path</span> + <span class="token punctuation">}</span><span class="token punctuation">)</span> + <span class="token keyword">return</span> mutex +<span class="token punctuation">}</span></code></pre> +<p>The idea is that every time we are invoking the function <code>doingSomethingCritical()</code> we are effectively &quot;queueing&quot; the execution of the code on the critical path using <code>mutex.then()</code>. If this is the first call, our initial instance of the <code>mutex</code> promise is a resolved promise, so the code on the critical path will be executed straight away on the next cycle of the event loop.</p> +<p>Calling <code>.then()</code> on a promise returns a new promise instance that is used to replace the original <code>mutex</code> instance and it's also returned by the <code>doingSomethingCritical()</code> function.</p> +<p>This allows us to have concurrent calls to <code>doingSomethingCritical()</code> being queued to be executed sequentially.</p> +<p>Note that we also specify a <code>mutex.catch()</code>. This allows us to catch and react to specific errors, but it also allows us not to break the chain of sequential execution in case an operation fails.</p> +<p>Ok, now that we have explored this idea, let's apply it to our example.</p> +<p>This is how our code is going to look like:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">randomDelay</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token comment">/* ... */</span><span class="token punctuation">}</span> + +<span class="token keyword">let</span> balance <span class="token operator">=</span> <span class="token number">0</span> +<span class="token keyword">let</span> mutex <span class="token operator">=</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// global mutex instance</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">loadBalance</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment">/* ... */</span><span class="token punctuation">}</span> +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">saveBalance</span> <span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment">/* ... */</span><span class="token punctuation">}</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">sellGrapes</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + mutex <span class="token operator">=</span> mutex<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> + <span class="token keyword">const</span> balance <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">loadBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">sellGrapes - balance loaded: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>balance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> + <span class="token keyword">const</span> newBalance <span class="token operator">=</span> balance <span class="token operator">+</span> <span class="token number">50</span> + <span class="token keyword">await</span> <span class="token function">saveBalance</span><span class="token punctuation">(</span>newBalance<span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">sellGrapes - balance updated: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>newBalance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> + <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> + <span class="token keyword">return</span> mutex +<span class="token punctuation">}</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">sellOlives</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + mutex <span class="token operator">=</span> mutex<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> + <span class="token keyword">const</span> balance <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">loadBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">sellOlives - balance loaded: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>balance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> + <span class="token keyword">const</span> newBalance <span class="token operator">=</span> balance <span class="token operator">+</span> <span class="token number">50</span> + <span class="token keyword">await</span> <span class="token function">saveBalance</span><span class="token punctuation">(</span>newBalance<span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">sellOlives - balance updated: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>newBalance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> + <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> + <span class="token keyword">return</span> mutex +<span class="token punctuation">}</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span> + <span class="token function">sellGrapes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> + <span class="token function">sellOlives</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> + <span class="token function">sellGrapes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> + <span class="token function">sellOlives</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> + <span class="token function">sellGrapes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> + <span class="token function">sellOlives</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + <span class="token punctuation">]</span><span class="token punctuation">)</span> + <span class="token keyword">const</span> balance <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">loadBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Final balance: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>balance<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> +<span class="token punctuation">}</span> + +<span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre> +<p>If you try to run this code, you will see that it consistently prints the same output as per our previous implementation using <code>async-mutex</code>!</p> +<p>So, here we have it, a simple mutex implementation in just few lines of code leveraging promise chainability!</p> +<h2 id="mutex-with-multiple-processes" tabindex="-1" class="title is-2">Mutex with multiple processes</h2> +<p>It is important to mention that the solutions presented in this article only work in a Node.js application running on a single process.</p> +<p>If you are running your application on multiple processes (for instance, by using the <a href="https://nodejs.org/api/cluster.html"><code>cluster</code> module</a>, <a href="https://nodejs.org/api/worker_threads.html">worker threads</a> or a multi-process runner like <a href="https://pm2.keymetrics.io/"><code>pm2</code></a>) using a mutex within our code is not going to solve race conditions across processes. This is also the case if you are running your application on multiple servers.</p> +<p>In these cases you have to rely on more complicated solutions like <a href="https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html">distributed locks</a> or, if you are using a central database, you can rely on solutions provided by your own database systems. We will discuss a simple example in the next section.</p> +<h2 id="mutex-performance" tabindex="-1" class="title is-2">Mutex performance</h2> +<p>We already mentioned that using a mutex might have a relevant performance impact in your application.</p> +<p>To try to visualize why a mutex has a performance impact in your application let's try to think about the case when an operation is trying to acquire a lock on a mutex but the mutex is already locked. In this case, our operation is simply waiting without doing nothing, while for instance it could be doing some IO operation like connecting to the database or sending a query. It will probably take the event loop several spins before the lock is released and the operation that is waiting in line can acquire the lock. This get worse with a high number of operations waiting in line.</p> +<p>With a mutex we are effectively serializing tasks, making sure that executed in sequence and non-concurrently. If you abuse this pattern, you might end up in a situation where you could effectively eliminate all concurrency from your application.</p> +<p>Measuring how a mutex might impact your specific application is not something that can be done holistically and we recommend you to run your own benchmarks to find out what is the effect of introducing one or more mutex instances in your application.</p> +<p>Our general recommendation is to use a mutex only when you are sure you have to protect your code from a race condition and to try to make the critical path as short as possible.</p> +<p>Be aware that a mutex is not the only solution to race conditions. For instance, in our example, if we were to use a real relational database as a data storage, we could have avoided any race condition (at the application level) by letting the database itself do the increment using a SQL query:</p> +<pre class="language-sql"><code class="language-sql"><span class="token keyword">UPDATE</span> game <span class="token keyword">SET</span> aurei <span class="token operator">=</span> aurei <span class="token operator">+</span> <span class="token number">50</span><span class="token punctuation">;</span></code></pre> +<p>With this approach, we are trusting the database to do the right thing and we are not slowing down our application.</p> +<p>And there are other alternative approches. Just to name one, <a href="https://en.wikipedia.org/wiki/Optimistic_concurrency_control">optimistic locks</a> might provide a great alternative if race conditions are possible but they actually happen only in rare occasions.</p> +<h2 id="conclusion" tabindex="-1" class="title is-2">Conclusion</h2> +<p>In this article, we have explored race conditions and learned why they can be harmful. We showed how race conditions can happen in Node.js and several techniques to address them including the adoptopm of a mutex.</p> +<p>This is an interesting topic which often gets explored in the context of multi-threaded languages. The theory isn't much different but there are some important differences when dealing with concurrent, single-threaded languages like Node.js.</p> +<p>If you are curious to understand better the difference between <strong>Parallelism</strong> and <strong>Concurrency</strong> I strongly recommend you to read this great essay titled <a href="http://yosefk.com/blog/parallelism-and-concurrency-need-different-tools.html">parallelism and concurrency need different tools</a>. You can also watch this wonderful talk by <a href="https://twitter.com/steveklabnik">Steve Klabnik</a> called <a href="https://www.youtube.com/watch?v=lJ3NC-R3gSI">Rust's Journey to Async/Await</a> (yes, it's not only about Rust, trust me).</p> +<p>I really hope you enjoyed this article. Make sure to <a href="https://twitter.com/loige">reach out to me on Twitter</a> and let me know what you think!</p> +<p>Bye 😋</p> +<h2 id="credits" tabindex="-1" class="title is-2">Credits</h2> +<p>Thanks to <a href="https://github.com/Jack-Barry">Jack Barry</a> for the inspiration for this post on the <a href="https://github.com/PacktPublishing/Node.js-Design-Patterns-Third-Edition/discussions/25">Node.js Design Patterns discussion board</a>. Thanks to <a href="https://twitter.com/quasi_modal">Peter Caulfield</a>, <a href="https://twitter.com/StefanoAbalsamo">Stefano Abalsamo</a>, <a href="https://twitter.com/gbinside">Roberto Gambuzzi</a> and <a href="https://twitter.com/mariocasciaro">Mario Casciaro</a> for kindly reviewing this post.</p> + + + + + Node.js development with Docker and Docker Compose + + 2021-04-07T18:00:00Z + https://www.nodejsdesignpatterns.com/blog/node-js-development-with-docker-and-docker-compose/ + <p>In this article, we are going to learn how to use Docker and Docker Compose for Node.js development. We will discuss the main benefits of this approach and explore some interesting examples. While doing that we will also learn what Docker is and why you should consider it, especially if you are developing multiple projects or if you are working in a team.</p> +<h2 id="what-is-a-docker-container-in-simple-terms%3F" tabindex="-1" class="title is-2">What is a Docker container in simple terms?</h2> +<p>To explain what a Docker container is, let's just start by imagining a virtual machine or a virtual server provisioned to run a specific program.</p> +<p>Virtual machines are great because they allow you to run some useful software in an isolated way and they are easy to distribute. You can create an image and run it in different environments. You can even run it in your own desktop machine or share it with your own colleagues. It's a consistent medium to develop and deploy software.</p> +<p>In many ways, Docker containers, are very similar to virtual machines. Docker containers are another way to package (or &quot;containerise&quot;) software and run it across different environments.</p> +<p>But there's a fundamental difference with virtual machines. With Docker containers, you don't have the overhead of having to include an entire operating system as part of your image, but only the basic dependencies needed to run some programs.</p> +<span style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; "> +<picture> + <source type="image/png" srcset="https://www.nodejsdesignpatterns.com/img/docker-vs-virtual-machines-m-F-bSuk1W-64.png 64w, https://www.nodejsdesignpatterns.com/img/docker-vs-virtual-machines-m-F-bSuk1W-128.png 128w, https://www.nodejsdesignpatterns.com/img/docker-vs-virtual-machines-m-F-bSuk1W-256.png 256w, https://www.nodejsdesignpatterns.com/img/docker-vs-virtual-machines-m-F-bSuk1W-512.png 512w, https://www.nodejsdesignpatterns.com/img/docker-vs-virtual-machines-m-F-bSuk1W-2900.png 2900w" sizes="(max-width: 800px) 100vw, 800px" /> +<source type="image/webp" srcset="https://www.nodejsdesignpatterns.com/img/docker-vs-virtual-machines-m-F-bSuk1W-64.webp 64w, https://www.nodejsdesignpatterns.com/img/docker-vs-virtual-machines-m-F-bSuk1W-128.webp 128w, https://www.nodejsdesignpatterns.com/img/docker-vs-virtual-machines-m-F-bSuk1W-256.webp 256w, https://www.nodejsdesignpatterns.com/img/docker-vs-virtual-machines-m-F-bSuk1W-512.webp 512w, https://www.nodejsdesignpatterns.com/img/docker-vs-virtual-machines-m-F-bSuk1W-2900.webp 2900w" sizes="(max-width: 800px) 100vw, 800px" /> +<img loading="lazy" decoding="async" style="max-width: 100%; width: 100%; margin: 0px; vertical-align: middle;" alt="Docker VS virtual machines" src="https://www.nodejsdesignpatterns.com/img/docker-vs-virtual-machines-m-F-bSuk1W-64.png" width="64" height="23" /> +</picture> +</span> +<p>From the image above you can see that the <strong>Container Engine</strong> runs on top of the operative system and that you don't have to include a guest operative system into your container images. For this reason, containers are much more lightweight than traditional virtual machines, while still providing the benefits of isolation and portability.</p> +<p>But how can we create a new Docker image?</p> +<p>This is generally done by using a special configuration file called <code>Dockerfile</code>. Let's not indulge with more theory and let's see an example instead!</p> +<p>Before getting started let's make sure that you have Docker installed in your machine. You can get docker for most platform using the installers available on the <a href="https://docs.docker.com/get-docker/">official Docker website</a>.</p> +<p>Ok, if you are ready, let's create our first <code>Dockerfile</code>:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">touch</span> Dockerfile</code></pre> +<p>What do we want to put into our Dockerfile? Let's keep things simple for now and let's say that we want to create a container that just run <code>npm</code>. To achieve this, this is what we can write into our <code>Dockerfile</code>:</p> +<pre class="language-dockerfile"><code class="language-dockerfile"><span class="token instruction"><span class="token keyword">FROM</span> debian:buster</span> +<span class="token instruction"><span class="token keyword">RUN</span> apt-get update &amp;&amp; apt-get install nodejs npm -y</span> +<span class="token instruction"><span class="token keyword">CMD</span> [<span class="token string">'npm'</span>]</span></code></pre> +<p>Ok, let's stop for a second... there's a lot to unpack here!</p> +<p>The first thing that we can see is that a <code>Dockerfile</code> is a file with a specific syntax. It is somewhat similar to a Bash script, but there's also something different about it...</p> +<p>The order of lines is important and every line starts with a keyword that defines the type of instruction: in our example, <code>FROM</code>, <code>RUN</code> and <code>CMD</code>.</p> +<ul> +<li><code>FROM</code> is generally the first line of any <code>Dockerfile</code> and it is used to define which <em>base image</em> do we want to extend. A base image provides a sort of a starting point that can make our life easier. For instance, here by specifying <code>debian:buster</code>, we are inheriting all the binaries and libraries that can be found in the <em>Buster</em> version of <em>Debian</em>. This includes the package manager <code>apt</code> that we can use to install additional software.</li> +<li><code>RUN</code> is used to run some scripting. This is generally done to install additional software, create configuration files, generate keys, download remote resources, etc. You can see <code>RUN</code> instructions as provisioning steps, in other words, instructions you need to run to configure your image. It is quite common to have multiple <code>RUN</code> instructions in a <code>Dockerfile</code>.</li> +<li><code>CMD</code> generally appears only once per <code>Dockerfile</code> and it indicates which command should be executed when the container runs. In our case, we are using it to specify that we want <code>npm</code> to be executed.</li> +</ul> +<p>There are other commands like <code>ENV</code> (define environment variables) and <code>COPY</code> (copy a file from your system into the image), but providing a comprehensive guide on how to write a <code>Dockerfile</code> goes beyond the scope of this article. If you want to find out more, check out the <a href="https://docs.docker.com/engine/reference/builder/">official <code>Dockerfile</code> reference</a>.</p> +<p>Ok, now that we have defined our <code>Dockerfile</code>, how do we build a container image?</p> +<p>That's easy, we just need to execute <code>docker build .</code> (where <code>.</code> means &quot;look for a <code>Dockerfile</code> in the current folder). We can also give a name to our image by using tags, so the final command we want to execute is actually going to look like this:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token parameter variable">-t</span> demo-docker/npmdemo <span class="token builtin class-name">.</span></code></pre> +<p>With this command we are &quot;tagging&quot; our image with the name <code>demo-docker/npmdemo</code>. We can use this name later to run our container.</p> +<p>So ok, let's run the container then!</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run demo-docker/npmdemo</code></pre> +<p>If everything went as expected we should see the following output:</p> +<pre class="language-text"><code class="language-text">Usage: npm <command /> + +where <command /> is one of: + access, adduser, audit, bin, bugs, c, cache, ci, cit, + clean-install, clean-install-test, completion, config, + create, ddp, dedupe, deprecate, dist-tag, docs, doctor, + edit, explore, fund, get, help, help-search, hook, i, init, + install, install-ci-test, install-test, it, link, list, ln, + login, logout, ls, org, outdated, owner, pack, ping, prefix, + profile, prune, publish, rb, rebuild, repo, restart, root, + run, run-script, s, se, search, set, shrinkwrap, star, + stars, start, stop, t, team, test, token, tst, un, + uninstall, unpublish, unstar, up, update, v, version, view, + whoami + +npm <command /> -h quick help on <command /> +npm -l display full usage info +npm help <term> search for help on <term> +npm help npm involved overview + +...</term></term></code></pre> +<p>After showing this output, the container is stopped (simply because <code>npm</code> exits after showing the help message) and we are back to our prompt.</p> +<p>Remember that we specified in our <code>CMD</code> instruction that we just wanted to run <code>npm</code> at container start? This is why we are seeing the <code>npm</code> help message when we start the container. What if we want to run the container again but this time we want to run a different command? Can we override the default command? We can easily do that! For instance, we can run the following:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run demo-docker/npmdemo <span class="token function">npm</span> <span class="token parameter variable">--version</span></code></pre> +<p>As you might expect, this is going to print something like this:</p> +<pre class="language-text"><code class="language-text">6.14.11</code></pre> +<p>I know this is not super useful just yet! But hold on for few other minutes so we can discuss why you might want to use Docker in your team. After that, we will actually go building a Docker image for a Node.js HTTP server! I promise that will be interesting :)</p> +<h2 id="why-docker-and-docker-compose-can-help-you-and-your-team" tabindex="-1" class="title is-2">Why Docker and Docker Compose can help you and your team</h2> +<p>Any active project will be constantly updated with new features, bug fixes or security patches. This is true for our project as well for the dependencies of our project. Of course, it is desirable to keep the dependencies of our project as much up to date as possible. But, inevitably, different projects might end up using different versions of the same dependency.</p> +<p>Keep in mind that, when we talk about dependencies, we don't mean only libraries but also other systems the project relies on, like a database. Let's take for example MySQL. In 2021 it is common to see project using <strong>MySQL 5.6, MySQL 5.7</strong> or <strong>MySQL 8</strong>.</p> +<p>Now, if we are working on multiple projects that require different versions of MySQL, how can we manage different versions of MySQL in our development machines? Is it always possible to switch from one version to another without messing up our configuration or corrupting the data?</p> +<p>With Docker you can avoid to spend hours to find a solution to this problem and run your entire stack on containers. Different projects can run locally by spinning up a different MySQL container and since every container is isolated, you won't have any issue when switching from one project to another: you won't accidentally corrupt MySQL configuration or its data.</p> +<p>So, the idea is to use Docker to manage every single &quot;process&quot; that is needed to run your application. Every process will run in its own container. For instance, in a regular web application, you might have a container for the web server process and a container for the database.</p> +<p>To manage and integrate many containers together, Docker offers a utility called Docker Compose. Docker Compose looks for a file called <code>docker-compose.yml</code> where you can define all the necessary configuration and containers you want to run for your project.</p> +<p>If you work in a team, you don't have to agree in advance on every single configuration detail with your teammates or make sure that everyone has exactly the same setup. In fact, with Docker compose, you can just share the <code>docker-compose.yml</code> configuration as part of your project and Docker will take care to spin up the same containers in every machine.</p> +<p>At any point in time, if someone on the team decides to update a dependency or introduce a new dependency, they will do so by updating the <code>docker-compose.yml</code> and everyone else will easily be able to get the latest changes.</p> +<p>Even if you have a new joiner in the team, they don't have to understand and install all the necessary dependencies one by one, they can simply install Docker rely on the latest version of <code>docker-compose.yml</code> to run the project on their development machine.</p> +<p>This is one of the main advantages that you can get by using Docker and Docker Compose for development.</p> +<p>Let's now see an example of a <code>docker-compose.yml</code>:</p> +<pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">'3'</span> + +<span class="token comment">################################################################################</span> +<span class="token comment"># SERVICES</span> +<span class="token comment">################################################################################</span> +<span class="token key atrule">services</span><span class="token punctuation">:</span> + + <span class="token comment"># ------------------------------------------------------------</span> + <span class="token comment"># MySQL Database</span> + <span class="token comment"># ------------------------------------------------------------</span> + <span class="token key atrule">mysqlserver</span><span class="token punctuation">:</span> + <span class="token key atrule">container_name</span><span class="token punctuation">:</span> myproject_mysql + <span class="token key atrule">image</span><span class="token punctuation">:</span> mysql<span class="token punctuation">:</span><span class="token number">5.7</span> + + <span class="token key atrule">environment</span><span class="token punctuation">:</span> + <span class="token key atrule">MYSQL_ROOT_PASSWORD</span><span class="token punctuation">:</span> <span class="token string">"docker"</span> + <span class="token key atrule">MYSQL_USER</span><span class="token punctuation">:</span> <span class="token string">"local"</span> + <span class="token key atrule">MYSQL_PASSWORD</span><span class="token punctuation">:</span> <span class="token string">"local"</span> + + <span class="token key atrule">volumes</span><span class="token punctuation">:</span> + <span class="token comment"># ---- Format: ----</span> + <span class="token comment"># HOST-DIRECTORY : DOCKER-DIRECTORY</span> + <span class="token punctuation">-</span> $<span class="token punctuation">{</span>MYSQL_BACKUP_FOLDER<span class="token punctuation">}</span><span class="token punctuation">:</span>/backup/ + + <span class="token key atrule">networks</span><span class="token punctuation">:</span> + <span class="token key atrule">app_net</span><span class="token punctuation">:</span> + <span class="token key atrule">ipv4_address</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>IP_MYSQL_SERVER<span class="token punctuation">}</span> + +<span class="token comment">################################################################################</span> +<span class="token comment"># NETWORK</span> +<span class="token comment">################################################################################</span> +<span class="token key atrule">networks</span><span class="token punctuation">:</span> + <span class="token key atrule">app_net</span><span class="token punctuation">:</span> + <span class="token key atrule">driver</span><span class="token punctuation">:</span> bridge + <span class="token key atrule">ipam</span><span class="token punctuation">:</span> + <span class="token key atrule">driver</span><span class="token punctuation">:</span> default + <span class="token key atrule">config</span><span class="token punctuation">:</span> + <span class="token punctuation">-</span> <span class="token key atrule">subnet</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>IP_SUBNET<span class="token punctuation">}</span></code></pre> +<p>What is inside this this file?</p> +<ul> +<li><code>services</code> : this is the list of the containers (and their settings) that we want to run in our project</li> +<li><code>mysqlserver</code> : the name of the service. This name is used when you use <code>docker-compose</code> command and you want to refer to this specific service (container).</li> +<li><code>container_name</code> : overrides the default alias of the container. It's a good practice to use something like <code>&lt;name of the project&gt;_&lt;name of the service&gt;</code> to avoid conflict with other projects.</li> +<li><code>image</code> : the name of the Docker image that we want to use (as defined in the Docker registry).</li> +<li><code>environment</code> : environment variables used inside the container.</li> +<li><code>volumes</code> : shared folder between your computer and the container. You can crate a mapping of your destination folder even if doesn't exist. In our example we map our backup folder with <code>/backup</code> folder inside the container. Very useful if we want to crate a standard script for restoring the data into the database (e.g. standard seed data shared across devs).</li> +<li><code>networks</code> : add specific ipv4 IP to our container. Setting up a specific IP for your container (service) is very useful when you have to work with different projects or you want to create local DNS aliases for your project.</li> +<li><code>networks → app_net → driver: bridge</code> this is a default configuration to allow the container to connect to the internet through the host system. The name <code>app_net</code> is a the name we want to give to the Docker network, so you can name it wathever makes sense to you. Refer to the <a href="https://docs.docker.com/compose/networking/#specify-custom-networks">official documentation on custom networks</a> for more information.</li> +</ul> +<p>Now let's see our <code>.env</code> file that we are going to use to store the enviornment variables needed for this project:</p> +<pre class="language-bash"><code class="language-bash"><span class="token comment">################################################################################</span> +<span class="token comment">### MySQL Settings</span> +<span class="token comment">################################################################################</span> +<span class="token assign-left variable">MYSQL_BACKUP_FOLDER</span><span class="token operator">=</span>/home/user/backup_db/project_x/ + +<span class="token comment">################################################################################</span> +<span class="token comment">### IP Settings</span> +<span class="token comment">################################################################################</span> +<span class="token assign-left variable">IP_SUBNET</span><span class="token operator">=</span><span class="token number">172.16</span>.250.0/24 +<span class="token assign-left variable">IP_LOCAL_COMPUTER</span><span class="token operator">=</span><span class="token number">172.16</span>.250.1 +<span class="token assign-left variable">IP_MYSQL_SERVER</span><span class="token operator">=</span><span class="token number">172.16</span>.250.11</code></pre> +<p>It is not always necessary to use an <code>.env</code> file, especially in simple examples like this one. It is a good practice to use one, though. The main advantage is when you have to share the <code>docker-compose.yml</code> and the <code>.env</code> with the team.</p> +<p>Generally the <code>.env</code> file is not committed to the repository. The idea is to use it to provide &quot;user specific&quot; configuration, so every member of the team can choose your own range of IPs or reference their own work folders.</p> +<p>Let's see an example. If we add in <code>docker-compose</code> a service for Node.js we can add some &quot;link&quot; to other services. +(official docker documentation: <a href="https://docs.docker.com/compose/compose-file/compose-file-v3/#extra_hosts">https://docs.docker.com/compose/compose-file/compose-file-v3/#extra_hosts</a>)</p> +<pre class="language-yaml"><code class="language-yaml"><span class="token comment"># ...</span> + + <span class="token comment"># ------------------------------------------------------------</span> + <span class="token comment"># Node.js Server</span> + <span class="token comment"># ------------------------------------------------------------</span> + <span class="token key atrule">nodejs</span><span class="token punctuation">:</span> + <span class="token comment"># ...</span> + + <span class="token key atrule">extra_hosts</span><span class="token punctuation">:</span> + <span class="token punctuation">-</span> mysql<span class="token punctuation">-</span>server<span class="token punctuation">-</span>service<span class="token punctuation">:</span>$<span class="token punctuation">{</span>IP_MYSQL_SERVER<span class="token punctuation">}</span> + +<span class="token comment"># ...</span></code></pre> +<p>In <code>extra_hosts</code> we can add all entries that are stored in our <code>/etc/hosts</code> file so we can create &quot;custom domain&quot; via local configuration without have a real domain.</p> +<p>In this specific case in our Node.js app we can refer to mysql server not via IP but via &quot;mysql-server-service&quot; name.</p> +<p>Let's go back to our docker-compose file. At this point we can run <code>docker-compose</code> to start all the docker services specified in our project:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">docker-compose</span> up <span class="token parameter variable">-d</span></code></pre> +<p>Note that the <code>-d</code> option will start all the services in background.</p> +<p>After a few seconds, all our services should be running. So let's see, for example, how we can access the MySQL shell and run a query:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">docker-compose</span> <span class="token builtin class-name">exec</span> mysqlserver /bin/bash +root@2f88599b3b1f:/<span class="token comment"># mysql -u root -pdocker (...enter root password)</span> +mysql<span class="token operator">></span> SHOW DATABASES<span class="token punctuation">;</span></code></pre> +<p>First of all: we need to &quot;open&quot; a bash shell inside the container so we can run any command. We can do that with: <code>docker-compose exec mysqlserver /bin/bash</code>.</p> +<p>These are the arguments of this <code>docker-compose</code> command:</p> +<ul> +<li><code>exec</code> : Docker searches for a running container (with the name <code>mysqlserver</code>) to run a given command. If you don't run <code>docker-compose up -d</code> before, this command will fail.</li> +<li><code>/bin/bash</code> : is the name of the command we want to run inside the container.</li> +</ul> +<p>Once we are &quot;inside&quot; the container we can do anything we want. In this case we want open a mysql terminal, so we run <code>mysql -u root -pdocker</code>.</p> +<p>Note that the root password is defined by environment variable <code>MYSQL_ROOT_PASSWORD</code>.</p> +<h2 id="create-and-configure-a-node.js-project-with-docker" tabindex="-1" class="title is-2">Create and configure a Node.js project with Docker</h2> +<p>Based on the complexity of the project we might decide to use Docker directly one or more containers or to rely on Docker Compose to orchestrate multiple containers.</p> +<p>Let's take a look at this simple &quot;Hello World&quot; web server (file <code>app.js</code>).</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> http <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'http'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + +<span class="token keyword">const</span> hostname <span class="token operator">=</span> <span class="token string">'0.0.0.0'</span><span class="token punctuation">;</span> +<span class="token keyword">const</span> port <span class="token operator">=</span> <span class="token number">4040</span><span class="token punctuation">;</span> + +<span class="token keyword">const</span> server <span class="token operator">=</span> http<span class="token punctuation">.</span><span class="token function">createServer</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> + res<span class="token punctuation">.</span>statusCode <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">;</span> + res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Content-Type'</span><span class="token punctuation">,</span> <span class="token string">'text/plain'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">'Hello World'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> +<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + +server<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span>port<span class="token punctuation">,</span> hostname<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Server running at http://</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>hostname<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>port<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> +<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> +<p>Nothing particularly complicated here, we are simply creating a web server that responds to every request with an &quot;Hello World!&quot;.</p> +<p>But pay special attention to <code>hostname</code>! With docker you can have connectivity problems if you use <code>&quot;localhost&quot;</code> or <code>&quot;127.0.0.1&quot;</code>, because it refers to the docker container and not to your local machine, therefore the container will only listen for connections from inside the container itself. It's better to use use <code>&quot;0.0.0.0&quot;</code> or directly the ip associated to the container so that we can issue requests from our local machine or from other containers.</p> +<h3 id="work-with-docker-directly-(no-configuration-file)" tabindex="-1" class="title is-3">Work with Docker directly (no configuration file)</h3> +<p>To start this application with Docker, we don't necessarily need to create a <code>Dockerfile</code> or a <code>docker-compose.yml</code>. We can simply run (in the same folder of <code>app.js</code>) the following command:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run <span class="token punctuation">\</span> + <span class="token parameter variable">--rm</span> <span class="token punctuation">\</span> + <span class="token parameter variable">-p</span> <span class="token string">"4040:4040"</span> <span class="token punctuation">\</span> + <span class="token parameter variable">-v</span> <span class="token variable">${<span class="token environment constant">PWD</span>}</span>:/home/node/project <span class="token punctuation">\</span> + node:14 <span class="token punctuation">\</span> + <span class="token function">node</span> /home/node/project/app.js</code></pre> +<p>At this point we should be able to connect to our web server using a browser by simply visiting <code>http://127.0.01:4040</code>.</p> +<span style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 600px; "> +<picture> + <source type="image/png" srcset="https://www.nodejsdesignpatterns.com/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-64.png 64w, https://www.nodejsdesignpatterns.com/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-128.png 128w, https://www.nodejsdesignpatterns.com/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-256.png 256w, https://www.nodejsdesignpatterns.com/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-512.png 512w, https://www.nodejsdesignpatterns.com/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-564.png 564w" sizes="(max-width: 600px) 100vw, 600px" /> +<source type="image/webp" srcset="https://www.nodejsdesignpatterns.com/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-64.webp 64w, https://www.nodejsdesignpatterns.com/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-128.webp 128w, https://www.nodejsdesignpatterns.com/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-256.webp 256w, https://www.nodejsdesignpatterns.com/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-512.webp 512w, https://www.nodejsdesignpatterns.com/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-564.webp 564w" sizes="(max-width: 600px) 100vw, 600px" /> +<img loading="lazy" decoding="async" style="max-width: 100%; width: 100%; margin: 0px; vertical-align: middle;" alt="A simple Node.js web server seen from the browser" src="https://www.nodejsdesignpatterns.com/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-64.png" width="64" height="18" /> +</picture> +</span> +<p>Let's discuss in more detail what's happening when we run the command above:</p> +<ul> +<li><code>--rm</code> means &quot;destroy the container once it is stopped&quot;.</li> +<li><code>-p</code> maps a port of your pc to a port in the container (in this case we are saying map the local port <code>4040</code> to the port <code>4040</code> inside the container).</li> +<li><code>-v</code> stands for &quot;volume&quot; and it is used to map the folder (<code>$PWD</code>) in the development machine to the <code>/home/node/project</code> folder inside the container.</li> +<li><code>node:14</code> is the name and version of the docker image we want to use (this comes from the <a href="https://hub.docker.com/">official Docker Registry</a>)</li> +<li><code>node /home/node/project/app.js</code> is the command we want to run once the container starts. We are simply executing <code>node</code> and running our app file.</li> +</ul> +<p>Note that <code>/home/node/project/app.js</code> refers to the mapping of the local folder to volume in the container. Remember that Docker runs isolated processes, so by default they don't have access to our filesystem and we can only share files by explicitly defining and attaching volumes to running containers.</p> +<h3 id="work-with-docker-compose.yml-and-the-docker-compose-command" tabindex="-1" class="title is-3">Work with docker-compose.yml and the docker-compose command</h3> +<p>Let's now look at doing the same thing but with a different approach. By using an <code>.env</code> file, a <code>docker-compose.yml</code> file and <code>docker-compose</code>, we can define all the necessary settings there so we can keep our command line as lean as possible.</p> +<p>This is how we can define our <code>docker-compose.yml</code>:</p> +<pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">'3'</span> +<span class="token key atrule">services</span><span class="token punctuation">:</span> + <span class="token key atrule">nodejs</span><span class="token punctuation">:</span> + <span class="token key atrule">container_name</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>COMPOSE_PROJECT_NAME<span class="token punctuation">}</span>_$<span class="token punctuation">{</span>NODEJS_SERVER_NAME<span class="token punctuation">}</span> + <span class="token key atrule">image</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>NODEJS_CONTAINER_IMAGE<span class="token punctuation">}</span> + <span class="token key atrule">user</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>NODEJS_USER<span class="token punctuation">}</span> + <span class="token key atrule">command</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>NODEJS_COMMAND<span class="token punctuation">}</span> + <span class="token key atrule">ports</span><span class="token punctuation">:</span> + <span class="token punctuation">-</span> <span class="token string">"4040:4040"</span> + <span class="token key atrule">environment</span><span class="token punctuation">:</span> + <span class="token key atrule">NODE_ENV</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>NODEJS_ENV<span class="token punctuation">}</span> + <span class="token key atrule">volumes</span><span class="token punctuation">:</span> + <span class="token punctuation">-</span> $<span class="token punctuation">{</span>PWD<span class="token punctuation">}</span><span class="token punctuation">:</span>/home/node/project + <span class="token key atrule">working_dir</span><span class="token punctuation">:</span> /home/node/project + <span class="token key atrule">networks</span><span class="token punctuation">:</span> + <span class="token key atrule">app_net</span><span class="token punctuation">:</span> + <span class="token key atrule">ipv4_address</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>IP_NODE_SERVER<span class="token punctuation">}</span> +<span class="token key atrule">networks</span><span class="token punctuation">:</span> + <span class="token key atrule">app_net</span><span class="token punctuation">:</span> + <span class="token key atrule">driver</span><span class="token punctuation">:</span> bridge + <span class="token key atrule">ipam</span><span class="token punctuation">:</span> + <span class="token key atrule">driver</span><span class="token punctuation">:</span> default + <span class="token key atrule">config</span><span class="token punctuation">:</span> + <span class="token punctuation">-</span> <span class="token key atrule">subnet</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>IP_SUBNET<span class="token punctuation">}</span></code></pre> +<p>And this is how we can define our <code>.env</code> file:</p> +<pre class="language-bash"><code class="language-bash"><span class="token assign-left variable">COMPOSE_PROJECT_NAME</span><span class="token operator">=</span>nodetest +<span class="token assign-left variable">NODEJS_CONTAINER_IMAGE</span><span class="token operator">=</span>node:14 +<span class="token assign-left variable">NODEJS_SERVER_NAME</span><span class="token operator">=</span>nodejs +<span class="token assign-left variable">NODEJS_USER</span><span class="token operator">=</span>node +<span class="token assign-left variable">NODEJS_ENV</span><span class="token operator">=</span>development +<span class="token assign-left variable">NODEJS_COMMAND</span><span class="token operator">=</span>node app.js +<span class="token assign-left variable">IP_SUBNET</span><span class="token operator">=</span><span class="token number">172.16</span>.250.0/24 +<span class="token assign-left variable">IP_LOCAL_COMPUTER</span><span class="token operator">=</span><span class="token number">172.16</span>.250.1 +<span class="token assign-left variable">IP_NODE_SERVER</span><span class="token operator">=</span><span class="token number">172.16</span>.250.10</code></pre> +<p>At this point we just need to run <code>docker-compose up</code> and we should have the same result as the command described before.</p> +<p>If you want to run in background mode (so you can close the terminal) just add <code>-d</code> to the command like <code>docker-compose up -d</code>.</p> +<p>If you want to change something like node version, just edit the <code>.env</code> file in the same folder and done!</p> +<p>Once you are finished working on the project and you want to stop the containers, you can simply run:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">docker-compose</span> down</code></pre> +<h2 id="pros-and-cons-of-using-docker-with-node.js" tabindex="-1" class="title is-2">Pros and cons of using Docker with Node.js</h2> +<p>It might seem that Docker is like magic for your project but, as with many things, &quot;all that glitters is not gold&quot;. Of course, Docker is great, but it can have its fair share of unexpected &quot;surprises&quot; that can be tedious to resolve.</p> +<p>Let's conclude this article, by discussing some of the main pros and cons of using Docker for Node.js development.</p> +<h3 id="pros" tabindex="-1" class="title is-3">PROS</h3> +<ul> +<li>You can manage multiple version of Node.js without having to install all of them or needing complicated setup to be able to switch version on demand.</li> +<li>You can use different version of Node.js at the same time. Imagine you have a microservice-oriented architecture and 2 different services need to use 2 different versions of Node.js!</li> +<li>It is easier to share a consistent setup with the members of your team. Docker becomes the only shared dependency that needs to be pre-installed.</li> +</ul> +<h3 id="cons" tabindex="-1" class="title is-3">CONS</h3> +<ul> +<li>For small projects running only one monolithic service setting up Docker and Docker Compose can be a bit of over-engineering.</li> +<li>With more advanced projects, you might need to setup some bash scripts to run some Docker command (because they might be long and difficult to remember).</li> +<li>For Mac OS and Windows you can have some setup problems and degraded performances because Docker is not running natively in these platforms.</li> +</ul> +<p>That's all! Hopefully you found this article interesting and you will decide to give Docker a shot for your Node.js development.</p> +<p>You can check out the code examples in the following repository: <a href="https://github.com/giuseppemorelli/docker-node-example">https://github.com/giuseppemorelli/docker-node-example</a>. If you liked this article, please consider giving it a star, everyone needs some vanity metrics, after all! :)</p> +<h2 id="about-the-author" tabindex="-1" class="title is-2">About the author</h2> +<p>Giuseppe Morelli is the guest author of this post. This is Giuseppe's bio.</p> +<p>Since I was a baby I came to love technology and programming. Today I am a remote worker for Italian and European companies. I like to work with Agile and Time-Material practices. +I have been a PHP Developer by trade since 2006, and I am particularly experienced with e-commerce development. Every day I try to learn something new by being a part of the open-source community. In 2020 I've started to study Node.js by reading Mario and Luciano's book :)</p> +<p>If you want to connect with me, check out <a href="https://giuseppemorelli.net/">my personal website</a> or my <a href="https://twitter.com/giuseppemorelli">Twitter account</a>.</p> + + + + + JavaScript async iterators + + 2021-05-04T13:10:00Z + https://www.nodejsdesignpatterns.com/blog/javascript-async-iterators/ + <p>Did you know that JavaScript offers a few protocols to allow iteration over certain objects? Of course, we know we can easily iterate over arrays, but with these protocols, you can make your own custom objects iterable as well.</p> +<p>When you have an iterable object representing a collection, you can use the <code>for...of</code> syntax to iterate over every single item of the collection.</p> +<p>But what if an object abstracts data generated asynchronously? For instance, think of an abstraction that allows us to fetch data from a paginated API, or think about some records consumed in batches from a database, or something as simple as a countdown timer. Well in these cases you can use the <code>for await...of</code> syntax!</p> +<p>In this article, we will learn more about the <em>iterator</em> and the <em>iterable</em> protocol (and their async counterparts) and we will see how to create custom objects that can expose their internal data in an ergonomic and idiomatic way.</p> +<h2 id="javascript-iteration-with-for...of" tabindex="-1" class="title is-2">JavaScript iteration with <code>for...of</code></h2> +<p>With ECMAScript 2015, JavaScript got the <code>for...of</code> syntax. This syntax provides a very easy way to iterate over collections, such as arrays, string, sets, and maps.</p> +<p>If you have never seen this syntax in action here are some examples:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> judokas <span class="token operator">=</span> <span class="token punctuation">[</span> + <span class="token string">'Driulis Gonzalez Morales'</span><span class="token punctuation">,</span> + <span class="token string">'Ilias Iliadis'</span><span class="token punctuation">,</span> + <span class="token string">'Tadahiro Nomura'</span><span class="token punctuation">,</span> + <span class="token string">'Anton Geesink'</span><span class="token punctuation">,</span> + <span class="token string">'Teddy Riner'</span><span class="token punctuation">,</span> + <span class="token string">'Ryoko Tani'</span> +<span class="token punctuation">]</span> + +<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> judoka <span class="token keyword">of</span> judokas<span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>judoka<span class="token punctuation">)</span> +<span class="token punctuation">}</span></code></pre> +<p>In the example above, we are iterating over an array using the <code>for...of</code> syntax. If we run this code, this is what we will get as output:</p> +<pre class="language-text"><code class="language-text">Driulis Gonzalez Morales +Ilias Iliadis +Tadahiro Nomura +Anton Geesink +Teddy Riner +Ryoko Tani</code></pre> +<p>The same syntax works also for iterating over the characters of a string:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> judoka <span class="token operator">=</span> <span class="token string">'Ryoko Tani'</span> + +<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> char <span class="token keyword">of</span> judoka<span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>char<span class="token punctuation">)</span> +<span class="token punctuation">}</span></code></pre> +<p>The above will print:</p> +<pre class="language-text"><code class="language-text">R +y +o +k +o + +T +a +n +i</code></pre> +<p>And we can even use this for <code>Set</code> and <code>Map</code>:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> medals <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">'gold'</span><span class="token punctuation">,</span> <span class="token string">'silver'</span><span class="token punctuation">,</span> <span class="token string">'bronze'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> + +<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> medal <span class="token keyword">of</span> medals<span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>medal<span class="token punctuation">)</span> +<span class="token punctuation">}</span></code></pre> +<p>Which is going to output:</p> +<pre class="language-text"><code class="language-text">gold +silver +bronze</code></pre> +<p><code>Map</code> is especially interesting because we can use <em>destructuring</em> to iterate over key-value pairs:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> medallists <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">[</span> + <span class="token punctuation">[</span><span class="token string">'Teddy Riner'</span><span class="token punctuation">,</span> <span class="token number">33</span><span class="token punctuation">]</span><span class="token punctuation">,</span> + <span class="token punctuation">[</span><span class="token string">'Driulis Gonzalez Morales'</span><span class="token punctuation">,</span> <span class="token number">16</span><span class="token punctuation">]</span><span class="token punctuation">,</span> + <span class="token punctuation">[</span><span class="token string">'Ryoko Tani'</span><span class="token punctuation">,</span> <span class="token number">16</span><span class="token punctuation">]</span><span class="token punctuation">,</span> + <span class="token punctuation">[</span><span class="token string">'Ilias Iliadis'</span><span class="token punctuation">,</span> <span class="token number">15</span><span class="token punctuation">]</span> +<span class="token punctuation">]</span><span class="token punctuation">)</span> + +<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token punctuation">[</span>judoka<span class="token punctuation">,</span> medals<span class="token punctuation">]</span> <span class="token keyword">of</span> medallists<span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>judoka<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> has won </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>medals<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> medals</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> +<span class="token punctuation">}</span></code></pre> +<p>The above example will output:</p> +<pre class="language-text"><code class="language-text">Teddy Riner has won 33 medals +Driulis Gonzalez Morales has won 16 medals +Ryoko Tani has won 16 medals +Ilias Iliadis has won 15 medals</code></pre> +<p>Finally, if you want to iterate over the key-value pairs of an object literal using the <code>for...of</code> syntax, we can do that by using the helper <code>Object.entries</code>:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> medallists <span class="token operator">=</span> <span class="token punctuation">{</span> + <span class="token string-property property">'Teddy Riner'</span><span class="token operator">:</span> <span class="token number">33</span><span class="token punctuation">,</span> + <span class="token string-property property">'Driulis Gonzalez Morales'</span><span class="token operator">:</span> <span class="token number">16</span><span class="token punctuation">,</span> + <span class="token string-property property">'Ryoko Tani'</span><span class="token operator">:</span> <span class="token number">16</span><span class="token punctuation">,</span> + <span class="token string-property property">'Ilias Iliadis'</span><span class="token operator">:</span> <span class="token number">15</span> +<span class="token punctuation">}</span> + +<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token punctuation">[</span>judoka<span class="token punctuation">,</span> medals<span class="token punctuation">]</span> <span class="token keyword">of</span> Object<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span>medallists<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>judoka<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> has won </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>medals<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> medals</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> +<span class="token punctuation">}</span></code></pre> +<p>The code snippet above will produce the same output as the previous example.</p> +<p>What's interesting here is that, if we try to use the <code>for...of</code> syntax directly on the object <code>medallists</code> (without <code>Object.entries</code>), we get the following error:</p> +<pre class="language-text"><code class="language-text">for (const [judoka, medals] of medallists) { + ^ + +TypeError: medallists is not iterable + at Object.<anonymous> (.../05-for-of-object.js:8:32) + at Module._compile (node:internal/modules/cjs/loader:1108:14) + at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10) + at Module.load (node:internal/modules/cjs/loader:988:32) + at Function.Module._load (node:internal/modules/cjs/loader:828:14) + at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12) + at node:internal/main/run_main_module:17:47</anonymous></code></pre> +<p>Let's read this error once again: <code>medallists is not iterable</code>!</p> +<p>Yeah, the error is clear: a regular JavaScript object is not <em>iterable</em>, while arrays, strings, maps, and sets are!</p> +<p>But what does it mean for an object to be <em>iterable</em>?</p> +<p>During the rest of this article, we will learn how JavaScript knows if a given object is <strong>iterable</strong> and how we can make our own custom <em>iterable</em> objects.</p> +<p>But first let's quickly take a look at how we can use async iterators.</p> +<h2 id="javascript-iteration-with-for-await...of" tabindex="-1" class="title is-2">JavaScript iteration with <code>for await...of</code></h2> +<p>ECMAScript 2018 introduced a new syntax called <code>for await...of</code>. This syntax is somewhat similar to <code>for...of</code> but it allows us to iterate over <em>asynchronous collections</em> where data becomes available over time in an asynchronous fashion.</p> +<p>A good use case for this syntax is reading data from a remote source like a database.</p> +<p>Here's an example that uses AWS DynamoDB and the <code>for await...of</code> syntax to list all the tables available in our account:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> DynamoDBClient<span class="token punctuation">,</span> paginateListTables <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@aws-sdk/client-dynamodb'</span> + +<span class="token keyword">const</span> client <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DynamoDBClient</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + +<span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> page <span class="token keyword">of</span> <span class="token function">paginateListTables</span><span class="token punctuation">(</span><span class="token punctuation">{</span> client <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token comment">// page.TableNames is an array of table names</span> + <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> tableName <span class="token keyword">of</span> page<span class="token punctuation">.</span>TableNames<span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>tableName<span class="token punctuation">)</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>In the example above, <code>paginateListTables</code> will <em>produce</em> pages over time, and every page will contain a portion of the data (information about all the available tables).</p> +<p>This approach allows us to list hundreds or even thousands of table names efficiently. In fact, the data can be printed as soon as it is available and we don't have to wait for the entire data set to be received.</p> +<p>Note how we are combining here <code>for await...of</code> and <code>for...of</code>. Pages become available over time asynchronously, so we need to use <code>for await...of</code> to iterate over this data. Every page contains an array of table names, so in this case, to iterate over every single table name we can simply use <code>for...of</code>.</p> +<p>In general, we can use the <code>for await...of</code> syntax with objects that are <strong>async iterable</strong>.</p> +<p>In the next few sections, we will see how JavaScript classifies a given object as <em>async iterable</em> and how we can build our custom <em>async iterable</em> objects.</p> +<h2 id="the-javascript-iterator-protocol" tabindex="-1" class="title is-2">The JavaScript iterator protocol</h2> +<p>JavaScript defines a number of protocols that are used to make objects iterable (or async iterable).</p> +<p>The first one we are going to start with is the <strong>iterator protocol</strong>.</p> +<blockquote> +<p>In JavaScript, an object is <strong>an iterator</strong> if it has a <code>next()</code> method. Every time you call it, it returns an object with the keys <code>done</code> (boolean) and <code>value</code>.</p> +</blockquote> +<p>Let's see an example. Let's say we want to build a countdown. This countdown is initialized with a positive integer and it will produce all the numbers from that value down to <code>0</code>:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">createCountdown</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token keyword">from</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">let</span> nextVal <span class="token operator">=</span> from + <span class="token keyword">return</span> <span class="token punctuation">{</span> + <span class="token function">next</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span>nextVal <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">done</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> + <span class="token punctuation">}</span> + + <span class="token keyword">return</span> <span class="token punctuation">{</span> + <span class="token literal-property property">done</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> + <span class="token literal-property property">value</span><span class="token operator">:</span> nextVal<span class="token operator">--</span> + <span class="token punctuation">}</span> + <span class="token punctuation">}</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>In this example, <code>createCountdown</code> is a simple factory function. From this function, we return an <em>iterator</em> object. In fact, the object implements the <em>iterator protocol</em> defined above. Note that the returned object implements a <code>next()</code> method and that this method returns either <code>{done: true}</code> or <code>{done: false, value: someNumber}</code>.</p> +<p>Let's see now how can we use this object to extract all the values:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> countdown <span class="token operator">=</span> <span class="token function">createCountdown</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { done: false, value: 3 }</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { done: false, value: 2 }</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { done: false, value: 1 }</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { done: false, value: 0 }</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { done: true }</span></code></pre> +<p>Or if we want to use this object with a more generic loop:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> countdown <span class="token operator">=</span> <span class="token function">createCountdown</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> +<span class="token keyword">let</span> result <span class="token operator">=</span> countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span> +<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span>result<span class="token punctuation">.</span>done<span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>value<span class="token punctuation">)</span> + result <span class="token operator">=</span> countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span> +<span class="token punctuation">}</span></code></pre> +<p>The code above will produce the following output:</p> +<pre class="language-text"><code class="language-text">3 +2 +1 +0</code></pre> +<p>This is not the most intuitive or ergonomic approach, but the iterator protocol is the basic building block for the <em>iterable protocol</em> which enables the <code>for...of</code> syntax.</p> +<h2 id="the-javascript-iterable-protocol" tabindex="-1" class="title is-2">The JavaScript iterable protocol</h2> +<p>As we said, the <em>iterable protocol</em> builds on top of the <em>iterator protocol</em> that we just explored. Let's see how:</p> +<blockquote> +<p>An object is <strong>iterable</strong> if it implements the <code>@@iterator</code> method, a zero-argument function that <strong>returns an iterator</strong>.</p> +</blockquote> +<p>Note that with <code>@@iterator</code> we indicate a symbol that is accessed with the global value <code>Symbol.iterator</code>.</p> +<p>Can we make our countdown example <em>iterable</em>? We certainly can!</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">createCountdown</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token keyword">from</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">let</span> nextVal <span class="token operator">=</span> from + <span class="token keyword">return</span> <span class="token punctuation">{</span> + <span class="token punctuation">[</span>Symbol<span class="token punctuation">.</span>iterator<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> + <span class="token function">next</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span>nextVal <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">done</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> + <span class="token punctuation">}</span> + + <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">done</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token literal-property property">value</span><span class="token operator">:</span> nextVal<span class="token operator">--</span> <span class="token punctuation">}</span> + <span class="token punctuation">}</span> + <span class="token punctuation">}</span><span class="token punctuation">)</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>In this new example, our factory function returns an <em>iterable</em> object. This object in fact has a method referenced with <code>Symbol.iterator</code> that returns an <em>iterator</em> object.</p> +<p>At this point, once we have an instance of a countdown, we can use the <code>for..of</code> syntax to iterate over all the numbers from the countdown:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> countdown <span class="token operator">=</span> <span class="token function">createCountdown</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> + +<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> value <span class="token keyword">of</span> countdown<span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> +<span class="token punctuation">}</span></code></pre> +<p>The example above will output:</p> +<pre class="language-text"><code class="language-text">3 +2 +1 +0</code></pre> +<p>Hooray! Now we know how to make iterators and iterable objects. If you find the two terms confusing, don't worry, that's quite common!</p> +<p>One good way to try to remember and distinguish these 2 concepts is the following:</p> +<ul> +<li>An <em>iterator</em> is a lower-level object that allows us to iterate over some data set using <code>next()</code></li> +<li>An <em>iterable</em> is an object on which we can iterate over using the <code>for...of</code> syntax.</li> +</ul> +<h3 id="using-javascript-generator-functions" tabindex="-1" class="title is-3">Using JavaScript generator functions</h3> +<p>An interesting detail is that JavaScript generator functions <em>produce</em> iterators.</p> +<p>This allows us to simplify the way we can implement both the <em>iterator</em> and the <em>iterable</em> protocols.</p> +<p>Let's see how can we rewrite our countdown logic using a generator function:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token operator">*</span> <span class="token function">createCountdown</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token keyword">from</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> from<span class="token punctuation">;</span> i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">yield</span> i + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>If we call <code>createCountdown(3)</code> we get an <em>iterator</em>. So this is perfectly compatible with our previous <em>iterator</em> implementation:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> countdown <span class="token operator">=</span> <span class="token function">createCountdown</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { value: 3, done: false }</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { value: 2, done: false }</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { value: 1, done: false }</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { value: 0, done: false }</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { value: undefined, done: true }</span></code></pre> +<p>Similarly, we can use generators to implement the <em>iterable protocol</em>:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">createCountdown</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token keyword">from</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">return</span> <span class="token punctuation">{</span> + <span class="token punctuation">[</span>Symbol<span class="token punctuation">.</span>iterator<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> from<span class="token punctuation">;</span> i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">yield</span> i + <span class="token punctuation">}</span> + <span class="token punctuation">}</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>And this factory will produce iterable objects, exactly as before:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> countdown <span class="token operator">=</span> <span class="token function">createCountdown</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> + +<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> value <span class="token keyword">of</span> countdown<span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> +<span class="token punctuation">}</span></code></pre> +<p>In general, generators can be considered great syntactic sugars to write iterators.</p> +<h3 id="the-spread-syntax-for-iterable-objects" tabindex="-1" class="title is-3">The spread syntax for iterable objects</h3> +<p>Another interesting detail is that all iterable objects can be used with the <em>spread syntax</em>.</p> +<p>The spread syntax looks like <code>...someIterable</code> and it basically allows us to apply every single element from the iterable to a given context.</p> +<p>The most common use cases are found with array literals and function calls.</p> +<p>Let's see a couple of examples:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> countdown <span class="token operator">=</span> <span class="token function">createCountdown</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> +<span class="token keyword">const</span> from5to0 <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token operator">...</span>countdown<span class="token punctuation">]</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>from5to0<span class="token punctuation">)</span> <span class="token comment">// [ 5, 4, 3, 2, 1, 0 ]</span> + +<span class="token keyword">const</span> countdown2 <span class="token operator">=</span> <span class="token function">createCountdown</span><span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">)</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'countdown2 data:'</span><span class="token punctuation">,</span> <span class="token operator">...</span>countdown2<span class="token punctuation">)</span> +<span class="token comment">// countdown2 data: 6 5 4 3 2 1 0</span></code></pre> +<p>This is something we see most often with arrays, but it's important to note that any iterable object can be used with the spread syntax.</p> +<h2 id="the-javascript-async-iterator-protocol" tabindex="-1" class="title is-2">The JavaScript async iterator protocol</h2> +<p>Ok, so far we have explored only synchronous iteration protocols. What about async?</p> +<p>Unsurprisingly, both the iterator protocol and the iterable protocol have their async counterparts!</p> +<p>Let's start with the <strong>async iterator protocol</strong>:</p> +<blockquote> +<p>An object is an <strong>async iterator</strong> if it has a <code>next()</code> method. Every time you call it, it returns <strong>a promise that resolves</strong> to an object with the keys <code>done</code> (boolean) and <code>value</code>.</p> +</blockquote> +<p>Note how this is quite similar to the synchronous version of the iterator protocol. The main difference here is that the <code>next()</code> function won't return an object straight away. Instead, it will return a promise that will eventually resolve to an object.</p> +<p>Let's now revisit our countdown example and let's say we want some time to pass before numbers are <em>produced</em>:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> setTimeout <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'timers/promises'</span> + +<span class="token keyword">function</span> <span class="token function">createAsyncCountdown</span> <span class="token punctuation">(</span><span class="token parameter">from<span class="token punctuation">,</span> delay <span class="token operator">=</span> <span class="token number">1000</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">let</span> nextVal <span class="token operator">=</span> from + <span class="token keyword">return</span> <span class="token punctuation">{</span> + <span class="token keyword">async</span> <span class="token function">next</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">await</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>delay<span class="token punctuation">)</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span>nextVal <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">done</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> + <span class="token punctuation">}</span> + + <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">done</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token literal-property property">value</span><span class="token operator">:</span> nextVal<span class="token operator">--</span> <span class="token punctuation">}</span> + <span class="token punctuation">}</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>Note that this time we are using an <em>async</em> function to implement <code>next()</code>. This will make this method immediately return a promise, that will later resolve when we run one of the <code>return</code> statements from within the <em>async</em> function.</p> +<p>Also, note that here we are using <code>setTimeout</code> from <code>timers/promises</code>, a new core module available from Node.js 16.</p> +<p>Ok, now we are ready to use this iterator:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> countdown <span class="token operator">=</span> <span class="token function">createAsyncCountdown</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">await</span> countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { done: false, value: 3 }</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">await</span> countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { done: false, value: 2 }</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">await</span> countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { done: false, value: 1 }</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">await</span> countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { done: false, value: 0 }</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">await</span> countdown<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { done: true }</span></code></pre> +<p>This works very similarly to its synchronous counterpart with two notable exceptions:</p> +<ul> +<li>We need to use <code>await</code> to wait for the next element to be produced.</li> +<li>Between one element and another about 1 second will pass, so this iteration is much slower.</li> +</ul> +<p style="text-align: center"><img loading="lazy" decoding="async" style="max-width: 100%; margin: 0px; vertical-align: middle;" alt="An example of JavaScript async iterator" src="https://www.nodejsdesignpatterns.com/img/javascript-async-iterator-countdown_fe19a712.gif" /></p> +<p>Of course, here we can use generators as well as a nice syntactic sugar:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> setTimeout <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'timers/promises'</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token operator">*</span> <span class="token function">createAsyncCountdown</span> <span class="token punctuation">(</span><span class="token parameter">from<span class="token punctuation">,</span> delay <span class="token operator">=</span> <span class="token number">1000</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> from<span class="token punctuation">;</span> i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">await</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>delay<span class="token punctuation">)</span> + <span class="token keyword">yield</span> i + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>This code is more concise and probably more readable, at least to those accustomed to async functions and generator functions.</p> +<h2 id="the-javascript-async-iterable-protocol" tabindex="-1" class="title is-2">The JavaScript async iterable protocol</h2> +<p>Let's now discuss the last iteration protocol: the <em>async iterable protocol</em>!</p> +<blockquote> +<p>An object is an <strong>async iterable</strong> if it implements the <code>@@asyncIterator</code> method, a zero-argument function that returns an <strong>async iterator</strong>.</p> +</blockquote> +<p>Note that with <code>@@asyncIterator</code> we indicate a symbol that can be accessed with the global value <code>Symbol.asyncIterator</code>.</p> +<p>Once again, this definition is quite similar to its synchronous counterpart. The main difference is that this type we have to use <code>Symbol.asyncIterator</code> and that it must return an <em>async</em> iterator.</p> +<p>Let's revisit our async countdown example:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> setTimeout <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'timers/promises'</span> + +<span class="token keyword">function</span> <span class="token function">createAsyncCountdown</span> <span class="token punctuation">(</span><span class="token parameter">from<span class="token punctuation">,</span> delay <span class="token operator">=</span> <span class="token number">1000</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">return</span> <span class="token punctuation">{</span> + <span class="token punctuation">[</span>Symbol<span class="token punctuation">.</span>asyncIterator<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> from<span class="token punctuation">;</span> i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">await</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>delay<span class="token punctuation">)</span> + <span class="token keyword">yield</span> i + <span class="token punctuation">}</span> + <span class="token punctuation">}</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>At this point, our <code>createAsyncCountdown</code> returns a valid async iterator, so we can finally use the <code>for await...of</code> syntax:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> countdown <span class="token operator">=</span> <span class="token function">createAsyncCountdown</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> + +<span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> value <span class="token keyword">of</span> countdown<span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> +<span class="token punctuation">}</span></code></pre> +<p>As you might expect, this code will produce <code>3</code>, <code>2</code>, <code>1</code> and <code>0</code> with a delay:</p> +<p style="text-align: center"><img loading="lazy" decoding="async" style="max-width: 100%; margin: 0px; vertical-align: middle;" alt="An example of JavaScript async iterator" src="https://www.nodejsdesignpatterns.com/img/javascript-async-iterable-countdown_6150da48.gif" /></p> +<p>Great!</p> +<p>At this point, we know how the JavaScript iteration protocol work and how to create iterator and iterable objects in a synchronous and asynchronous fashion!</p> +<h2 id="combining-iterator-and-iterable" tabindex="-1" class="title is-2">Combining iterator and iterable</h2> +<p>Can an object be both an iterator and an iterable at the same time?</p> +<p>Yes! Nothing is stopping us from implementing both protocols for a given object. This basically means that <code>@@iterator</code> or <code>@@asyncIterator</code> will have to return the same object as in the following example:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> iterableIterator <span class="token operator">=</span> <span class="token punctuation">{</span> + <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">done</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token string">"hello"</span> <span class="token punctuation">}</span> + <span class="token punctuation">}</span><span class="token punctuation">,</span> + <span class="token punctuation">[</span>Symbol<span class="token punctuation">.</span>iterator<span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">return</span> <span class="token keyword">this</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span> + +<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> value <span class="token keyword">of</span> iterableIterator<span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> <span class="token comment">// "hello"</span> +<span class="token punctuation">}</span></code></pre> +<p>The example above will print &quot;hello&quot; endlessly.</p> +<p>What's even cooler is that generator functions are also iterable. This means that we can greatly simplify our countdown examples.</p> +<p>Let's see how the syncrhonous countdown would look like:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token operator">*</span> <span class="token function">createCountdown</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token keyword">from</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> from<span class="token punctuation">;</span> i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">yield</span> i + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>We don't even need to bother with <code>Symbol.iterator</code>!</p> +<p>The same goes for the asynchronous version of our countdown:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> setTimeout <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'timers/promises'</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token operator">*</span> <span class="token function">createAsyncCountdown</span> <span class="token punctuation">(</span><span class="token parameter">from<span class="token punctuation">,</span> delay <span class="token operator">=</span> <span class="token number">1000</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> from<span class="token punctuation">;</span> i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">await</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>delay<span class="token punctuation">)</span> + <span class="token keyword">yield</span> i + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>And here we don't have to explicitly use <code>Symbol.asyncIterator</code>, in fact, an async generator function is already an async iterable!</p> +<p>If we decide to use generators, this will help us to write even more concise iterator and iterable objects.</p> +<h2 id="using-javascript-iteration-protocols-with-node.js" tabindex="-1" class="title is-2">Using JavaScript iteration protocols with Node.js</h2> +<p>Everything we have been discussing so far is part of the JavaScript specification, but what about Node.js?</p> +<p>Actually, support for these features looks quite good in Node.js!</p> +<p>Synchronous iteration protocols have been supported in Node.js for a long time (since Node.js 0.12).</p> +<p>Recent versions of Node.js (Node.js 10.3) introduced support for async iterators and the <code>for await...of</code> syntax.</p> +<p>Synchronous iterable objects and the <code>for...of</code> syntax are quite widespread, so in the next sections, we will focus on providing some examples of how you can take advantage of its asynchronous counterpart and the <code>for await...of</code> syntax.</p> +<h3 id="node.js-readable-streams-and-async-iterators" tabindex="-1" class="title is-3">Node.js readable streams and async iterators</h3> +<p>One interesting detail that needs a bit more visibility is that Node.js <em>Readable</em> streams are async iterable objects since Node.js 11.14.</p> +<p>This basically means that we can consume data from a Readable stream using <code>for await...of</code>.</p> +<p>Let's see a simple example of a CLI utility that allows us to read the content of a given file and count the number of bytes:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> createReadStream <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'fs'</span> + +<span class="token keyword">const</span> sourcePath <span class="token operator">=</span> process<span class="token punctuation">.</span>argv<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> +<span class="token keyword">const</span> sourceStream <span class="token operator">=</span> <span class="token function">createReadStream</span><span class="token punctuation">(</span>sourcePath<span class="token punctuation">)</span> + +<span class="token keyword">let</span> bytes <span class="token operator">=</span> <span class="token number">0</span> +<span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> chunk <span class="token keyword">of</span> sourceStream<span class="token punctuation">)</span> <span class="token punctuation">{</span> + bytes <span class="token operator">+=</span> chunk<span class="token punctuation">.</span>length +<span class="token punctuation">}</span> + +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>process<span class="token punctuation">.</span>argv<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>bytes<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> bytes</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span></code></pre> +<p>The interesting thing is that when we are using this approach the stream is consumed in <em>non-flowing</em> (or <em>paused</em>) mode which can help us to handle backpressure in a very simple way.</p> +<p>Let's say that we want to write every chunk to a very slow transform stream (that we are going to identify with <code>SlowTransform</code>), this is how we can handle backpressure:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> createReadStream <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'fs'</span> +<span class="token keyword">import</span> <span class="token punctuation">{</span> once <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'events'</span> + +<span class="token keyword">const</span> sourcePath <span class="token operator">=</span> process<span class="token punctuation">.</span>argv<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> +<span class="token keyword">const</span> sourceStream <span class="token operator">=</span> <span class="token function">createReadStream</span><span class="token punctuation">(</span>sourcePath<span class="token punctuation">)</span> +<span class="token keyword">const</span> destStream <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SlowTransform</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + +<span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> chunk <span class="token keyword">of</span> sourceStream<span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">const</span> canContinue <span class="token operator">=</span> destStream<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>chunk<span class="token punctuation">)</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>canContinue<span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token comment">// backpressure, now we stop and we need to wait for drain</span> + <span class="token keyword">await</span> <span class="token function">once</span><span class="token punctuation">(</span>destStream<span class="token punctuation">,</span> <span class="token string">'drain'</span><span class="token punctuation">)</span> + <span class="token comment">// ok now it's safe to resume writing</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>Note that having an <code>await</code> inside the <code>for await...of</code> block will effectively pause the iteration. This will stop consuming data from the source stream until the destination stream is drained.</p> +<h3 id="converting-a-node.js-event-emitter-to-an-async-iterable" tabindex="-1" class="title is-3">Converting a Node.js event emitter to an async iterable</h3> +<p>Another interesting use case for async iteration in Node.js is when dealing with repeated events happening over time.</p> +<p>Events are generally fired by an <em>event emitter</em> and, since version 12.16, Node.js offers an interesting utility to convert a sequence of events into an async iterable.</p> +<p>We can see a simple example by using the third party module <a href="https://npm.im/glob"><code>glob</code></a> which allows us to find files matching a specific glob expression.</p> +<p>In this example we will find and print all the JavaScript files (<code>.js</code> extension) in the current folder (and subfolders):</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> on <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'events'</span> +<span class="token keyword">import</span> glob <span class="token keyword">from</span> <span class="token string">'glob'</span> + +<span class="token keyword">const</span> matcher <span class="token operator">=</span> <span class="token function">glob</span><span class="token punctuation">(</span><span class="token string">'**/*.js'</span><span class="token punctuation">)</span> + +<span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token punctuation">[</span>filePath<span class="token punctuation">]</span> <span class="token keyword">of</span> <span class="token function">on</span><span class="token punctuation">(</span>matcher<span class="token punctuation">,</span> <span class="token string">'match'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>filePath<span class="token punctuation">)</span> +<span class="token punctuation">}</span></code></pre> +<p>As you can see, we are using <code>on(matcher, 'match')</code> to create an async iterable that will <em>produce</em> a new value every time the <code>matcher</code> instance fires a <code>match</code> event.</p> +<p>Note that the value produced by this async iterable at every iteration is an array containing all the values contained in the original <code>match</code> event. This is the reason why we need to use destructuring to extract the <code>filePath</code>.</p> +<p>At this point you might ask: &quot;wait a second, but how do we know, with this approach, when there are no more events to process?&quot;</p> +<p>And that's a great question... we don't!</p> +<p>In fact, we are only listening for <code>match</code> events and we don't really have a way to stop the loop.</p> +<p>If we put any code just after the <code>for await...of</code> loop, that code will never be executed.</p> +<p>One solution to this problem is the <code>AbortController</code>, which allows us to create an Async Iterable that can be aborted.</p> +<p>With that, we could listen for the <code>end</code> event on our <code>matcher</code> instance and, once that happens, we can use the <code>AbortController</code> to stop the iteration.</p> +<p>Let's see some code:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> on <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'events'</span> +<span class="token keyword">import</span> glob <span class="token keyword">from</span> <span class="token string">'glob'</span> + +<span class="token keyword">const</span> matcher <span class="token operator">=</span> <span class="token function">glob</span><span class="token punctuation">(</span><span class="token string">'**/*.js'</span><span class="token punctuation">)</span> +<span class="token keyword">const</span> ac <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">global<span class="token punctuation">.</span>AbortController</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + +matcher<span class="token punctuation">.</span><span class="token function">once</span><span class="token punctuation">(</span><span class="token string">'end'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> ac<span class="token punctuation">.</span><span class="token function">abort</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> + +<span class="token keyword">try</span> <span class="token punctuation">{</span> + <span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token punctuation">[</span>filePath<span class="token punctuation">]</span> <span class="token keyword">of</span> <span class="token function">on</span><span class="token punctuation">(</span>matcher<span class="token punctuation">,</span> <span class="token string">'match'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">signal</span><span class="token operator">:</span> ac<span class="token punctuation">.</span>signal <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">./</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>filePath<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ac<span class="token punctuation">.</span>signal<span class="token punctuation">.</span>aborted<span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> + process<span class="token punctuation">.</span><span class="token function">exit</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span> + +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'NOW WE GETTING HERE! :)'</span><span class="token punctuation">)</span></code></pre> +<p>In the code example above, you can see that we are creating a new instance of <code>AbortController</code> by using <code>new global.AbortController()</code>.</p> +<p>Then, we listen for the <code>end</code> event on our <code>matcher</code> and when that happens we invoke <code>abort()</code> on our <code>AbortController</code> instance.</p> +<p>The last step is to pass the <code>AbortController</code> instance to the <code>on()</code> function. We do that by passing an options object and using the <code>signal</code> option.</p> +<p>You might have noticed that we also added a <code>try/catch</code> block. This is actually very important. When we stop the iteration using an <code>AbortController</code> this will not simply stop the iteration, but it will raise an exception.</p> +<p>In this case the exception is expected, so we handle it gracefully. We also want to distinguish the abort exception from other unintended exceptions, so we make sure to check wheter our abort signal was raised, otherwise we exit the program with an error.</p> +<p>Note that this is a lot of work, so this pattern, while it's cute, might not always give you great benefits compared to simply handling events using regular listeners.</p> +<h2 id="consuming-paginated-data-with-async-iterators" tabindex="-1" class="title is-2">Consuming paginated data with async iterators</h2> +<p>As we mentioned before with the DynamoDB examples, another great use case for async iteration is when we need to fetch data from a remote paginated dataset. Even more so when we cannot determine how to access the next page until we have fetched the previous one. This is a typical example of asynchronous sequential iteration and it's probably the most adequate use case for async iterators.</p> +<p>Just to present a very simple example, let's use <a href="https://swapi.dev/">a free and open-source Star Wars API</a> (happy May 4th everyone!) which allows us to access all the Star Wars characters in a paginated fashion.</p> +<p>To get data from this API, we can make a GET request to the following endpoint:</p> +<pre class="language-text"><code class="language-text">https://swapi.dev/api/people</code></pre> +<p>This request will respond with a JSON message that looks like this:</p> +<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> + <span class="token property">"count"</span><span class="token operator">:</span> <span class="token number">82</span><span class="token punctuation">,</span> + <span class="token property">"next"</span><span class="token operator">:</span> <span class="token string">"http://swapi.dev/api/people/?page=2"</span><span class="token punctuation">,</span> + <span class="token property">"results"</span><span class="token operator">:</span> <span class="token punctuation">[</span> + <span class="token punctuation">{</span> + <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Sly Moore"</span><span class="token punctuation">,</span> + <span class="token property">"height"</span><span class="token operator">:</span> <span class="token string">"178"</span><span class="token punctuation">,</span> + <span class="token property">"..."</span><span class="token operator">:</span> <span class="token string">"more fields..."</span><span class="token punctuation">,</span> + <span class="token punctuation">}</span><span class="token punctuation">,</span> + <span class="token punctuation">{</span> + <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Another character"</span><span class="token punctuation">,</span> + <span class="token property">"height"</span><span class="token operator">:</span> <span class="token string">"whatever"</span><span class="token punctuation">,</span> + <span class="token property">"..."</span><span class="token operator">:</span> <span class="token string">"more fields..."</span><span class="token punctuation">,</span> + <span class="token punctuation">}</span><span class="token punctuation">,</span> + <span class="token punctuation">{</span> + <span class="token property">"..."</span><span class="token operator">:</span> <span class="token string">"more characters"</span> + <span class="token punctuation">}</span> + <span class="token punctuation">]</span> +<span class="token punctuation">}</span></code></pre> +<p>Note that the <code>next</code> field contains the URL that we can use to fetch the data from the following page. All the records for the current page are presented in the <code>results</code> field.</p> +<p>With these details in mind, this is how we can create a custom client that allows us to fetch all the characters using the <code>for await...of</code> syntax:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> axios <span class="token keyword">from</span> <span class="token string">'axios'</span> + +<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token operator">*</span> <span class="token function">starWarsCharacters</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">let</span> nextUrl <span class="token operator">=</span> <span class="token string">'https://swapi.dev/api/people'</span> + <span class="token keyword">while</span> <span class="token punctuation">(</span>nextUrl<span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>nextUrl<span class="token punctuation">)</span> + nextUrl <span class="token operator">=</span> response<span class="token punctuation">.</span>data<span class="token punctuation">.</span>next + <span class="token keyword">yield</span> response<span class="token punctuation">.</span>data<span class="token punctuation">.</span>results + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>Now we can use this function as follows:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> page <span class="token keyword">of</span> <span class="token function">starWarsCharacters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> char <span class="token keyword">of</span> page<span class="token punctuation">)</span> <span class="token punctuation">{</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>char<span class="token punctuation">.</span>name<span class="token punctuation">)</span> + <span class="token punctuation">}</span> +<span class="token punctuation">}</span></code></pre> +<p>If we run this code we should see the following output:</p> +<pre class="language-text"><code class="language-text">Luke Skywalker +C-3PO +R2-D2 +Darth Vader +Leia Organa +[... other 77 names]</code></pre> +<h2 id="wrapping-up" tabindex="-1" class="title is-2">Wrapping up</h2> +<p>This concludes our exploration of JavaScript iteration protocols. At this point, you should feel comfortable understanding what the various protocols are and how to use <code>for...of</code> and <code>for await...of</code> effectively in both JavaScript and Node.js.</p> +<p>These techniques are often ideal to implement synchronous and asynchronous sequential iteration patterns, which makes them very effective tools in our toolbelt.</p> +<p>If you are interested in learning more patterns and interesting Node.js techniques, consider checking out <a href="https://www.nodejsdesignpatterns.com/">Node.js Design Patterns</a>. You can grab a free chapter for free by filling the form at the end of this page. Among other things, this free chapter contains some other examples of iteration and async iterators!</p> +<p>See you at the next post!</p> +<p>CIAO 👋</p> +<p>P.S. All the examples presented in this article are available on GitHub at <a href="https://github.com/lmammino/javascript-iteration-protocols">lmammino/javascript-iteration-protocols</a>.</p> +<p><small>Thanks to <a href="https://twitter.com/mariocasciaro">Mario Casciaro</a> for the kind review of this article and to <a href="https://twitter.com/Dominus_Kelvin">Kelvin Omereshone</a> for finding and fixing a few typos.</small></p> + + + + + Node.js stream consumer utilities + + 2022-03-11T12:30:00Z + https://www.nodejsdesignpatterns.com/blog/node-js-stream-consumer/ + <p>How many times did you need to read the entire content of a <code>Readable</code> stream into memory and ended up writing something like this?</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> chunks <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> +someReadableStream<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'data'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">chunk</span><span class="token punctuation">)</span> <span class="token operator">=></span> chunks<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>chunk<span class="token punctuation">)</span><span class="token punctuation">)</span> +someReadableStream<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'end'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> + <span class="token keyword">const</span> data <span class="token operator">=</span> Buffer<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>chunks<span class="token punctuation">)</span> + <span class="token comment">// do something with `data`</span> +<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre> +<p>Or using <a href="https://www.nodejsdesignpatterns.com/blog/javascript-async-iterators/">async iterators</a>:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> chunks <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> +<span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> chunk <span class="token keyword">of</span> someReadableStream<span class="token punctuation">)</span> <span class="token punctuation">{</span> + chunks<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>chunk<span class="token punctuation">)</span> +<span class="token punctuation">}</span> +<span class="token keyword">const</span> data <span class="token operator">=</span> Buffer<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>chunks<span class="token punctuation">)</span> +<span class="token comment">// do something with `data`</span></code></pre> +<p>This is a bit of a boilerplate-heavy solution for just consuming an entire readable stream. Consider that here we are not even handling errors, trying to do that (as we should!) will add even more boilerplate!</p> +<p>If you wish there was an easier way, well keep reading, this article is for you!</p> +<h2 id="the-stream%2Fconsumers-module" tabindex="-1" class="title is-2">The <code>stream/consumers</code> module</h2> +<p>Since Node.js version 16, there is a new built in stream utility library called <code>stream/consumers</code> which offers a bunch of useful utilities to consume the entire content of a <code>ReadableStream</code>.</p> +<p><s>At the time of writing this article, <code>stream/consumers</code> does not even appear in the official Node.js documentation, so it's still of a hidden gem. Hopefully this article will help to spread the word a little bit.</s></p> +<p><strong>UPDATE</strong>: It turns out that this module is documented under the <a href="https://nodejs.org/api/webstreams.html#streamconsumersjsonstream">Web Streams API section</a> and in fact these utilities are both compatible with Node.js streams and web streams.</p> +<p>Without further ado, let's see what's inside this module:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> consumers <span class="token keyword">from</span> <span class="token string">'stream/consumers'</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>consumers<span class="token punctuation">)</span></code></pre> +<p>If we run this code, we will see the following output:</p> +<pre class="language-plain"><code class="language-plain">{ + arrayBuffer: [AsyncFunction: arrayBuffer], + blob: [AsyncFunction: blob], + buffer: [AsyncFunction: buffer], + text: [AsyncFunction: text], + json: [AsyncFunction: json] +}</code></pre> +<p>So, what we can tell is that the <code>stream/consumers</code> module exposes some async function that seem to be helpful to consume <code>Readable</code> streams in different ways:</p> +<ul> +<li>As binary data (<code>ArrayBuffer</code>, <code>Blob</code>, <code>Buffer</code>)</li> +<li>As text</li> +<li>As JSON</li> +</ul> +<p>In the next sections we will see some examples on how to use these functions.</p> +<h2 id="reading-a-binary-file-from-a-readable-stream" tabindex="-1" class="title is-2">Reading a binary file from a Readable stream</h2> +<p>Ok, let's say that we have to do some processing on a picture and, in order to do that, we need to load the entire binary content representing the picture from a file to memory.</p> +<p>We could easily use the <code>buffer</code> function from the <code>stream/consumers</code> library to do that:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> path <span class="token keyword">from</span> <span class="token string">'path'</span> +<span class="token keyword">import</span> <span class="token punctuation">{</span> createReadStream <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'fs'</span> +<span class="token keyword">import</span> consumers <span class="token keyword">from</span> <span class="token string">'stream/consumers'</span> + +<span class="token keyword">const</span> __dirname <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span><span class="token string">'.'</span><span class="token punctuation">,</span> <span class="token keyword">import</span><span class="token punctuation">.</span>meta<span class="token punctuation">.</span>url<span class="token punctuation">)</span><span class="token punctuation">.</span>pathname<span class="token punctuation">;</span> +<span class="token keyword">const</span> readable <span class="token operator">=</span> <span class="token function">createReadStream</span><span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span>__dirname<span class="token punctuation">,</span> <span class="token string">'picture.png'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> +<span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> consumers<span class="token punctuation">.</span><span class="token function">buffer</span><span class="token punctuation">(</span>readable<span class="token punctuation">)</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span></code></pre> +<p>If we execute this code, we will see the following output:</p> +<pre class="language-plain"><code class="language-plain">(node:7685) ExperimentalWarning: buffer.Blob is an experimental feature. This feature could change at any time +(Use `node --trace-warnings ...` to show where the warning was created) +&lt;Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 02 80 00 00 01 c1 08 02 00 00 00 76 43 9d 20 00 00 00 01 73 52 47 42 00 ae ce 1c e9 00 00 00 04 ... 347824 more bytes></code></pre> +<p>You can see that all the binary data (~300Kb) was loaded in the buffer, but also that this feature (as of v17.7.1) is still experimental and therefore we get a warning. You will get a similar warning also when trying to use <code>consumers.arrayBuffer</code> and <code>consumers.blob</code>. This will be the case until <code>buffer.Blob</code> is stabilised.</p> +<h2 id="reading-a-json-object-from-a-readable-stream" tabindex="-1" class="title is-2">Reading a JSON object from a Readable stream</h2> +<p>Similarly to what we just saw in the previous section, we can use the <code>stream/consumers</code> library to consume the entire content of a <code>ReadableStream</code> as a JSON encoded string. For instance, we could use this to process the response body from an HTTP request:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> get <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'https'</span> +<span class="token keyword">import</span> consumers <span class="token keyword">from</span> <span class="token string">'stream/consumers'</span> + + +<span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token string">'https://rickandmortyapi.com/api/character/639'</span> +<span class="token function">get</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> + <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> consumers<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span>response<span class="token punctuation">)</span> + console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> +<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre> +<p>Here we are using the awesome (and free) <a href="https://rickandmortyapi.com/">The Rick and Morty API</a>. If we run this code we should see the following output:</p> +<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> + id<span class="token operator">:</span> <span class="token number">639</span><span class="token punctuation">,</span> + name<span class="token operator">:</span> 'Uncle Nibbles'<span class="token punctuation">,</span> + status<span class="token operator">:</span> 'Dead'<span class="token punctuation">,</span> + species<span class="token operator">:</span> 'Alien'<span class="token punctuation">,</span> + type<span class="token operator">:</span> 'Soulless Puppet'<span class="token punctuation">,</span> + gender<span class="token operator">:</span> 'Male'<span class="token punctuation">,</span> + origin<span class="token operator">:</span> <span class="token punctuation">{</span> + name<span class="token operator">:</span> 'Tickets Please Guy Nightmare'<span class="token punctuation">,</span> + url<span class="token operator">:</span> 'https<span class="token operator">:</span><span class="token comment">//rickandmortyapi.com/api/location/98'</span> + <span class="token punctuation">}</span><span class="token punctuation">,</span> + location<span class="token operator">:</span> <span class="token punctuation">{</span> + name<span class="token operator">:</span> 'Tickets Please Guy Nightmare'<span class="token punctuation">,</span> + url<span class="token operator">:</span> 'https<span class="token operator">:</span><span class="token comment">//rickandmortyapi.com/api/location/98'</span> + <span class="token punctuation">}</span><span class="token punctuation">,</span> + image<span class="token operator">:</span> 'https<span class="token operator">:</span><span class="token comment">//rickandmortyapi.com/api/character/avatar/639.jpeg',</span> + episode<span class="token operator">:</span> <span class="token punctuation">[</span> 'https<span class="token operator">:</span><span class="token comment">//rickandmortyapi.com/api/episode/37' ],</span> + url<span class="token operator">:</span> 'https<span class="token operator">:</span><span class="token comment">//rickandmortyapi.com/api/character/639',</span> + created<span class="token operator">:</span> '<span class="token number">2020</span><span class="token number">-08</span>-06T16<span class="token operator">:</span><span class="token number">51</span><span class="token operator">:</span><span class="token number">23</span>.084Z' +<span class="token punctuation">}</span></code></pre> +<p>It's also worth mentioning that <code>consumers.json</code> does not produce any warning, so this feature can be considered stable in Node.js 16.</p> +<h2 id="reading-a-text-from-a-readable-stream" tabindex="-1" class="title is-2">Reading a text from a Readable stream</h2> +<p>Let's discuss one more example. Let's try to consume an entire readable stream as text, which means that we will be consuming the stream assuming it's a valid UTF-8 encoded string and save the result into a string variable.</p> +<p>One simple example could be to try to read a string from the standard input in a CLI application:</p> +<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> consumers <span class="token keyword">from</span> <span class="token string">'stream/consumers'</span> + +<span class="token keyword">const</span> input <span class="token operator">=</span> <span class="token keyword">await</span> consumers<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span>process<span class="token punctuation">.</span>stdin<span class="token punctuation">)</span> +console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>input<span class="token punctuation">)</span></code></pre> +<p>If we try to run the script as follows:</p> +<pre class="language-bash"><code class="language-bash"><span class="token function">cat</span> mobydick.txt <span class="token operator">|</span> <span class="token function">node</span> stdin.js</code></pre> +<p>We should see something like this in the output:</p> +<pre class="language-plain"><code class="language-plain">CHAPTER 1 + +Loomings. + + +Call me Ishmael. Some years ago--never mind how long +precisely--having little or no money in my purse, and nothing +particular to interest me on shore, I thought I would sail about a +little and see the watery part of the world. It is a way I have of +driving off the spleen and regulating the circulation. Whenever I +find myself growing grim about the mouth; whenever it is a damp, +drizzly November in my soul; whenever I find myself involuntarily +pausing before coffin warehouses, and bringing up the rear of every +funeral I meet; and especially whenever my hypos get such an upper +hand of me, that it requires a strong moral principle to prevent me +from deliberately stepping into the street, and methodically knocking +people's hats off--then, I account it high time to get to sea as soon +as I can. This is my substitute for pistol and ball. With a +philosophical flourish Cato throws himself upon his sword; I quietly +take to the ship. There is nothing surprising in this. If they but +knew it, almost all men in their degree, some time or other, cherish +very nearly the same feelings towards the ocean with me. + +[...]</code></pre> +<p>Again, no warnings, so this feature is stable in Node.js v16.</p> +<h2 id="is-this-even-a-good-idea%3F" tabindex="-1" class="title is-2">Is this even a good idea?</h2> +<p>Now that you know how to use this utility, it's worth mentioning that as with all good things it should be used with moderation.</p> +<p>In fact, accumulating all the content of a stream in memory is something that should not be done lightly.</p> +<p>Streams are an abstraction that has been built into Node.js to allow developers to handle arbitrary amounts of data (even infinite streams) and process such data as soon as possible, while still keeping the memory footprint low.</p> +<p>By processing the data in chunks, we can keep the amount of memory being allocated at any given time low and have our processing logic run efficiently.</p> +<p>When we accumulate an entire stream we are effectively defeating all the advantages of Node.js streams, so this is something that is recommended only when you are absolutely certain you are dealing with small amounts of data.</p> +<h2 id="wapping-up" tabindex="-1" class="title is-2">Wapping up</h2> +<p>This is all for this article, feel free to <a href="https://twitter.com/loige">reach out to me on Twitter</a> if you found this article interesting and if you think you learned something useful.</p> +<p>If you are curious, you can also <a href="https://github.com/nodejs/node/blob/main/lib/stream/consumers.js">read the code of the <code>stream/consumers</code> module</a>, it's actually a really thin layer (less than 100 lines) and you can learn a trick or two by doing that.</p> +<p>See you in the next article!</p> + + + \ No newline at end of file diff --git a/favicon-16x16.png b/favicon-16x16.png new file mode 100755 index 0000000..4846ec2 Binary files /dev/null and b/favicon-16x16.png differ diff --git a/favicon-32x32.png b/favicon-32x32.png new file mode 100755 index 0000000..c4727ac Binary files /dev/null and b/favicon-32x32.png differ diff --git a/favicon.ico b/favicon.ico new file mode 100755 index 0000000..2af6d13 Binary files /dev/null and b/favicon.ico differ diff --git a/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-128.png b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-128.png new file mode 100644 index 0000000..c2c63aa Binary files /dev/null and b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-128.png differ diff --git a/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-128.webp b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-128.webp new file mode 100644 index 0000000..fe1c699 Binary files /dev/null and b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-128.webp differ diff --git a/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-256.png b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-256.png new file mode 100644 index 0000000..f42db49 Binary files /dev/null and b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-256.png differ diff --git a/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-256.webp b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-256.webp new file mode 100644 index 0000000..77af9e7 Binary files /dev/null and b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-256.webp differ diff --git a/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-512.png b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-512.png new file mode 100644 index 0000000..a2167fa Binary files /dev/null and b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-512.png differ diff --git a/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-512.webp b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-512.webp new file mode 100644 index 0000000..7950d9d Binary files /dev/null and b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-512.webp differ diff --git a/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-64.png b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-64.png new file mode 100644 index 0000000..d5ee950 Binary files /dev/null and b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-64.png differ diff --git a/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-64.webp b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-64.webp new file mode 100644 index 0000000..fa91db3 Binary files /dev/null and b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-64.webp differ diff --git a/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-800.png b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-800.png new file mode 100644 index 0000000..1405909 Binary files /dev/null and b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-800.png differ diff --git a/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-800.webp b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-800.webp new file mode 100644 index 0000000..2730cff Binary files /dev/null and b/img/amazon-reviews-rating-screenshot-5RxCqdhtLt-800.webp differ diff --git a/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-128.png b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-128.png new file mode 100644 index 0000000..fc3a666 Binary files /dev/null and b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-128.png differ diff --git a/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-128.webp b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-128.webp new file mode 100644 index 0000000..4b6d151 Binary files /dev/null and b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-128.webp differ diff --git a/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-2022.png b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-2022.png new file mode 100644 index 0000000..68ac51a Binary files /dev/null and b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-2022.png differ diff --git a/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-2022.webp b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-2022.webp new file mode 100644 index 0000000..1480bc1 Binary files /dev/null and b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-2022.webp differ diff --git a/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-256.png b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-256.png new file mode 100644 index 0000000..1eb7d7f Binary files /dev/null and b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-256.png differ diff --git a/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-256.webp b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-256.webp new file mode 100644 index 0000000..87d4300 Binary files /dev/null and b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-256.webp differ diff --git a/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-512.png b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-512.png new file mode 100644 index 0000000..aa37b22 Binary files /dev/null and b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-512.png differ diff --git a/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-512.webp b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-512.webp new file mode 100644 index 0000000..84bd31d Binary files /dev/null and b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-512.webp differ diff --git a/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-64.png b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-64.png new file mode 100644 index 0000000..ea42c9a Binary files /dev/null and b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-64.png differ diff --git a/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-64.webp b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-64.webp new file mode 100644 index 0000000..1bd208e Binary files /dev/null and b/img/aurei-fixed-race-condition-node-js-JsApfpFGe9-64.webp differ diff --git a/img/aurei-race-condition-node-js-Fngk3b5IPO-128.png b/img/aurei-race-condition-node-js-Fngk3b5IPO-128.png new file mode 100644 index 0000000..c08f502 Binary files /dev/null and b/img/aurei-race-condition-node-js-Fngk3b5IPO-128.png differ diff --git a/img/aurei-race-condition-node-js-Fngk3b5IPO-128.webp b/img/aurei-race-condition-node-js-Fngk3b5IPO-128.webp new file mode 100644 index 0000000..7f09dd0 Binary files /dev/null and b/img/aurei-race-condition-node-js-Fngk3b5IPO-128.webp differ diff --git a/img/aurei-race-condition-node-js-Fngk3b5IPO-2002.png b/img/aurei-race-condition-node-js-Fngk3b5IPO-2002.png new file mode 100644 index 0000000..d2df5ba Binary files /dev/null and b/img/aurei-race-condition-node-js-Fngk3b5IPO-2002.png differ diff --git a/img/aurei-race-condition-node-js-Fngk3b5IPO-2002.webp b/img/aurei-race-condition-node-js-Fngk3b5IPO-2002.webp new file mode 100644 index 0000000..288f957 Binary files /dev/null and b/img/aurei-race-condition-node-js-Fngk3b5IPO-2002.webp differ diff --git a/img/aurei-race-condition-node-js-Fngk3b5IPO-256.png b/img/aurei-race-condition-node-js-Fngk3b5IPO-256.png new file mode 100644 index 0000000..240ac63 Binary files /dev/null and b/img/aurei-race-condition-node-js-Fngk3b5IPO-256.png differ diff --git a/img/aurei-race-condition-node-js-Fngk3b5IPO-256.webp b/img/aurei-race-condition-node-js-Fngk3b5IPO-256.webp new file mode 100644 index 0000000..b00326f Binary files /dev/null and b/img/aurei-race-condition-node-js-Fngk3b5IPO-256.webp differ diff --git a/img/aurei-race-condition-node-js-Fngk3b5IPO-512.png b/img/aurei-race-condition-node-js-Fngk3b5IPO-512.png new file mode 100644 index 0000000..e3d8c9e Binary files /dev/null and b/img/aurei-race-condition-node-js-Fngk3b5IPO-512.png differ diff --git a/img/aurei-race-condition-node-js-Fngk3b5IPO-512.webp b/img/aurei-race-condition-node-js-Fngk3b5IPO-512.webp new file mode 100644 index 0000000..871d52d Binary files /dev/null and b/img/aurei-race-condition-node-js-Fngk3b5IPO-512.webp differ diff --git a/img/aurei-race-condition-node-js-Fngk3b5IPO-64.png b/img/aurei-race-condition-node-js-Fngk3b5IPO-64.png new file mode 100644 index 0000000..9cb349a Binary files /dev/null and b/img/aurei-race-condition-node-js-Fngk3b5IPO-64.png differ diff --git a/img/aurei-race-condition-node-js-Fngk3b5IPO-64.webp b/img/aurei-race-condition-node-js-Fngk3b5IPO-64.webp new file mode 100644 index 0000000..f8859db Binary files /dev/null and b/img/aurei-race-condition-node-js-Fngk3b5IPO-64.webp differ diff --git a/img/book-stack-LOzEKLkbLm-128.png b/img/book-stack-LOzEKLkbLm-128.png new file mode 100644 index 0000000..a545110 Binary files /dev/null and b/img/book-stack-LOzEKLkbLm-128.png differ diff --git a/img/book-stack-LOzEKLkbLm-128.webp b/img/book-stack-LOzEKLkbLm-128.webp new file mode 100644 index 0000000..796e5b0 Binary files /dev/null and b/img/book-stack-LOzEKLkbLm-128.webp differ diff --git a/img/book-stack-LOzEKLkbLm-1338.png b/img/book-stack-LOzEKLkbLm-1338.png new file mode 100644 index 0000000..63ab865 Binary files /dev/null and b/img/book-stack-LOzEKLkbLm-1338.png differ diff --git a/img/book-stack-LOzEKLkbLm-1338.webp b/img/book-stack-LOzEKLkbLm-1338.webp new file mode 100644 index 0000000..0985c77 Binary files /dev/null and b/img/book-stack-LOzEKLkbLm-1338.webp differ diff --git a/img/book-stack-LOzEKLkbLm-256.png b/img/book-stack-LOzEKLkbLm-256.png new file mode 100644 index 0000000..cd76775 Binary files /dev/null and b/img/book-stack-LOzEKLkbLm-256.png differ diff --git a/img/book-stack-LOzEKLkbLm-256.webp b/img/book-stack-LOzEKLkbLm-256.webp new file mode 100644 index 0000000..64a1825 Binary files /dev/null and b/img/book-stack-LOzEKLkbLm-256.webp differ diff --git a/img/book-stack-LOzEKLkbLm-512.png b/img/book-stack-LOzEKLkbLm-512.png new file mode 100644 index 0000000..a70c7c0 Binary files /dev/null and b/img/book-stack-LOzEKLkbLm-512.png differ diff --git a/img/book-stack-LOzEKLkbLm-512.webp b/img/book-stack-LOzEKLkbLm-512.webp new file mode 100644 index 0000000..918c259 Binary files /dev/null and b/img/book-stack-LOzEKLkbLm-512.webp differ diff --git a/img/book-stack-LOzEKLkbLm-64.png b/img/book-stack-LOzEKLkbLm-64.png new file mode 100644 index 0000000..6edf5ae Binary files /dev/null and b/img/book-stack-LOzEKLkbLm-64.png differ diff --git a/img/book-stack-LOzEKLkbLm-64.webp b/img/book-stack-LOzEKLkbLm-64.webp new file mode 100644 index 0000000..095453a Binary files /dev/null and b/img/book-stack-LOzEKLkbLm-64.webp differ diff --git a/img/book-unboxed-VGNyzgzG86-1024.png b/img/book-unboxed-VGNyzgzG86-1024.png new file mode 100644 index 0000000..1c4d47b Binary files /dev/null and b/img/book-unboxed-VGNyzgzG86-1024.png differ diff --git a/img/book-unboxed-VGNyzgzG86-1024.webp b/img/book-unboxed-VGNyzgzG86-1024.webp new file mode 100644 index 0000000..fba3ba7 Binary files /dev/null and b/img/book-unboxed-VGNyzgzG86-1024.webp differ diff --git a/img/book-unboxed-VGNyzgzG86-1230.png b/img/book-unboxed-VGNyzgzG86-1230.png new file mode 100644 index 0000000..590dae6 Binary files /dev/null and b/img/book-unboxed-VGNyzgzG86-1230.png differ diff --git a/img/book-unboxed-VGNyzgzG86-1230.webp b/img/book-unboxed-VGNyzgzG86-1230.webp new file mode 100644 index 0000000..37fa8d8 Binary files /dev/null and b/img/book-unboxed-VGNyzgzG86-1230.webp differ diff --git a/img/book-unboxed-VGNyzgzG86-128.png b/img/book-unboxed-VGNyzgzG86-128.png new file mode 100644 index 0000000..ede42d7 Binary files /dev/null and b/img/book-unboxed-VGNyzgzG86-128.png differ diff --git a/img/book-unboxed-VGNyzgzG86-128.webp b/img/book-unboxed-VGNyzgzG86-128.webp new file mode 100644 index 0000000..5e4ea56 Binary files /dev/null and b/img/book-unboxed-VGNyzgzG86-128.webp differ diff --git a/img/book-unboxed-VGNyzgzG86-256.png b/img/book-unboxed-VGNyzgzG86-256.png new file mode 100644 index 0000000..b1cae5b Binary files /dev/null and b/img/book-unboxed-VGNyzgzG86-256.png differ diff --git a/img/book-unboxed-VGNyzgzG86-256.webp b/img/book-unboxed-VGNyzgzG86-256.webp new file mode 100644 index 0000000..9c20eab Binary files /dev/null and b/img/book-unboxed-VGNyzgzG86-256.webp differ diff --git a/img/book-unboxed-VGNyzgzG86-512.png b/img/book-unboxed-VGNyzgzG86-512.png new file mode 100644 index 0000000..e8e5bba Binary files /dev/null and b/img/book-unboxed-VGNyzgzG86-512.png differ diff --git a/img/book-unboxed-VGNyzgzG86-512.webp b/img/book-unboxed-VGNyzgzG86-512.webp new file mode 100644 index 0000000..232f0f6 Binary files /dev/null and b/img/book-unboxed-VGNyzgzG86-512.webp differ diff --git a/img/book-unboxed-VGNyzgzG86-64.png b/img/book-unboxed-VGNyzgzG86-64.png new file mode 100644 index 0000000..adb8f48 Binary files /dev/null and b/img/book-unboxed-VGNyzgzG86-64.png differ diff --git a/img/book-unboxed-VGNyzgzG86-64.webp b/img/book-unboxed-VGNyzgzG86-64.webp new file mode 100644 index 0000000..e44f6b8 Binary files /dev/null and b/img/book-unboxed-VGNyzgzG86-64.webp differ diff --git a/img/david-gonzalez-node-js-GDOhVftNq2-400.png b/img/david-gonzalez-node-js-GDOhVftNq2-400.png new file mode 100644 index 0000000..5db14ec Binary files /dev/null and b/img/david-gonzalez-node-js-GDOhVftNq2-400.png differ diff --git a/img/david-gonzalez-node-js-GDOhVftNq2-400.webp b/img/david-gonzalez-node-js-GDOhVftNq2-400.webp new file mode 100644 index 0000000..d64c730 Binary files /dev/null and b/img/david-gonzalez-node-js-GDOhVftNq2-400.webp differ diff --git a/img/david-gonzalez-node-js-GDOhVftNq2-64.png b/img/david-gonzalez-node-js-GDOhVftNq2-64.png new file mode 100644 index 0000000..b839bed Binary files /dev/null and b/img/david-gonzalez-node-js-GDOhVftNq2-64.png differ diff --git a/img/david-gonzalez-node-js-GDOhVftNq2-64.webp b/img/david-gonzalez-node-js-GDOhVftNq2-64.webp new file mode 100644 index 0000000..662e7bd Binary files /dev/null and b/img/david-gonzalez-node-js-GDOhVftNq2-64.webp differ diff --git a/img/david-wells-node-js-gd6PYVBXHU-400.png b/img/david-wells-node-js-gd6PYVBXHU-400.png new file mode 100644 index 0000000..4a90820 Binary files /dev/null and b/img/david-wells-node-js-gd6PYVBXHU-400.png differ diff --git a/img/david-wells-node-js-gd6PYVBXHU-400.webp b/img/david-wells-node-js-gd6PYVBXHU-400.webp new file mode 100644 index 0000000..b18a380 Binary files /dev/null and b/img/david-wells-node-js-gd6PYVBXHU-400.webp differ diff --git a/img/david-wells-node-js-gd6PYVBXHU-64.png b/img/david-wells-node-js-gd6PYVBXHU-64.png new file mode 100644 index 0000000..b809245 Binary files /dev/null and b/img/david-wells-node-js-gd6PYVBXHU-64.png differ diff --git a/img/david-wells-node-js-gd6PYVBXHU-64.webp b/img/david-wells-node-js-gd6PYVBXHU-64.webp new file mode 100644 index 0000000..5c0d767 Binary files /dev/null and b/img/david-wells-node-js-gd6PYVBXHU-64.webp differ diff --git a/img/docker-vs-virtual-machines-m-F-bSuk1W-128.png b/img/docker-vs-virtual-machines-m-F-bSuk1W-128.png new file mode 100644 index 0000000..9484d85 Binary files /dev/null and b/img/docker-vs-virtual-machines-m-F-bSuk1W-128.png differ diff --git a/img/docker-vs-virtual-machines-m-F-bSuk1W-128.webp b/img/docker-vs-virtual-machines-m-F-bSuk1W-128.webp new file mode 100644 index 0000000..d0e8bab Binary files /dev/null and b/img/docker-vs-virtual-machines-m-F-bSuk1W-128.webp differ diff --git a/img/docker-vs-virtual-machines-m-F-bSuk1W-256.png b/img/docker-vs-virtual-machines-m-F-bSuk1W-256.png new file mode 100644 index 0000000..0b56f8d Binary files /dev/null and b/img/docker-vs-virtual-machines-m-F-bSuk1W-256.png differ diff --git a/img/docker-vs-virtual-machines-m-F-bSuk1W-256.webp b/img/docker-vs-virtual-machines-m-F-bSuk1W-256.webp new file mode 100644 index 0000000..bcf7b56 Binary files /dev/null and b/img/docker-vs-virtual-machines-m-F-bSuk1W-256.webp differ diff --git a/img/docker-vs-virtual-machines-m-F-bSuk1W-2900.png b/img/docker-vs-virtual-machines-m-F-bSuk1W-2900.png new file mode 100644 index 0000000..18ec6d6 Binary files /dev/null and b/img/docker-vs-virtual-machines-m-F-bSuk1W-2900.png differ diff --git a/img/docker-vs-virtual-machines-m-F-bSuk1W-2900.webp b/img/docker-vs-virtual-machines-m-F-bSuk1W-2900.webp new file mode 100644 index 0000000..f5b215b Binary files /dev/null and b/img/docker-vs-virtual-machines-m-F-bSuk1W-2900.webp differ diff --git a/img/docker-vs-virtual-machines-m-F-bSuk1W-512.png b/img/docker-vs-virtual-machines-m-F-bSuk1W-512.png new file mode 100644 index 0000000..1a5b0f9 Binary files /dev/null and b/img/docker-vs-virtual-machines-m-F-bSuk1W-512.png differ diff --git a/img/docker-vs-virtual-machines-m-F-bSuk1W-512.webp b/img/docker-vs-virtual-machines-m-F-bSuk1W-512.webp new file mode 100644 index 0000000..1787a04 Binary files /dev/null and b/img/docker-vs-virtual-machines-m-F-bSuk1W-512.webp differ diff --git a/img/docker-vs-virtual-machines-m-F-bSuk1W-64.png b/img/docker-vs-virtual-machines-m-F-bSuk1W-64.png new file mode 100644 index 0000000..2e0e636 Binary files /dev/null and b/img/docker-vs-virtual-machines-m-F-bSuk1W-64.png differ diff --git a/img/docker-vs-virtual-machines-m-F-bSuk1W-64.webp b/img/docker-vs-virtual-machines-m-F-bSuk1W-64.webp new file mode 100644 index 0000000..1c8eded Binary files /dev/null and b/img/docker-vs-virtual-machines-m-F-bSuk1W-64.webp differ diff --git a/img/ersel-aker-node-js-o1tKGWsXX2-500.png b/img/ersel-aker-node-js-o1tKGWsXX2-500.png new file mode 100644 index 0000000..2af2ddc Binary files /dev/null and b/img/ersel-aker-node-js-o1tKGWsXX2-500.png differ diff --git a/img/ersel-aker-node-js-o1tKGWsXX2-500.webp b/img/ersel-aker-node-js-o1tKGWsXX2-500.webp new file mode 100644 index 0000000..a440ca9 Binary files /dev/null and b/img/ersel-aker-node-js-o1tKGWsXX2-500.webp differ diff --git a/img/ersel-aker-node-js-o1tKGWsXX2-64.png b/img/ersel-aker-node-js-o1tKGWsXX2-64.png new file mode 100644 index 0000000..25d0c24 Binary files /dev/null and b/img/ersel-aker-node-js-o1tKGWsXX2-64.png differ diff --git a/img/ersel-aker-node-js-o1tKGWsXX2-64.webp b/img/ersel-aker-node-js-o1tKGWsXX2-64.webp new file mode 100644 index 0000000..79c549f Binary files /dev/null and b/img/ersel-aker-node-js-o1tKGWsXX2-64.webp differ diff --git a/img/gleb-bahmutov-node-js-c-b1CxKPd6-400.png b/img/gleb-bahmutov-node-js-c-b1CxKPd6-400.png new file mode 100644 index 0000000..a571ead Binary files /dev/null and b/img/gleb-bahmutov-node-js-c-b1CxKPd6-400.png differ diff --git a/img/gleb-bahmutov-node-js-c-b1CxKPd6-400.webp b/img/gleb-bahmutov-node-js-c-b1CxKPd6-400.webp new file mode 100644 index 0000000..82fb7ca Binary files /dev/null and b/img/gleb-bahmutov-node-js-c-b1CxKPd6-400.webp differ diff --git a/img/gleb-bahmutov-node-js-c-b1CxKPd6-64.png b/img/gleb-bahmutov-node-js-c-b1CxKPd6-64.png new file mode 100644 index 0000000..e0ee258 Binary files /dev/null and b/img/gleb-bahmutov-node-js-c-b1CxKPd6-64.png differ diff --git a/img/gleb-bahmutov-node-js-c-b1CxKPd6-64.webp b/img/gleb-bahmutov-node-js-c-b1CxKPd6-64.webp new file mode 100644 index 0000000..2372e34 Binary files /dev/null and b/img/gleb-bahmutov-node-js-c-b1CxKPd6-64.webp differ diff --git a/img/ire-aderinokun-node-js-Ztl0vq2kdO-316.png b/img/ire-aderinokun-node-js-Ztl0vq2kdO-316.png new file mode 100644 index 0000000..374f1a9 Binary files /dev/null and b/img/ire-aderinokun-node-js-Ztl0vq2kdO-316.png differ diff --git a/img/ire-aderinokun-node-js-Ztl0vq2kdO-316.webp b/img/ire-aderinokun-node-js-Ztl0vq2kdO-316.webp new file mode 100644 index 0000000..66e7e6e Binary files /dev/null and b/img/ire-aderinokun-node-js-Ztl0vq2kdO-316.webp differ diff --git a/img/ire-aderinokun-node-js-Ztl0vq2kdO-64.png b/img/ire-aderinokun-node-js-Ztl0vq2kdO-64.png new file mode 100644 index 0000000..09cfec8 Binary files /dev/null and b/img/ire-aderinokun-node-js-Ztl0vq2kdO-64.png differ diff --git a/img/ire-aderinokun-node-js-Ztl0vq2kdO-64.webp b/img/ire-aderinokun-node-js-Ztl0vq2kdO-64.webp new file mode 100644 index 0000000..b2a055d Binary files /dev/null and b/img/ire-aderinokun-node-js-Ztl0vq2kdO-64.webp differ diff --git a/img/javascript-async-iterable-countdown_6150da48.gif b/img/javascript-async-iterable-countdown_6150da48.gif new file mode 100644 index 0000000..bdc9b8f Binary files /dev/null and b/img/javascript-async-iterable-countdown_6150da48.gif differ diff --git a/img/javascript-async-iterator-countdown_fe19a712.gif b/img/javascript-async-iterator-countdown_fe19a712.gif new file mode 100644 index 0000000..12efccb Binary files /dev/null and b/img/javascript-async-iterator-countdown_fe19a712.gif differ diff --git a/img/joe-karlsson-node-js-y4BQLuV3pM-500.png b/img/joe-karlsson-node-js-y4BQLuV3pM-500.png new file mode 100644 index 0000000..8228127 Binary files /dev/null and b/img/joe-karlsson-node-js-y4BQLuV3pM-500.png differ diff --git a/img/joe-karlsson-node-js-y4BQLuV3pM-500.webp b/img/joe-karlsson-node-js-y4BQLuV3pM-500.webp new file mode 100644 index 0000000..bba7b09 Binary files /dev/null and b/img/joe-karlsson-node-js-y4BQLuV3pM-500.webp differ diff --git a/img/joe-karlsson-node-js-y4BQLuV3pM-64.png b/img/joe-karlsson-node-js-y4BQLuV3pM-64.png new file mode 100644 index 0000000..0b91015 Binary files /dev/null and b/img/joe-karlsson-node-js-y4BQLuV3pM-64.png differ diff --git a/img/joe-karlsson-node-js-y4BQLuV3pM-64.webp b/img/joe-karlsson-node-js-y4BQLuV3pM-64.webp new file mode 100644 index 0000000..91e789d Binary files /dev/null and b/img/joe-karlsson-node-js-y4BQLuV3pM-64.webp differ diff --git a/img/kostas-bariotis-node-js-gg10ozMaaW-300.png b/img/kostas-bariotis-node-js-gg10ozMaaW-300.png new file mode 100644 index 0000000..ad5cb42 Binary files /dev/null and b/img/kostas-bariotis-node-js-gg10ozMaaW-300.png differ diff --git a/img/kostas-bariotis-node-js-gg10ozMaaW-300.webp b/img/kostas-bariotis-node-js-gg10ozMaaW-300.webp new file mode 100644 index 0000000..699eb82 Binary files /dev/null and b/img/kostas-bariotis-node-js-gg10ozMaaW-300.webp differ diff --git a/img/kostas-bariotis-node-js-gg10ozMaaW-64.png b/img/kostas-bariotis-node-js-gg10ozMaaW-64.png new file mode 100644 index 0000000..5d54b50 Binary files /dev/null and b/img/kostas-bariotis-node-js-gg10ozMaaW-64.png differ diff --git a/img/kostas-bariotis-node-js-gg10ozMaaW-64.webp b/img/kostas-bariotis-node-js-gg10ozMaaW-64.webp new file mode 100644 index 0000000..435502e Binary files /dev/null and b/img/kostas-bariotis-node-js-gg10ozMaaW-64.webp differ diff --git a/img/luciano-mammino-Xswj6S6N_w-128.png b/img/luciano-mammino-Xswj6S6N_w-128.png new file mode 100644 index 0000000..b816698 Binary files /dev/null and b/img/luciano-mammino-Xswj6S6N_w-128.png differ diff --git a/img/luciano-mammino-Xswj6S6N_w-128.webp b/img/luciano-mammino-Xswj6S6N_w-128.webp new file mode 100644 index 0000000..5379c93 Binary files /dev/null and b/img/luciano-mammino-Xswj6S6N_w-128.webp differ diff --git a/img/luciano-mammino-Xswj6S6N_w-2356.png b/img/luciano-mammino-Xswj6S6N_w-2356.png new file mode 100644 index 0000000..8cd600c Binary files /dev/null and b/img/luciano-mammino-Xswj6S6N_w-2356.png differ diff --git a/img/luciano-mammino-Xswj6S6N_w-2356.webp b/img/luciano-mammino-Xswj6S6N_w-2356.webp new file mode 100644 index 0000000..280b9cf Binary files /dev/null and b/img/luciano-mammino-Xswj6S6N_w-2356.webp differ diff --git a/img/luciano-mammino-Xswj6S6N_w-256.png b/img/luciano-mammino-Xswj6S6N_w-256.png new file mode 100644 index 0000000..5a70cb4 Binary files /dev/null and b/img/luciano-mammino-Xswj6S6N_w-256.png differ diff --git a/img/luciano-mammino-Xswj6S6N_w-256.webp b/img/luciano-mammino-Xswj6S6N_w-256.webp new file mode 100644 index 0000000..a5c96ba Binary files /dev/null and b/img/luciano-mammino-Xswj6S6N_w-256.webp differ diff --git a/img/luciano-mammino-Xswj6S6N_w-64.png b/img/luciano-mammino-Xswj6S6N_w-64.png new file mode 100644 index 0000000..fae5dfd Binary files /dev/null and b/img/luciano-mammino-Xswj6S6N_w-64.png differ diff --git a/img/luciano-mammino-Xswj6S6N_w-64.webp b/img/luciano-mammino-Xswj6S6N_w-64.webp new file mode 100644 index 0000000..4b4e5bd Binary files /dev/null and b/img/luciano-mammino-Xswj6S6N_w-64.webp differ diff --git a/img/man-with-node-js-design-patterns-Yhze0K0jeS-1024.png b/img/man-with-node-js-design-patterns-Yhze0K0jeS-1024.png new file mode 100644 index 0000000..8e35233 Binary files /dev/null and b/img/man-with-node-js-design-patterns-Yhze0K0jeS-1024.png differ diff --git a/img/man-with-node-js-design-patterns-Yhze0K0jeS-1024.webp b/img/man-with-node-js-design-patterns-Yhze0K0jeS-1024.webp new file mode 100644 index 0000000..93302da Binary files /dev/null and b/img/man-with-node-js-design-patterns-Yhze0K0jeS-1024.webp differ diff --git a/img/man-with-node-js-design-patterns-Yhze0K0jeS-128.png b/img/man-with-node-js-design-patterns-Yhze0K0jeS-128.png new file mode 100644 index 0000000..51add4e Binary files /dev/null and b/img/man-with-node-js-design-patterns-Yhze0K0jeS-128.png differ diff --git a/img/man-with-node-js-design-patterns-Yhze0K0jeS-128.webp b/img/man-with-node-js-design-patterns-Yhze0K0jeS-128.webp new file mode 100644 index 0000000..29e5c53 Binary files /dev/null and b/img/man-with-node-js-design-patterns-Yhze0K0jeS-128.webp differ diff --git a/img/man-with-node-js-design-patterns-Yhze0K0jeS-1920.png b/img/man-with-node-js-design-patterns-Yhze0K0jeS-1920.png new file mode 100644 index 0000000..a4b91d6 Binary files /dev/null and b/img/man-with-node-js-design-patterns-Yhze0K0jeS-1920.png differ diff --git a/img/man-with-node-js-design-patterns-Yhze0K0jeS-1920.webp b/img/man-with-node-js-design-patterns-Yhze0K0jeS-1920.webp new file mode 100644 index 0000000..33b63d3 Binary files /dev/null and b/img/man-with-node-js-design-patterns-Yhze0K0jeS-1920.webp differ diff --git a/img/man-with-node-js-design-patterns-Yhze0K0jeS-256.png b/img/man-with-node-js-design-patterns-Yhze0K0jeS-256.png new file mode 100644 index 0000000..2f1333c Binary files /dev/null and b/img/man-with-node-js-design-patterns-Yhze0K0jeS-256.png differ diff --git a/img/man-with-node-js-design-patterns-Yhze0K0jeS-256.webp b/img/man-with-node-js-design-patterns-Yhze0K0jeS-256.webp new file mode 100644 index 0000000..013599d Binary files /dev/null and b/img/man-with-node-js-design-patterns-Yhze0K0jeS-256.webp differ diff --git a/img/man-with-node-js-design-patterns-Yhze0K0jeS-512.png b/img/man-with-node-js-design-patterns-Yhze0K0jeS-512.png new file mode 100644 index 0000000..5c8409c Binary files /dev/null and b/img/man-with-node-js-design-patterns-Yhze0K0jeS-512.png differ diff --git a/img/man-with-node-js-design-patterns-Yhze0K0jeS-512.webp b/img/man-with-node-js-design-patterns-Yhze0K0jeS-512.webp new file mode 100644 index 0000000..ba90d1d Binary files /dev/null and b/img/man-with-node-js-design-patterns-Yhze0K0jeS-512.webp differ diff --git a/img/man-with-node-js-design-patterns-Yhze0K0jeS-64.png b/img/man-with-node-js-design-patterns-Yhze0K0jeS-64.png new file mode 100644 index 0000000..b80bda5 Binary files /dev/null and b/img/man-with-node-js-design-patterns-Yhze0K0jeS-64.png differ diff --git a/img/man-with-node-js-design-patterns-Yhze0K0jeS-64.webp b/img/man-with-node-js-design-patterns-Yhze0K0jeS-64.webp new file mode 100644 index 0000000..e9e3cee Binary files /dev/null and b/img/man-with-node-js-design-patterns-Yhze0K0jeS-64.webp differ diff --git a/img/mario-casciaro-QFNKAaFoOy-128.png b/img/mario-casciaro-QFNKAaFoOy-128.png new file mode 100644 index 0000000..1e9d3bb Binary files /dev/null and b/img/mario-casciaro-QFNKAaFoOy-128.png differ diff --git a/img/mario-casciaro-QFNKAaFoOy-128.webp b/img/mario-casciaro-QFNKAaFoOy-128.webp new file mode 100644 index 0000000..4c90c75 Binary files /dev/null and b/img/mario-casciaro-QFNKAaFoOy-128.webp differ diff --git a/img/mario-casciaro-QFNKAaFoOy-2134.png b/img/mario-casciaro-QFNKAaFoOy-2134.png new file mode 100644 index 0000000..edf88f4 Binary files /dev/null and b/img/mario-casciaro-QFNKAaFoOy-2134.png differ diff --git a/img/mario-casciaro-QFNKAaFoOy-2134.webp b/img/mario-casciaro-QFNKAaFoOy-2134.webp new file mode 100644 index 0000000..1cff100 Binary files /dev/null and b/img/mario-casciaro-QFNKAaFoOy-2134.webp differ diff --git a/img/mario-casciaro-QFNKAaFoOy-256.png b/img/mario-casciaro-QFNKAaFoOy-256.png new file mode 100644 index 0000000..483b764 Binary files /dev/null and b/img/mario-casciaro-QFNKAaFoOy-256.png differ diff --git a/img/mario-casciaro-QFNKAaFoOy-256.webp b/img/mario-casciaro-QFNKAaFoOy-256.webp new file mode 100644 index 0000000..e65e0bd Binary files /dev/null and b/img/mario-casciaro-QFNKAaFoOy-256.webp differ diff --git a/img/mario-casciaro-QFNKAaFoOy-64.png b/img/mario-casciaro-QFNKAaFoOy-64.png new file mode 100644 index 0000000..2522daa Binary files /dev/null and b/img/mario-casciaro-QFNKAaFoOy-64.png differ diff --git a/img/mario-casciaro-QFNKAaFoOy-64.webp b/img/mario-casciaro-QFNKAaFoOy-64.webp new file mode 100644 index 0000000..a43dcce Binary files /dev/null and b/img/mario-casciaro-QFNKAaFoOy-64.webp differ diff --git a/img/maxim-salnikov-node-js--l0QduFKAD-400.png b/img/maxim-salnikov-node-js--l0QduFKAD-400.png new file mode 100644 index 0000000..e717469 Binary files /dev/null and b/img/maxim-salnikov-node-js--l0QduFKAD-400.png differ diff --git a/img/maxim-salnikov-node-js--l0QduFKAD-400.webp b/img/maxim-salnikov-node-js--l0QduFKAD-400.webp new file mode 100644 index 0000000..bd85341 Binary files /dev/null and b/img/maxim-salnikov-node-js--l0QduFKAD-400.webp differ diff --git a/img/maxim-salnikov-node-js--l0QduFKAD-64.png b/img/maxim-salnikov-node-js--l0QduFKAD-64.png new file mode 100644 index 0000000..d2a1c15 Binary files /dev/null and b/img/maxim-salnikov-node-js--l0QduFKAD-64.png differ diff --git a/img/maxim-salnikov-node-js--l0QduFKAD-64.webp b/img/maxim-salnikov-node-js--l0QduFKAD-64.webp new file mode 100644 index 0000000..ea495e8 Binary files /dev/null and b/img/maxim-salnikov-node-js--l0QduFKAD-64.webp differ diff --git a/img/maya-shavin-node-js-RbPRhIJwNi-399.png b/img/maya-shavin-node-js-RbPRhIJwNi-399.png new file mode 100644 index 0000000..095ae7f Binary files /dev/null and b/img/maya-shavin-node-js-RbPRhIJwNi-399.png differ diff --git a/img/maya-shavin-node-js-RbPRhIJwNi-399.webp b/img/maya-shavin-node-js-RbPRhIJwNi-399.webp new file mode 100644 index 0000000..b2ff1e9 Binary files /dev/null and b/img/maya-shavin-node-js-RbPRhIJwNi-399.webp differ diff --git a/img/maya-shavin-node-js-RbPRhIJwNi-64.png b/img/maya-shavin-node-js-RbPRhIJwNi-64.png new file mode 100644 index 0000000..d154dba Binary files /dev/null and b/img/maya-shavin-node-js-RbPRhIJwNi-64.png differ diff --git a/img/maya-shavin-node-js-RbPRhIJwNi-64.webp b/img/maya-shavin-node-js-RbPRhIJwNi-64.webp new file mode 100644 index 0000000..c43203a Binary files /dev/null and b/img/maya-shavin-node-js-RbPRhIJwNi-64.webp differ diff --git a/img/mike-alche-node-js-GitEV3ONbE-400.png b/img/mike-alche-node-js-GitEV3ONbE-400.png new file mode 100644 index 0000000..2a73232 Binary files /dev/null and b/img/mike-alche-node-js-GitEV3ONbE-400.png differ diff --git a/img/mike-alche-node-js-GitEV3ONbE-400.webp b/img/mike-alche-node-js-GitEV3ONbE-400.webp new file mode 100644 index 0000000..9e7b62a Binary files /dev/null and b/img/mike-alche-node-js-GitEV3ONbE-400.webp differ diff --git a/img/mike-alche-node-js-GitEV3ONbE-64.png b/img/mike-alche-node-js-GitEV3ONbE-64.png new file mode 100644 index 0000000..cf87b00 Binary files /dev/null and b/img/mike-alche-node-js-GitEV3ONbE-64.png differ diff --git a/img/mike-alche-node-js-GitEV3ONbE-64.webp b/img/mike-alche-node-js-GitEV3ONbE-64.webp new file mode 100644 index 0000000..8545e9d Binary files /dev/null and b/img/mike-alche-node-js-GitEV3ONbE-64.webp differ diff --git a/img/mike-rourke-node-TsJkWBW1Vi-480.png b/img/mike-rourke-node-TsJkWBW1Vi-480.png new file mode 100644 index 0000000..261e259 Binary files /dev/null and b/img/mike-rourke-node-TsJkWBW1Vi-480.png differ diff --git a/img/mike-rourke-node-TsJkWBW1Vi-480.webp b/img/mike-rourke-node-TsJkWBW1Vi-480.webp new file mode 100644 index 0000000..9644ee7 Binary files /dev/null and b/img/mike-rourke-node-TsJkWBW1Vi-480.webp differ diff --git a/img/mike-rourke-node-TsJkWBW1Vi-64.png b/img/mike-rourke-node-TsJkWBW1Vi-64.png new file mode 100644 index 0000000..eb32d74 Binary files /dev/null and b/img/mike-rourke-node-TsJkWBW1Vi-64.png differ diff --git a/img/mike-rourke-node-TsJkWBW1Vi-64.webp b/img/mike-rourke-node-TsJkWBW1Vi-64.webp new file mode 100644 index 0000000..3c943df Binary files /dev/null and b/img/mike-rourke-node-TsJkWBW1Vi-64.webp differ diff --git a/img/n_ac172e26.gif b/img/n_ac172e26.gif new file mode 100644 index 0000000..4642329 Binary files /dev/null and b/img/n_ac172e26.gif differ diff --git a/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-128.png b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-128.png new file mode 100644 index 0000000..f4df80d Binary files /dev/null and b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-128.png differ diff --git a/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-128.webp b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-128.webp new file mode 100644 index 0000000..397b40f Binary files /dev/null and b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-128.webp differ diff --git a/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-256.png b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-256.png new file mode 100644 index 0000000..77152aa Binary files /dev/null and b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-256.png differ diff --git a/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-256.webp b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-256.webp new file mode 100644 index 0000000..6926fcd Binary files /dev/null and b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-256.webp differ diff --git a/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-512.png b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-512.png new file mode 100644 index 0000000..d4dede9 Binary files /dev/null and b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-512.png differ diff --git a/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-512.webp b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-512.webp new file mode 100644 index 0000000..4c7c909 Binary files /dev/null and b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-512.webp differ diff --git a/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-64.png b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-64.png new file mode 100644 index 0000000..045bcac Binary files /dev/null and b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-64.png differ diff --git a/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-64.webp b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-64.webp new file mode 100644 index 0000000..f59e17e Binary files /dev/null and b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-64.webp differ diff --git a/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-800.png b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-800.png new file mode 100644 index 0000000..a2d72ea Binary files /dev/null and b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-800.png differ diff --git a/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-800.webp b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-800.webp new file mode 100644 index 0000000..e915bdd Binary files /dev/null and b/img/node-js-design-patterns-open-book-with-reactor-patterns-diagrams-HblgoROxOv-800.webp differ diff --git a/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-1024.png b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-1024.png new file mode 100644 index 0000000..336b339 Binary files /dev/null and b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-1024.png differ diff --git a/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-1024.webp b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-1024.webp new file mode 100644 index 0000000..ee377db Binary files /dev/null and b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-1024.webp differ diff --git a/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-1218.png b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-1218.png new file mode 100644 index 0000000..3e2bbae Binary files /dev/null and b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-1218.png differ diff --git a/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-1218.webp b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-1218.webp new file mode 100644 index 0000000..064f179 Binary files /dev/null and b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-1218.webp differ diff --git a/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-128.png b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-128.png new file mode 100644 index 0000000..e10f42b Binary files /dev/null and b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-128.png differ diff --git a/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-128.webp b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-128.webp new file mode 100644 index 0000000..65716e9 Binary files /dev/null and b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-128.webp differ diff --git a/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-256.png b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-256.png new file mode 100644 index 0000000..9baef6c Binary files /dev/null and b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-256.png differ diff --git a/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-256.webp b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-256.webp new file mode 100644 index 0000000..003e130 Binary files /dev/null and b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-256.webp differ diff --git a/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-512.png b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-512.png new file mode 100644 index 0000000..623f04d Binary files /dev/null and b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-512.png differ diff --git a/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-512.webp b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-512.webp new file mode 100644 index 0000000..967c039 Binary files /dev/null and b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-512.webp differ diff --git a/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-64.png b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-64.png new file mode 100644 index 0000000..4ddabf6 Binary files /dev/null and b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-64.png differ diff --git a/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-64.webp b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-64.webp new file mode 100644 index 0000000..0c254a2 Binary files /dev/null and b/img/node-js-design-patterns-open-chapter9-Khc9PnzSAE-64.webp differ diff --git a/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-1090.png b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-1090.png new file mode 100644 index 0000000..57d0236 Binary files /dev/null and b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-1090.png differ diff --git a/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-1090.webp b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-1090.webp new file mode 100644 index 0000000..06dbe65 Binary files /dev/null and b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-1090.webp differ diff --git a/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-128.png b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-128.png new file mode 100644 index 0000000..567e0b2 Binary files /dev/null and b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-128.png differ diff --git a/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-128.webp b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-128.webp new file mode 100644 index 0000000..c85fd5f Binary files /dev/null and b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-128.webp differ diff --git a/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-256.png b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-256.png new file mode 100644 index 0000000..69ff32d Binary files /dev/null and b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-256.png differ diff --git a/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-256.webp b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-256.webp new file mode 100644 index 0000000..e96c0fb Binary files /dev/null and b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-256.webp differ diff --git a/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-512.png b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-512.png new file mode 100644 index 0000000..85a09dd Binary files /dev/null and b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-512.png differ diff --git a/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-512.webp b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-512.webp new file mode 100644 index 0000000..958d837 Binary files /dev/null and b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-512.webp differ diff --git a/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-64.png b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-64.png new file mode 100644 index 0000000..7e63600 Binary files /dev/null and b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-64.png differ diff --git a/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-64.webp b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-64.webp new file mode 100644 index 0000000..1d644c1 Binary files /dev/null and b/img/node-js-design-patterns-print-ebook-kindle-vkBXc6xKKx-64.webp differ diff --git a/img/node-js-design-patterns.jpg b/img/node-js-design-patterns.jpg new file mode 100644 index 0000000..367a3bb Binary files /dev/null and b/img/node-js-design-patterns.jpg differ diff --git a/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-128.png b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-128.png new file mode 100644 index 0000000..ef0db2b Binary files /dev/null and b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-128.png differ diff --git a/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-128.webp b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-128.webp new file mode 100644 index 0000000..64e9af9 Binary files /dev/null and b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-128.webp differ diff --git a/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-256.png b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-256.png new file mode 100644 index 0000000..3d36c83 Binary files /dev/null and b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-256.png differ diff --git a/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-256.webp b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-256.webp new file mode 100644 index 0000000..2802039 Binary files /dev/null and b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-256.webp differ diff --git a/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-512.png b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-512.png new file mode 100644 index 0000000..f6a955c Binary files /dev/null and b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-512.png differ diff --git a/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-512.webp b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-512.webp new file mode 100644 index 0000000..c2110b3 Binary files /dev/null and b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-512.webp differ diff --git a/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-564.png b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-564.png new file mode 100644 index 0000000..5031044 Binary files /dev/null and b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-564.png differ diff --git a/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-564.webp b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-564.webp new file mode 100644 index 0000000..5680f73 Binary files /dev/null and b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-564.webp differ diff --git a/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-64.png b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-64.png new file mode 100644 index 0000000..a1dd19e Binary files /dev/null and b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-64.png differ diff --git a/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-64.webp b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-64.webp new file mode 100644 index 0000000..a34408b Binary files /dev/null and b/img/node-js-hello-world-http-server-from-browser-yCAvls1S5S-64.webp differ diff --git a/img/node-js-macos-installer-screenshot-ceJZLISO1x-128.png b/img/node-js-macos-installer-screenshot-ceJZLISO1x-128.png new file mode 100644 index 0000000..011f632 Binary files /dev/null and b/img/node-js-macos-installer-screenshot-ceJZLISO1x-128.png differ diff --git a/img/node-js-macos-installer-screenshot-ceJZLISO1x-128.webp b/img/node-js-macos-installer-screenshot-ceJZLISO1x-128.webp new file mode 100644 index 0000000..26d497b Binary files /dev/null and b/img/node-js-macos-installer-screenshot-ceJZLISO1x-128.webp differ diff --git a/img/node-js-macos-installer-screenshot-ceJZLISO1x-256.png b/img/node-js-macos-installer-screenshot-ceJZLISO1x-256.png new file mode 100644 index 0000000..a986101 Binary files /dev/null and b/img/node-js-macos-installer-screenshot-ceJZLISO1x-256.png differ diff --git a/img/node-js-macos-installer-screenshot-ceJZLISO1x-256.webp b/img/node-js-macos-installer-screenshot-ceJZLISO1x-256.webp new file mode 100644 index 0000000..1b97b44 Binary files /dev/null and b/img/node-js-macos-installer-screenshot-ceJZLISO1x-256.webp differ diff --git a/img/node-js-macos-installer-screenshot-ceJZLISO1x-512.png b/img/node-js-macos-installer-screenshot-ceJZLISO1x-512.png new file mode 100644 index 0000000..ae084b1 Binary files /dev/null and b/img/node-js-macos-installer-screenshot-ceJZLISO1x-512.png differ diff --git a/img/node-js-macos-installer-screenshot-ceJZLISO1x-512.webp b/img/node-js-macos-installer-screenshot-ceJZLISO1x-512.webp new file mode 100644 index 0000000..917023c Binary files /dev/null and b/img/node-js-macos-installer-screenshot-ceJZLISO1x-512.webp differ diff --git a/img/node-js-macos-installer-screenshot-ceJZLISO1x-64.png b/img/node-js-macos-installer-screenshot-ceJZLISO1x-64.png new file mode 100644 index 0000000..b80d393 Binary files /dev/null and b/img/node-js-macos-installer-screenshot-ceJZLISO1x-64.png differ diff --git a/img/node-js-macos-installer-screenshot-ceJZLISO1x-64.webp b/img/node-js-macos-installer-screenshot-ceJZLISO1x-64.webp new file mode 100644 index 0000000..1bb1d01 Binary files /dev/null and b/img/node-js-macos-installer-screenshot-ceJZLISO1x-64.webp differ diff --git a/img/node-js-macos-installer-screenshot-ceJZLISO1x-732.png b/img/node-js-macos-installer-screenshot-ceJZLISO1x-732.png new file mode 100644 index 0000000..903d527 Binary files /dev/null and b/img/node-js-macos-installer-screenshot-ceJZLISO1x-732.png differ diff --git a/img/node-js-macos-installer-screenshot-ceJZLISO1x-732.webp b/img/node-js-macos-installer-screenshot-ceJZLISO1x-732.webp new file mode 100644 index 0000000..20187d6 Binary files /dev/null and b/img/node-js-macos-installer-screenshot-ceJZLISO1x-732.webp differ diff --git a/img/node-js-macos-precompiled-binary-XVV50zv_zZ-1077.png b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-1077.png new file mode 100644 index 0000000..22011eb Binary files /dev/null and b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-1077.png differ diff --git a/img/node-js-macos-precompiled-binary-XVV50zv_zZ-1077.webp b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-1077.webp new file mode 100644 index 0000000..b70b0df Binary files /dev/null and b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-1077.webp differ diff --git a/img/node-js-macos-precompiled-binary-XVV50zv_zZ-128.png b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-128.png new file mode 100644 index 0000000..dc5898e Binary files /dev/null and b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-128.png differ diff --git a/img/node-js-macos-precompiled-binary-XVV50zv_zZ-128.webp b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-128.webp new file mode 100644 index 0000000..b94b0a2 Binary files /dev/null and b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-128.webp differ diff --git a/img/node-js-macos-precompiled-binary-XVV50zv_zZ-256.png b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-256.png new file mode 100644 index 0000000..ecddcb7 Binary files /dev/null and b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-256.png differ diff --git a/img/node-js-macos-precompiled-binary-XVV50zv_zZ-256.webp b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-256.webp new file mode 100644 index 0000000..551daad Binary files /dev/null and b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-256.webp differ diff --git a/img/node-js-macos-precompiled-binary-XVV50zv_zZ-512.png b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-512.png new file mode 100644 index 0000000..1f33528 Binary files /dev/null and b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-512.png differ diff --git a/img/node-js-macos-precompiled-binary-XVV50zv_zZ-512.webp b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-512.webp new file mode 100644 index 0000000..6b5cae0 Binary files /dev/null and b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-512.webp differ diff --git a/img/node-js-macos-precompiled-binary-XVV50zv_zZ-64.png b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-64.png new file mode 100644 index 0000000..8ff851e Binary files /dev/null and b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-64.png differ diff --git a/img/node-js-macos-precompiled-binary-XVV50zv_zZ-64.webp b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-64.webp new file mode 100644 index 0000000..0f5b5d5 Binary files /dev/null and b/img/node-js-macos-precompiled-binary-XVV50zv_zZ-64.webp differ diff --git a/img/node-repl-with-docker_edc66675.gif b/img/node-repl-with-docker_edc66675.gif new file mode 100644 index 0000000..6f0b8a5 Binary files /dev/null and b/img/node-repl-with-docker_edc66675.gif differ diff --git a/img/nodejs-release-schedule_9b4bf060.svg b/img/nodejs-release-schedule_9b4bf060.svg new file mode 100644 index 0000000..5601275 --- /dev/null +++ b/img/nodejs-release-schedule_9b4bf060.svg @@ -0,0 +1 @@ +Oct 2020Jan 2021Apr 2021Jul 2021Oct 2021Jan 2022Apr 2022Jul 2022Oct 2022Jan 2023MasterNode.js 10Node.js 12Node.js 14Node.js 15Node.js 16unstablemaintenancemaintenanceactivemaintenanceactivecurrentactivecurrent \ No newline at end of file diff --git a/img/poll-results-M4yStsOXME-128.png b/img/poll-results-M4yStsOXME-128.png new file mode 100644 index 0000000..1744dac Binary files /dev/null and b/img/poll-results-M4yStsOXME-128.png differ diff --git a/img/poll-results-M4yStsOXME-128.webp b/img/poll-results-M4yStsOXME-128.webp new file mode 100644 index 0000000..857507a Binary files /dev/null and b/img/poll-results-M4yStsOXME-128.webp differ diff --git a/img/poll-results-M4yStsOXME-256.png b/img/poll-results-M4yStsOXME-256.png new file mode 100644 index 0000000..e66e04c Binary files /dev/null and b/img/poll-results-M4yStsOXME-256.png differ diff --git a/img/poll-results-M4yStsOXME-256.webp b/img/poll-results-M4yStsOXME-256.webp new file mode 100644 index 0000000..af3935b Binary files /dev/null and b/img/poll-results-M4yStsOXME-256.webp differ diff --git a/img/poll-results-M4yStsOXME-512.png b/img/poll-results-M4yStsOXME-512.png new file mode 100644 index 0000000..31d8b8e Binary files /dev/null and b/img/poll-results-M4yStsOXME-512.png differ diff --git a/img/poll-results-M4yStsOXME-512.webp b/img/poll-results-M4yStsOXME-512.webp new file mode 100644 index 0000000..d82d240 Binary files /dev/null and b/img/poll-results-M4yStsOXME-512.webp differ diff --git a/img/poll-results-M4yStsOXME-633.png b/img/poll-results-M4yStsOXME-633.png new file mode 100644 index 0000000..5ee86fa Binary files /dev/null and b/img/poll-results-M4yStsOXME-633.png differ diff --git a/img/poll-results-M4yStsOXME-633.webp b/img/poll-results-M4yStsOXME-633.webp new file mode 100644 index 0000000..244fe1f Binary files /dev/null and b/img/poll-results-M4yStsOXME-633.webp differ diff --git a/img/poll-results-M4yStsOXME-64.png b/img/poll-results-M4yStsOXME-64.png new file mode 100644 index 0000000..60eccd1 Binary files /dev/null and b/img/poll-results-M4yStsOXME-64.png differ diff --git a/img/poll-results-M4yStsOXME-64.webp b/img/poll-results-M4yStsOXME-64.webp new file mode 100644 index 0000000..1cb449c Binary files /dev/null and b/img/poll-results-M4yStsOXME-64.webp differ diff --git a/img/radoslav-stankov-node-js-rLfrqtUu-9-400.png b/img/radoslav-stankov-node-js-rLfrqtUu-9-400.png new file mode 100644 index 0000000..d7db6a3 Binary files /dev/null and b/img/radoslav-stankov-node-js-rLfrqtUu-9-400.png differ diff --git a/img/radoslav-stankov-node-js-rLfrqtUu-9-400.webp b/img/radoslav-stankov-node-js-rLfrqtUu-9-400.webp new file mode 100644 index 0000000..4f699e9 Binary files /dev/null and b/img/radoslav-stankov-node-js-rLfrqtUu-9-400.webp differ diff --git a/img/radoslav-stankov-node-js-rLfrqtUu-9-64.png b/img/radoslav-stankov-node-js-rLfrqtUu-9-64.png new file mode 100644 index 0000000..be2e0f0 Binary files /dev/null and b/img/radoslav-stankov-node-js-rLfrqtUu-9-64.png differ diff --git a/img/radoslav-stankov-node-js-rLfrqtUu-9-64.webp b/img/radoslav-stankov-node-js-rLfrqtUu-9-64.webp new file mode 100644 index 0000000..df6440a Binary files /dev/null and b/img/radoslav-stankov-node-js-rLfrqtUu-9-64.webp differ diff --git a/img/simon-hoiberg-node-js-axY8rfRZhD-400.png b/img/simon-hoiberg-node-js-axY8rfRZhD-400.png new file mode 100644 index 0000000..36b2f62 Binary files /dev/null and b/img/simon-hoiberg-node-js-axY8rfRZhD-400.png differ diff --git a/img/simon-hoiberg-node-js-axY8rfRZhD-400.webp b/img/simon-hoiberg-node-js-axY8rfRZhD-400.webp new file mode 100644 index 0000000..bc92786 Binary files /dev/null and b/img/simon-hoiberg-node-js-axY8rfRZhD-400.webp differ diff --git a/img/simon-hoiberg-node-js-axY8rfRZhD-64.png b/img/simon-hoiberg-node-js-axY8rfRZhD-64.png new file mode 100644 index 0000000..7e0703d Binary files /dev/null and b/img/simon-hoiberg-node-js-axY8rfRZhD-64.png differ diff --git a/img/simon-hoiberg-node-js-axY8rfRZhD-64.webp b/img/simon-hoiberg-node-js-axY8rfRZhD-64.webp new file mode 100644 index 0000000..89faf29 Binary files /dev/null and b/img/simon-hoiberg-node-js-axY8rfRZhD-64.webp differ diff --git a/img/theodore-vorillas-node-js-E5NjOdFsCM-400.png b/img/theodore-vorillas-node-js-E5NjOdFsCM-400.png new file mode 100644 index 0000000..9f70a5b Binary files /dev/null and b/img/theodore-vorillas-node-js-E5NjOdFsCM-400.png differ diff --git a/img/theodore-vorillas-node-js-E5NjOdFsCM-400.webp b/img/theodore-vorillas-node-js-E5NjOdFsCM-400.webp new file mode 100644 index 0000000..9f9d7bd Binary files /dev/null and b/img/theodore-vorillas-node-js-E5NjOdFsCM-400.webp differ diff --git a/img/theodore-vorillas-node-js-E5NjOdFsCM-64.png b/img/theodore-vorillas-node-js-E5NjOdFsCM-64.png new file mode 100644 index 0000000..bdbfe6c Binary files /dev/null and b/img/theodore-vorillas-node-js-E5NjOdFsCM-64.png differ diff --git a/img/theodore-vorillas-node-js-E5NjOdFsCM-64.webp b/img/theodore-vorillas-node-js-E5NjOdFsCM-64.webp new file mode 100644 index 0000000..b0d29ba Binary files /dev/null and b/img/theodore-vorillas-node-js-E5NjOdFsCM-64.webp differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..699ae8d --- /dev/null +++ b/index.html @@ -0,0 +1,593 @@ +Node.js Design Patterns Third Edition by Mario Casciaro and Luciano Mammino

            Node.js Design Patterns: the ultimate guide to becoming a Node.js expert

            The top-tier book for Node.js that will guide you from A to Z through the design and implementation of production-grade Node.js applications with tested patterns and techniques.

            Node.js Design Patterns book cover

            The most practical and comprehensive Node.js book on the market

            150 Working examples & 50 exercises

            Put into practice everything you learn. Step-by-step explanation of code samples and engaging coding challenges at the end of every chapter.

            13 Exquisitely crafted chapters

            From the basics of the Node.js architecture to how to scale and distribute your application.

            660 Pages packed with knowledge

            In-depth explanations and examples, so that even if you are a novice you can follow and immediately apply advanced techniques.

            Looking for a reliable source to learn how to create extensible modern web applications?

            Node.js Design Patterns book cover

            If you are here, you have probably wandered a lot around the internet to find a valuable source to deepen your Node.js knowledge.

            Maybe you have already used Node.js but you feel like you have not yet fully understood its key concepts.

            You have read articles, and watched videos and webinars, but none of them have offered you a Node.js learning experience that seems complete.

            What if we tell you that after a long search, you have finally landed on the right page?

            You don't need to search for different sources and then put them together to obtain a detailed guide.

            You need a comprehensive manual with TESTED content that guides you from A to Z in becoming a Node.js expert using both theoretical knowledge and practical exercises.

            A Complete book that makes you confident with implementing scalable Node.js applications

            Are you keen to understand how Node.js works under the hood?

            A guide that teaches you all the best practices that you need to know to grow your career.

            You will finally understand how JavaScript and Node.js work and how to build performant and scalabale web applications.

            You will find clear writing, diagrams, and real-world code examples, taking a deep dive into projects using technologies like Redis or Fastify.

            All this will be possible thanks to Node.js Design Patterns: the first book that covers all the Node.js topics that you need to become a professional.

            From theory to practice, from real-world exercises to useful best practices.

            Start your journey to become a Node.js pro now!

            Node.js Design Patterns book cover

            More than just Design Patterns: a book with Everything you need to know about Node.js

            13 chapters carefully crafted to explore and master various aspects of Node.js professional development

            • Explore the basics of Node.js, analyzing its asynchronous event-driven architectures and design patterns (including event emitters, callbacks, promises and async/await);
            • Dive into Node.js streams and some of the most famous Gang of Four design patterns reinterpreted in the context of Node.js plus some novel patterns that are unique to JavaScript and Node.js;
            • Explore advanced topics such as Universal JavaScript with Node.js, React and Webpack;
            • Discover best practices to scale Node.js services, microservices and messaging patterns for enterprise-grade distributed applications;

            What you will find in the book:

            1. The Node.js Platform

              Learn about the Node.js philosophy, the reactor patterns and the differences between JavaScript on the browser and Node.js on the server.

            2. The Module System

              Learn how to leverage the powerful Node.js module system and discover the main differences between CommonJS and ESM.

            3. Callbacks and Events

              Discover the callback pattern, how it works and the conventions used in Node.js. Learn how to avoid pitfalls and when to take advantage of the observer pattern using Node.js built-in event emitter.

            4. Asynchronous Control Flow Patterns with Callbacks

              Lean how to avoid callback hell and explore common asynchronous patterns such as sequential execution, sequential iteration, parallel execution and limited parallel execution.

            5. Asynchronous Control Flow Patterns with Promises and Async/Await

              Find out how promises work and how to use them effectively to implement various asynchronous control flow patterns. Explore the modern async/await syntax, the main tool today for dealing with asynchronous code in Node.js

            6. Coding with Streams

              Understand why streams are so important in Node.js. Learn how to use standard streams and how to create custom ones. Explore various streaming patterns and learn how to build powerful streaming pipelines.

            7. Creational Design Patterns

              Learn about the most famous creational design patterns in Node.js: the Factory pattern, the Revealing Constructor pattern, the Builder pattern. Finally, explore the Singleton pattern and the Dependency Injection pattern.

            8. Structural Design Patterns

              Discover how to implement and use the Proxy, the Decorator and the Adapter pattern in Node.js.

            9. Behavioural Design Patterns

              Learn how to implement and leverage some of the most well known behavioural design patterns in the context of Node.js: the Strategy pattern, the State pattern, the Template pattern, the Iterator pattern, the Middleware pattern, and the Command pattern.

              Free Chapter!

            10. Universal JavaScript

              Explore the fundamentals of JavaScript cross-platform development and learn how to share code between the browser and Node.js. Learn how to leverage React.js to build a complete universal JavaScript application.

            11. Advanced Recipes

              Discover well-known recipes to deal with some more advanced Node.js intricacies such as dealing with asynchronously initialized components, performing asynchronous request batching and caching, canceling asynchronous operations and running CPU-bound tasks.

            12. Scalability and Architectural Patterns

              Master the art of Node.js scalability by learning about the "Scale Cube", discover how to run multiple instances of the same application and how to use load balancers and service registers. Learn how to use containers and containers orchestration platforms such as Kubernetes. Finally, find out how to design and build microservices architectures.

            13. Messaging and Integration Patterns

              Learn how to integrate complex distributed Node.js applications using the most popular messaging systems. Learn how to implement the most common messaging patterns on top of ZeroMQ, RabbitMQ and Redis Streams.

            What are the Benefits of choosing Node.js Design Patterns?

            Get a 360° knowledge of the Node.js ecosystem

            660 pages packed with deep knowledge of Node.js from a theoretical and practical point of view, to become a Node.js pro and apply what you discover to real-world projects.

            Thoroughly test your understanding

            You will find practical exercises at the end of each chapter to test what you have previously learnt with the theory.

            Join a thriving Node.js dev community

            Gain access to an active community on GitHub, where other developers who are reading the book share ideas, comments, and correct exercises together.

            Meet the authors

            Meet Mario and Luciano, two passionate software engineers with a shared love for Node.js and more than 30 years of collective experience on the field!

            Mario Casciaro

            Mario Casciaro's picture

            Mario is currently the CTO of D4H Technologies, where he creates software for emergency management. In the past, Mario has worked at IBM as a team lead and also founded a couple of software companies.

            Luciano Mammino

            Luciano Mammino's picture

            Luciano is a Senior Architect at fourTheorem where he is helping companies to get the best out of the Cloud and AWS. He is an active speaker and in the last few years he has delivered more than 130 talks in conferences and meetups around the World.

            What the experts say

            Joe Karlsson's profile picture

            Joe Karlsson — Developer Advocate at Tinybird

            I've read basically every Node.js book ever published, and this is my personal favorite (and best) by far. Even months after reading all the way through this text, I still learn new things each time I browse through it. What can I say, this is a must for anyone writing code in Node. js. Everything in the book, including the code examples, are very useful in practice.

            Gleb Bahmutov's profile picture

            Gleb Bahmutov — Senior Director Of Engineering at Mercari

            Wow! This book ... is amazing. After many years of programming with JavaScript, I still have learnt so much from this book. It covers many topics relevant to large Node.js applications with ease, with many relevant code snippets making the material easily approachable. While not for beginners, it is a great book for anyone looking to take their JS applications to the higher level.

            Radoslav Stankov's profile picture

            Radoslav Stankov — CTO and Co-Founder at Angry Building

            Node.js Design Patterns is an excellent resource for learning. I learned a lot of things that I thought I already knew. My favorite part is the Commonjs and ES6 import systems and a lot of the more advanced recipes.

            Ire Aderinokun's profile picture

            Ire Aderinokun — Frontend Engineer, Entrepreneur, Investor

            Node.js Design Patterns is a really in-depth look into the world of Node.js. Everything you ever wanted to know, and more! It’s very comprehensive, and gets into the details of how Node.js works. It’s a perfect book for anyone who already knows a bit about Node.js, and is looking to deepen their knowledge.

            Ersel Aker's profile picture

            Ersel Aker — Co-Founder at futurecast.studio

            Read through a few chapters of Node.js Design Patterns this morning. It's an excellent reference book covering everything from from the intricacies of the Node.js runtime to scaling your services to serve millions of users. Grabbed another copy for our engineering team to use as a study tool.

            Mike Rourke's profile picture

            Mike Rourke — Software Engineer at a.i. solutions and Author

            There is a lot of useful content in this book, and I think the title doesn't do it justice. If you want a comprehensive book about Node.js that not only acts as a reference for best practices and design patterns, but also as a one-stop shop for understanding how Node.js works behind the scenes, look no further!

            Maya Shavin's profile picture

            Maya Shavin — Senior Software Engineer at Microsoft and Author

            This book surprised me for how detailed it is! It contains a lot of useful and easy-to-understand information. For someone familiar with the Node.js, I'm intrigued by how much depth the book provides in working with Node.js. Every topic is approached in a very straightforward way: while being practical, this book offers different readers' levels - from the most juniors to the experienced ones - an excellent reference to apply design patterns in any real scale Node.js application. Totally going to bookmark it as my favorite Node.js book!

            Kostas Bariotis's profile picture

            Kostas Bariotis — Sr. Software Engineer at Mixmax

            Node.js Design Patterns is an amazing resource for every level of Node.js developers. It covers important design patters that are used in the Node.js world (modules, callbacks, asynchronicity, etc) and explores critical bits of the core library (promises, streams, event emitters, etc). Finally, it provides best practices on running Node.js as part of a bigger architecture where you need to horizontally scale an application, balance the incoming load and setup a message bus in between your services.

            David Wells's profile picture

            David Wells — Full Stack Engineer at Vendia

            If you want a deep dive into how Node.js works and the available design patterns, I'd recommend checking out Mario and Luciano's new book. I particularly enjoyed the section on using streams. After reading, I realized haven't been leveraging streams enough in my projects.

            Simon HĂžiberg's profile picture

            Simon Hþiberg — Software Developer and Startup CEO

            Node.js Design Patterns is a great, in-depth resource for both beginners and advanced. Even though I've been writing Node.js applications for the past 5 years, I learned a lot from this comprehensive piece of work. By far the best Node.js book currently out there.

            Mike Alche's profile picture

            Mike Alche — React, React Native & Node.js software consultant

            Every time a new Node.js book comes out I read it. I think there isn’t a single one that I haven’t read. And out of all of them I must say that Node.js Design Patterns is by far — and I mean it BY FAR— the undisputed best. To put it in the most honest way possible: this is the book I go to read when preparing to teach a Node.js class to groups of software engineers.

            Theodore Vorillas's profile picture

            Theodore Vorillas — Independent Software Engineer and JavaScript expert

            Node.js Design Patterns is a must read, an excellent resource for learning how to build Node.js applications. I loved the fact that you can actually learn new stuff regardless your experience. My favorite chapters are about scaling a production application (Universal JavaScript, Scalability and Architectural Patterns, Advanced Recipes)... and the coding samples are also available for free on GitHub!

            David Gonzalez's profile picture

            David Gonzalez — Principal Engineer at Cloudsmith and author

            I am a hardcore techie, big into Node.js and I have to say I have learnt a lot by just going through Node.js Design Patterns. The level of detail and quality of the examples are incredible and in general, it is a good book have as a reference to solve any problem that might arise while code (not only in Node.js). This is a book I recommend to my students to go from 0 to hero and keep it as a reference forever. The previous edition was good, but this one is just out of this world.

            Maxim Salnikov's profile picture

            Maxim Salnikov — Developer Productivity Business Lead at Microsoft

            I have "Node.js Design Patterns" always at hand both when I start quick new projects and when the time comes to refactor the larger and mature ones. It's an invaluable and well-structured source of patterns, best practices, and guidelines. Also, it’s always good to know that particular features are in use in well-known projects - thanks to the “In the wild” section. As a full-stack developer focused on the front-end, I especially enjoyed a chapter about universal JavaScript.

            Who is Node.js Design Patterns for?

            • For those who already know the basics of the JavaScript language and want to become a Node.js professional
            • For those who want to understand how the event loop works and learn to use Node.js to its full potential without errors and in the most efficient way
            • For those who want to learn how to adapt classic design patterns to Node.js and discover Node.js specific design patterns
            • For those who want to learn how to use production ready tools such as LevelDB, Redis, RabbitMQ, ZeroMQ in the Node.js context to develop real applications that can scale to millions of users

            Why shouldn't you Miss this book?

            Much more than just a textbook!

            Node.js Design Patterns is a specific manual covering Node.js topics from A to Z.

            You will have the opportunity of applying what you are learning with lots of exercises.

            It is much more practical than a regular manual, just to make sure you understand actual market needs.

            You will also have the possibility of connecting with an entire developers community on GitHub and the authors.

            Node.js Design Patterns. open book showing some of the diagrams about the reactor pattern

            Are you ready to take your Node.js knowledge to the next level?

            Mario Casciaro's picture

            Developers' Favorite book to learn Node.js like a Pro

            Node.js Design Patterns is the first book on the market that can help you become a Node.js professional not just with theory, but also with practical exercises, best practices and design patterns.

            Rated 4.7 with 250+ reviews on Amazon

            Extremely helpful

            From the title, the book sounded like it would be exactly what I needed and I am happy to say it is. There are always several ways of doing things and the pitfalls/best practices (and reasons behind them) are not always immediately obvious. This book gives you the tools to reason about which pattern is going to be best for a certain task, which is so helpful. I really appreciate the quality of the resource. They also provide exercises at the end of the chapter which I think is super helpful for reinforcing concepts and keeping things interactive.

            — Rebecca

            Highly recommend to add this to your toolkit if you're developing Node.js applications

            I've been developing for Node.js for a number of years, and this book helps me to understand concepts and things under the hood I’ve never had a chance to explore, all in a single source. I’m amazed that design patterns are covered with JS codes, as it shows me how to design and architect Node applications better to run at scale. Another thing I love is the hands-on aspects in guiding me to set up and run production-grade services such as task distribution, callbacks, concurrency and streams. Highly recommend to add this to your toolkit if you're developing Node.js applications.

            — James Ma

            Clean and well structured

            This book gets straight to the point, and there are many practical examples. You can find fundamentals and more advanced notions. Each chapter is well designed and the presentation of topics follows a well-structured logic.

            — Valerio

            Deep knowledge about Node.js

            I found this book very useful and knowledgeable. Great and clean up to date content. You can either read it in order or use it as a reference. Love it. Thank you

            — Or Hasson

            Excelent content

            I am liking the book, it explains a lot of low level concepts of Node.js and the part of design patterns shows great exemples how to use the pattern explained, instead of just theory. Worth the purchase. I really recommend.

            — Renan Truppel Ayoub

            Wonderful book, the best for Node.js. A book to have in your library

            A really well structured book, with clear and precise examples, aimed at explaining the concepts set out. Very complete and comprehensive, it covers practically all aspects and approaches of Node. Great Job for Mario and Luciano.

            — Massimiliano C.

            Indispensable

            A book that I always recommend to junior developers or those who want to improve their skills with Node. Well structured and with the necessary content to understand how to work professionally with this language.

            — Juan

            Deep dive into Node.js, JavaScript style

            An excellent book that covers many topics, including design patterns, with every example in JavaScript. The author does a great job of explaining each subject with multiple examples refactoring each into the optimal solution.

            — NeoModulus

            FAQs

            This book is for developers and software architects who have some prior knowledge of JavaScript and Node.js and now want to get the most out of these technologies in terms of productivity, design quality, and scalability. Software professionals with intermediate experience in Node.js and JavaScript will also find valuable the more advanced patterns and techniques presented in this book.

            This book assumes that you have an intermediate understanding of JavaScript, web application development, databases, and software design principles. If you are new to JavaScript but you are familiar with other languages and technologies this book can still be a valuable resource for you but we do recommend you to get familiar with the basics of JavaScript first. Some good FREE resources you could check out are Eloquent JavaScript, JavaScript introduction by W3C and JavaScript in 30 days.

            This book will get you comfortable with writing asynchronous code by leveraging callbacks, promises, and the async/await syntax. It will teach you how to leverage Node.js streams to create data-driven asynchronous processing pipelines, how to implement well-known software design patterns to create production grade applications, and how to share code between Node.js and the browser, taking full advantage of full-stack JavaScript. Finally, this book will show you how to build and scale microservices and distributed systems powered by Node.js and how to use Node.js in conjunction with other powerful technologies such as Redis, RabbitMQ, ZeroMQ, and LevelDB.

            No. Node.js Design Patterns is the result of countless hours of hard work by the authors, the reviewers, and many professionals at Packt, so we simply can't afford to give it away for free. If you want to have a feeling about the quality of the content in the book, you can download Chapter 9: Behavioural Design Patterns for FREE. You can also check out for FREE all the code examples available in the book on the official GitHub repository.

            The book has 13 chapters totaling about 660 pages.

            The book was written to give you continuity throughout the chapters so that you can enjoy reading the book from the first to the very last page. Although, if you are already familiar with many Node.js concepts and design patterns, you can easily skim through the chapters and focus only on the content that matters the most to you. Check out the Table of Contents if you want to have a better feeling on how the book is structured.

            The third edition of this book (released in July 2020) has been updated to cover the features of the most recent Node.js LTS version (Node.js 14). Node.js Design Patterns (third edition) leverages modern best practices and Node.js features such as Async/Await and EcmaScript Modules (ESM).

            All the code examples contained in the book are available on GitHub and can be accessed for FREE.

            Node.js Design Patterns provides a mix of simple examples and real-life applications to gradually introduce you to new topics and patterns. Some examples of real-life applications are:

            • Your own module loader
            • A website spider and downloader
            • A streaming file archive pipeline
            • A plugin for LevelUp
            • A full stack universal JavaScript website with Fastify, React and Webpack
            • A dynamic HTTP load balancer using Consul
            • A peer-to-peer network for web services
            • A distributed real-time chat application with an interactive web UI which leverages ZeroMQ, RabbitMQ and Redis Streams
            • A worker pool for background job executions using ZeroMQ, RabbitMQ and Redis Streams

            You can checkout the source code for these examples for FREE on GitHub.

            Every chapter comes with a set of engaging coding challenges and exercises crafted to put your new learnings into practice. Node.js Design Patterns contains 50 exercises in total.

            You are more than welcome to reach out to the authors on X (@mariocasciaro and @loige) or you can write an email to authors@nodejsdesignpatterns.com.

            Still in doubt?

            We know there's tons of material about Node.js on the internet. And some of the available content is actually good.

            We just want to make sure you know that Node.js Design Patterns is the most complete book about Node.js available right now to help you become a professional.

            Maybe you are not sure if the content will be clear and useful enough for your needs?

            This is why we want to give you a gift
 A free chapter from “Node.js Design patterns” to help you make an even more informed purchase decision.

            54 pages to learn how to implement and leverage some of the most well known behavioural design patterns in the context of Node.js: Strategy, State, Template, Iterator, Middleware, and Command Pattern.

              Node.js Design Patterns chapter 9 behavioral design patterns
              \ No newline at end of file diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..24b85c0 --- /dev/null +++ b/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Allow: / +Sitemap: https://www.nodejsdesignpatterns.com/sitemap.xml \ No newline at end of file diff --git a/site.webmanifest b/site.webmanifest new file mode 100755 index 0000000..5361858 --- /dev/null +++ b/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "Node.js Design Patterns - Book website", + "short_name": "nodejsdesignpatterns.com", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..37d7b2a --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,53 @@ + + + + + https://www.nodejsdesignpatterns.com/blog/5-ways-to-install-node-js/ + 2020-12-24 + monthly + 0.8 + + + + https://www.nodejsdesignpatterns.com/blog/node-js-race-conditions/ + 2021-01-24 + monthly + 0.8 + + + + https://www.nodejsdesignpatterns.com/blog/node-js-development-with-docker-and-docker-compose/ + 2021-04-07 + monthly + 0.8 + + + + https://www.nodejsdesignpatterns.com/blog/javascript-async-iterators/ + 2021-05-04 + monthly + 0.8 + + + + https://www.nodejsdesignpatterns.com/blog/node-js-stream-consumer/ + 2022-03-11 + monthly + 0.8 + + + + https://www.nodejsdesignpatterns.com/blog/ + 2024-01-05 + monthly + 0.8 + + + + https://www.nodejsdesignpatterns.com/ + 2024-01-05 + monthly + 0.8 + + + \ No newline at end of file diff --git a/static/giuseppe-morelli-avatar-small.jpg b/static/giuseppe-morelli-avatar-small.jpg new file mode 100644 index 0000000..ace4ca9 Binary files /dev/null and b/static/giuseppe-morelli-avatar-small.jpg differ diff --git a/static/luciano-mammino-avatar-small.jpg b/static/luciano-mammino-avatar-small.jpg new file mode 100644 index 0000000..07c5350 Binary files /dev/null and b/static/luciano-mammino-avatar-small.jpg differ diff --git a/static/mario-casciaro-avatar-small.png b/static/mario-casciaro-avatar-small.png new file mode 100644 index 0000000..c118819 Binary files /dev/null and b/static/mario-casciaro-avatar-small.png differ