diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.nojekyll @@ -0,0 +1 @@ + diff --git a/404.html b/404.html new file mode 100644 index 0000000..ea3439f --- /dev/null +++ b/404.html @@ -0,0 +1 @@ + 404: Page not found | Phyo's Log
404: Page not found
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ffb205f --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +default: install + +h help: + @grep '^[a-z]' Makefile + +install: + bundle config set --local path vendor/bundle + bundle + +s serve: + bundle exec jekyll serve diff --git a/about/index.html b/about/index.html new file mode 100644 index 0000000..42e2f06 --- /dev/null +++ b/about/index.html @@ -0,0 +1 @@ + About | Phyo's Log
About

About

My picture

Hi, my name is Phyo. I am passionate about tech and outdoor adventures. I am hoping to consolidate all my random notes and thoughts under this blog. Thanks for visiting!

diff --git a/app.js b/app.js new file mode 100644 index 0000000..c6d1832 --- /dev/null +++ b/app.js @@ -0,0 +1 @@ +const $notification = $('#notification'); const $btnRefresh = $('#notification .toast-body>button'); if ('serviceWorker' in navigator) { /* Registering Service Worker */ navigator.serviceWorker.register('/sw.js') .then(registration => { /* in case the user ignores the notification */ if (registration.waiting) { $notification.toast('show'); } registration.addEventListener('updatefound', () => { registration.installing.addEventListener('statechange', () => { if (registration.waiting) { if (navigator.serviceWorker.controller) { $notification.toast('show'); } } }); }); $btnRefresh.click(() => { if (registration.waiting) { registration.waiting.postMessage('SKIP_WAITING'); } $notification.toast('hide'); }); }); let refreshing = false; /* Detect controller change and refresh all the opened tabs */ navigator.serviceWorker.addEventListener('controllerchange', () => { if (!refreshing) { window.location.reload(); refreshing = true; } }); } diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 0000000..50a022f --- /dev/null +++ b/archives/index.html @@ -0,0 +1 @@ + Archives | Phyo's Log
Archives
diff --git a/assets/css/jekyll-theme-chirpy.css b/assets/css/jekyll-theme-chirpy.css new file mode 100644 index 0000000..b820df7 --- /dev/null +++ b/assets/css/jekyll-theme-chirpy.css @@ -0,0 +1 @@ +#search-results a,h5,h4,h3,h2,h1{color:var(--heading-color);font-weight:400;font-family:Lato,"Microsoft Yahei",sans-serif}main h5,main h4,main h3,main h2{margin-top:2.5rem;margin-bottom:1.25rem}main h5:focus,main h4:focus,main h3:focus,main h2:focus{outline:none}h5 .anchor,h4 .anchor,h3 .anchor,h2 .anchor{font-size:80%}@media(hover: hover){h5 .anchor,h4 .anchor,h3 .anchor,h2 .anchor{visibility:hidden;opacity:0;transition:opacity .25s ease-in,visibility 0s ease-in .25s}h5:hover .anchor,h4:hover .anchor,h3:hover .anchor,h2:hover .anchor{visibility:visible;opacity:1;transition:opacity .25s ease-in,visibility 0s ease-in 0s}}.post-tags .post-tag:hover,.tag:hover{background:var(--tag-hover);transition:background .35s ease-in-out}.table-wrapper>table tbody tr td,.table-wrapper>table thead th{padding:.4rem 1rem;font-size:95%;white-space:nowrap}#page-category a:hover,#page-tag a:hover,.post-tags .post-tag:hover,.post-tail-wrapper .license-wrapper>a:hover,#search-results a:hover,#topbar #breadcrumb a:hover,.content a:not(.img-link):hover,.post-meta a:not([class]):hover,#access-lastmod a:hover,footer a:hover{color:#d2603a !important;border-bottom:1px solid #d2603a;text-decoration:none}#search-results a,#search-hints .post-tag,a{color:var(--link-color)}.post-tail-wrapper .post-meta a:not(:hover),.content a:not(.img-link){border-bottom:1px solid var(--link-underline-color)}#sidebar .sidebar-bottom a,#sidebar .site-title a,#sidebar .profile-wrapper{transition:all .3s ease-in-out}#sidebar .sidebar-bottom .icon-border,.content a.popup,i.far,i.fas,.code-header{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#page-category ul>li>a,#page-tag ul>li>a,.post-tags .post-tag:hover,#search-results a,main .categories a:not(:hover),main #tags a:not(:hover),main #archives a:not(:hover),#access-lastmod a{border-bottom:none}.post-tail-wrapper .share-wrapper .share-icons button,#search-cancel,.code-header button{cursor:pointer}#related-posts time,#post-list .card .card-body .post-meta em,.post-meta em{font-style:normal}.categories.card,.categories .list-group,.embed-video,.post-preview::before,.post-preview,.preview-img img,.preview-img,blockquote[class^=prompt-],.code-header button,div[class^=language-],.highlight{border-radius:.625rem}.content a.popup+em{display:block;text-align:center;font-style:normal;font-size:80%;padding:0;color:#6d6c6c}#sidebar .sidebar-bottom .mode-toggle,#sidebar a{color:var(--sidebar-muted-color);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#related-posts .card h4,#post-list .card .card-body .card-text.content p,#post-list .card .card-body .card-title{display:-webkit-box;overflow:hidden;text-overflow:ellipsis;-webkit-line-clamp:2;-webkit-box-orient:vertical}.post-tail-wrapper .license-wrapper>a,h1+.post-meta em,h1+.post-meta time,footer a{color:var(--text-muted-hightlight-color);font-weight:600}.post-tail-wrapper .license-wrapper span:last-child,.post-tail-wrapper,.post-meta{font-size:.85rem}#related-posts time,footer{font-size:.8rem}sup:target,.footnotes>ol>li:target{background-color:var(--footnote-target-bg);width:-moz-fit-content;width:-webkit-fit-content;width:fit-content;transition:background-color 1.75s ease-in-out}@media(prefers-color-scheme: light){html:not([data-mode]),html[data-mode=light]{--language-border-color: #ececec;--highlight-bg-color: #f6f8fa;--highlighter-rouge-color: #3f596f;--highlight-lineno-color: #9e9e9e;--inline-code-bg: #f6f6f7;--code-color: #3a3a3a;--code-header-text-color: #a3a3a3;--code-header-muted-color: #e5e5e5;--code-header-icon-color: #c9c8c8;--clipboard-checked-color: #43c743}html:not([data-mode]) [class^=prompt-],html[data-mode=light] [class^=prompt-]{--inline-code-bg: #fbfafa}html:not([data-mode]) .highlight table td,html[data-mode=light] .highlight table td{padding:5px}html:not([data-mode]) .highlight table pre,html[data-mode=light] .highlight table pre{margin:0}html:not([data-mode]) .highlight,html:not([data-mode]) .highlight .w,html[data-mode=light] .highlight,html[data-mode=light] .highlight .w{color:#24292f;background-color:#f6f8fa}html:not([data-mode]) .highlight .k,html:not([data-mode]) .highlight .kd,html:not([data-mode]) .highlight .kn,html:not([data-mode]) .highlight .kp,html:not([data-mode]) .highlight .kr,html:not([data-mode]) .highlight .kt,html:not([data-mode]) .highlight .kv,html[data-mode=light] .highlight .k,html[data-mode=light] .highlight .kd,html[data-mode=light] .highlight .kn,html[data-mode=light] .highlight .kp,html[data-mode=light] .highlight .kr,html[data-mode=light] .highlight .kt,html[data-mode=light] .highlight .kv{color:#cf222e}html:not([data-mode]) .highlight .gr,html[data-mode=light] .highlight .gr{color:#f6f8fa}html:not([data-mode]) .highlight .gd,html[data-mode=light] .highlight .gd{color:#82071e;background-color:#ffebe9}html:not([data-mode]) .highlight .nb,html[data-mode=light] .highlight .nb{color:#953800}html:not([data-mode]) .highlight .nc,html[data-mode=light] .highlight .nc{color:#953800}html:not([data-mode]) .highlight .no,html[data-mode=light] .highlight .no{color:#953800}html:not([data-mode]) .highlight .nn,html[data-mode=light] .highlight .nn{color:#953800}html:not([data-mode]) .highlight .sr,html[data-mode=light] .highlight .sr{color:#116329}html:not([data-mode]) .highlight .na,html[data-mode=light] .highlight .na{color:#116329}html:not([data-mode]) .highlight .nt,html[data-mode=light] .highlight .nt{color:#116329}html:not([data-mode]) .highlight .gi,html[data-mode=light] .highlight .gi{color:#116329;background-color:#dafbe1}html:not([data-mode]) .highlight .kc,html[data-mode=light] .highlight .kc{color:#0550ae}html:not([data-mode]) .highlight .l,html:not([data-mode]) .highlight .ld,html:not([data-mode]) .highlight .m,html:not([data-mode]) .highlight .mb,html:not([data-mode]) .highlight .mf,html:not([data-mode]) .highlight .mh,html:not([data-mode]) .highlight .mi,html:not([data-mode]) .highlight .il,html:not([data-mode]) .highlight .mo,html:not([data-mode]) .highlight .mx,html[data-mode=light] .highlight .l,html[data-mode=light] .highlight .ld,html[data-mode=light] .highlight .m,html[data-mode=light] .highlight .mb,html[data-mode=light] .highlight .mf,html[data-mode=light] .highlight .mh,html[data-mode=light] .highlight .mi,html[data-mode=light] .highlight .il,html[data-mode=light] .highlight .mo,html[data-mode=light] .highlight .mx{color:#0550ae}html:not([data-mode]) .highlight .sb,html[data-mode=light] .highlight .sb{color:#0550ae}html:not([data-mode]) .highlight .bp,html[data-mode=light] .highlight .bp{color:#0550ae}html:not([data-mode]) .highlight .ne,html[data-mode=light] .highlight .ne{color:#0550ae}html:not([data-mode]) .highlight .nl,html[data-mode=light] .highlight .nl{color:#0550ae}html:not([data-mode]) .highlight .py,html[data-mode=light] .highlight .py{color:#0550ae}html:not([data-mode]) .highlight .nv,html:not([data-mode]) .highlight .vc,html:not([data-mode]) .highlight .vg,html:not([data-mode]) .highlight .vi,html:not([data-mode]) .highlight .vm,html[data-mode=light] .highlight .nv,html[data-mode=light] .highlight .vc,html[data-mode=light] .highlight .vg,html[data-mode=light] .highlight .vi,html[data-mode=light] .highlight .vm{color:#0550ae}html:not([data-mode]) .highlight .o,html:not([data-mode]) .highlight .ow,html[data-mode=light] .highlight .o,html[data-mode=light] .highlight .ow{color:#0550ae}html:not([data-mode]) .highlight .gh,html[data-mode=light] .highlight .gh{color:#0550ae;font-weight:bold}html:not([data-mode]) .highlight .gu,html[data-mode=light] .highlight .gu{color:#0550ae;font-weight:bold}html:not([data-mode]) .highlight .s,html:not([data-mode]) .highlight .sa,html:not([data-mode]) .highlight .sc,html:not([data-mode]) .highlight .dl,html:not([data-mode]) .highlight .sd,html:not([data-mode]) .highlight .s2,html:not([data-mode]) .highlight .se,html:not([data-mode]) .highlight .sh,html:not([data-mode]) .highlight .sx,html:not([data-mode]) .highlight .s1,html:not([data-mode]) .highlight .ss,html[data-mode=light] .highlight .s,html[data-mode=light] .highlight .sa,html[data-mode=light] .highlight .sc,html[data-mode=light] .highlight .dl,html[data-mode=light] .highlight .sd,html[data-mode=light] .highlight .s2,html[data-mode=light] .highlight .se,html[data-mode=light] .highlight .sh,html[data-mode=light] .highlight .sx,html[data-mode=light] .highlight .s1,html[data-mode=light] .highlight .ss{color:#0a3069}html:not([data-mode]) .highlight .nd,html[data-mode=light] .highlight .nd{color:#8250df}html:not([data-mode]) .highlight .nf,html:not([data-mode]) .highlight .fm,html[data-mode=light] .highlight .nf,html[data-mode=light] .highlight .fm{color:#8250df}html:not([data-mode]) .highlight .err,html[data-mode=light] .highlight .err{color:#f6f8fa;background-color:#82071e}html:not([data-mode]) .highlight .c,html:not([data-mode]) .highlight .ch,html:not([data-mode]) .highlight .cd,html:not([data-mode]) .highlight .cm,html:not([data-mode]) .highlight .cp,html:not([data-mode]) .highlight .cpf,html:not([data-mode]) .highlight .c1,html:not([data-mode]) .highlight .cs,html[data-mode=light] .highlight .c,html[data-mode=light] .highlight .ch,html[data-mode=light] .highlight .cd,html[data-mode=light] .highlight .cm,html[data-mode=light] .highlight .cp,html[data-mode=light] .highlight .cpf,html[data-mode=light] .highlight .c1,html[data-mode=light] .highlight .cs{color:#68717a}html:not([data-mode]) .highlight .gl,html[data-mode=light] .highlight .gl{color:#68717a}html:not([data-mode]) .highlight .gt,html[data-mode=light] .highlight .gt{color:#68717a}html:not([data-mode]) .highlight .ni,html[data-mode=light] .highlight .ni{color:#24292f}html:not([data-mode]) .highlight .si,html[data-mode=light] .highlight .si{color:#24292f}html:not([data-mode]) .highlight .ge,html[data-mode=light] .highlight .ge{color:#24292f;font-style:italic}html:not([data-mode]) .highlight .gs,html[data-mode=light] .highlight .gs{color:#24292f;font-weight:bold}html[data-mode=dark]{--language-border-color: #2d2d2d;--highlight-bg-color: #151515;--highlighter-rouge-color: #c9def1;--highlight-lineno-color: #808080;--inline-code-bg: #323238;--code-color: #b0b0b0;--code-header-text-color: #6a6a6a;--code-header-muted-color: #353535;--code-header-icon-color: #565656;--clipboard-checked-color: #2bcc2b;--filepath-text-color: #cacaca}html[data-mode=dark] .highlight .gp{color:#87939d}html[data-mode=dark] .highlight table td{padding:5px}html[data-mode=dark] .highlight table pre{margin:0}html[data-mode=dark] .highlight,html[data-mode=dark] .highlight .w{color:#d0d0d0;background-color:#151515}html[data-mode=dark] .highlight .err{color:#151515;background-color:#ac4142}html[data-mode=dark] .highlight .c,html[data-mode=dark] .highlight .ch,html[data-mode=dark] .highlight .cd,html[data-mode=dark] .highlight .cm,html[data-mode=dark] .highlight .cpf,html[data-mode=dark] .highlight .c1,html[data-mode=dark] .highlight .cs{color:#848484}html[data-mode=dark] .highlight .cp{color:#f4bf75}html[data-mode=dark] .highlight .nt{color:#f4bf75}html[data-mode=dark] .highlight .o,html[data-mode=dark] .highlight .ow{color:#d0d0d0}html[data-mode=dark] .highlight .p,html[data-mode=dark] .highlight .pi{color:#d0d0d0}html[data-mode=dark] .highlight .gi{color:#90a959}html[data-mode=dark] .highlight .gd{color:#f08a8b;background-color:#320000}html[data-mode=dark] .highlight .gh{color:#6a9fb5;background-color:#151515;font-weight:bold}html[data-mode=dark] .highlight .k,html[data-mode=dark] .highlight .kn,html[data-mode=dark] .highlight .kp,html[data-mode=dark] .highlight .kr,html[data-mode=dark] .highlight .kv{color:#aa759f}html[data-mode=dark] .highlight .kc{color:#d28445}html[data-mode=dark] .highlight .kt{color:#d28445}html[data-mode=dark] .highlight .kd{color:#d28445}html[data-mode=dark] .highlight .s,html[data-mode=dark] .highlight .sb,html[data-mode=dark] .highlight .sc,html[data-mode=dark] .highlight .dl,html[data-mode=dark] .highlight .sd,html[data-mode=dark] .highlight .s2,html[data-mode=dark] .highlight .sh,html[data-mode=dark] .highlight .sx,html[data-mode=dark] .highlight .s1{color:#90a959}html[data-mode=dark] .highlight .sa{color:#aa759f}html[data-mode=dark] .highlight .sr{color:#75b5aa}html[data-mode=dark] .highlight .si{color:#b76d45}html[data-mode=dark] .highlight .se{color:#b76d45}html[data-mode=dark] .highlight .nn{color:#f4bf75}html[data-mode=dark] .highlight .nc{color:#f4bf75}html[data-mode=dark] .highlight .no{color:#f4bf75}html[data-mode=dark] .highlight .na{color:#6a9fb5}html[data-mode=dark] .highlight .m,html[data-mode=dark] .highlight .mb,html[data-mode=dark] .highlight .mf,html[data-mode=dark] .highlight .mh,html[data-mode=dark] .highlight .mi,html[data-mode=dark] .highlight .il,html[data-mode=dark] .highlight .mo,html[data-mode=dark] .highlight .mx{color:#90a959}html[data-mode=dark] .highlight .ss{color:#90a959}}@media(prefers-color-scheme: dark){html:not([data-mode]),html[data-mode=dark]{--language-border-color: #2d2d2d;--highlight-bg-color: #151515;--highlighter-rouge-color: #c9def1;--highlight-lineno-color: #808080;--inline-code-bg: #323238;--code-color: #b0b0b0;--code-header-text-color: #6a6a6a;--code-header-muted-color: #353535;--code-header-icon-color: #565656;--clipboard-checked-color: #2bcc2b;--filepath-text-color: #cacaca}html:not([data-mode]) .highlight .gp,html[data-mode=dark] .highlight .gp{color:#87939d}html:not([data-mode]) .highlight table td,html[data-mode=dark] .highlight table td{padding:5px}html:not([data-mode]) .highlight table pre,html[data-mode=dark] .highlight table pre{margin:0}html:not([data-mode]) .highlight,html:not([data-mode]) .highlight .w,html[data-mode=dark] .highlight,html[data-mode=dark] .highlight .w{color:#d0d0d0;background-color:#151515}html:not([data-mode]) .highlight .err,html[data-mode=dark] .highlight .err{color:#151515;background-color:#ac4142}html:not([data-mode]) .highlight .c,html:not([data-mode]) .highlight .ch,html:not([data-mode]) .highlight .cd,html:not([data-mode]) .highlight .cm,html:not([data-mode]) .highlight .cpf,html:not([data-mode]) .highlight .c1,html:not([data-mode]) .highlight .cs,html[data-mode=dark] .highlight .c,html[data-mode=dark] .highlight .ch,html[data-mode=dark] .highlight .cd,html[data-mode=dark] .highlight .cm,html[data-mode=dark] .highlight .cpf,html[data-mode=dark] .highlight .c1,html[data-mode=dark] .highlight .cs{color:#848484}html:not([data-mode]) .highlight .cp,html[data-mode=dark] .highlight .cp{color:#f4bf75}html:not([data-mode]) .highlight .nt,html[data-mode=dark] .highlight .nt{color:#f4bf75}html:not([data-mode]) .highlight .o,html:not([data-mode]) .highlight .ow,html[data-mode=dark] .highlight .o,html[data-mode=dark] .highlight .ow{color:#d0d0d0}html:not([data-mode]) .highlight .p,html:not([data-mode]) .highlight .pi,html[data-mode=dark] .highlight .p,html[data-mode=dark] .highlight .pi{color:#d0d0d0}html:not([data-mode]) .highlight .gi,html[data-mode=dark] .highlight .gi{color:#90a959}html:not([data-mode]) .highlight .gd,html[data-mode=dark] .highlight .gd{color:#f08a8b;background-color:#320000}html:not([data-mode]) .highlight .gh,html[data-mode=dark] .highlight .gh{color:#6a9fb5;background-color:#151515;font-weight:bold}html:not([data-mode]) .highlight .k,html:not([data-mode]) .highlight .kn,html:not([data-mode]) .highlight .kp,html:not([data-mode]) .highlight .kr,html:not([data-mode]) .highlight .kv,html[data-mode=dark] .highlight .k,html[data-mode=dark] .highlight .kn,html[data-mode=dark] .highlight .kp,html[data-mode=dark] .highlight .kr,html[data-mode=dark] .highlight .kv{color:#aa759f}html:not([data-mode]) .highlight .kc,html[data-mode=dark] .highlight .kc{color:#d28445}html:not([data-mode]) .highlight .kt,html[data-mode=dark] .highlight .kt{color:#d28445}html:not([data-mode]) .highlight .kd,html[data-mode=dark] .highlight .kd{color:#d28445}html:not([data-mode]) .highlight .s,html:not([data-mode]) .highlight .sb,html:not([data-mode]) .highlight .sc,html:not([data-mode]) .highlight .dl,html:not([data-mode]) .highlight .sd,html:not([data-mode]) .highlight .s2,html:not([data-mode]) .highlight .sh,html:not([data-mode]) .highlight .sx,html:not([data-mode]) .highlight .s1,html[data-mode=dark] .highlight .s,html[data-mode=dark] .highlight .sb,html[data-mode=dark] .highlight .sc,html[data-mode=dark] .highlight .dl,html[data-mode=dark] .highlight .sd,html[data-mode=dark] .highlight .s2,html[data-mode=dark] .highlight .sh,html[data-mode=dark] .highlight .sx,html[data-mode=dark] .highlight .s1{color:#90a959}html:not([data-mode]) .highlight .sa,html[data-mode=dark] .highlight .sa{color:#aa759f}html:not([data-mode]) .highlight .sr,html[data-mode=dark] .highlight .sr{color:#75b5aa}html:not([data-mode]) .highlight .si,html[data-mode=dark] .highlight .si{color:#b76d45}html:not([data-mode]) .highlight .se,html[data-mode=dark] .highlight .se{color:#b76d45}html:not([data-mode]) .highlight .nn,html[data-mode=dark] .highlight .nn{color:#f4bf75}html:not([data-mode]) .highlight .nc,html[data-mode=dark] .highlight .nc{color:#f4bf75}html:not([data-mode]) .highlight .no,html[data-mode=dark] .highlight .no{color:#f4bf75}html:not([data-mode]) .highlight .na,html[data-mode=dark] .highlight .na{color:#6a9fb5}html:not([data-mode]) .highlight .m,html:not([data-mode]) .highlight .mb,html:not([data-mode]) .highlight .mf,html:not([data-mode]) .highlight .mh,html:not([data-mode]) .highlight .mi,html:not([data-mode]) .highlight .il,html:not([data-mode]) .highlight .mo,html:not([data-mode]) .highlight .mx,html[data-mode=dark] .highlight .m,html[data-mode=dark] .highlight .mb,html[data-mode=dark] .highlight .mf,html[data-mode=dark] .highlight .mh,html[data-mode=dark] .highlight .mi,html[data-mode=dark] .highlight .il,html[data-mode=dark] .highlight .mo,html[data-mode=dark] .highlight .mx{color:#90a959}html:not([data-mode]) .highlight .ss,html[data-mode=dark] .highlight .ss{color:#90a959}html[data-mode=light]{--language-border-color: #ececec;--highlight-bg-color: #f6f8fa;--highlighter-rouge-color: #3f596f;--highlight-lineno-color: #9e9e9e;--inline-code-bg: #f6f6f7;--code-color: #3a3a3a;--code-header-text-color: #a3a3a3;--code-header-muted-color: #e5e5e5;--code-header-icon-color: #c9c8c8;--clipboard-checked-color: #43c743}html[data-mode=light] [class^=prompt-]{--inline-code-bg: #fbfafa}html[data-mode=light] .highlight table td{padding:5px}html[data-mode=light] .highlight table pre{margin:0}html[data-mode=light] .highlight,html[data-mode=light] .highlight .w{color:#24292f;background-color:#f6f8fa}html[data-mode=light] .highlight .k,html[data-mode=light] .highlight .kd,html[data-mode=light] .highlight .kn,html[data-mode=light] .highlight .kp,html[data-mode=light] .highlight .kr,html[data-mode=light] .highlight .kt,html[data-mode=light] .highlight .kv{color:#cf222e}html[data-mode=light] .highlight .gr{color:#f6f8fa}html[data-mode=light] .highlight .gd{color:#82071e;background-color:#ffebe9}html[data-mode=light] .highlight .nb{color:#953800}html[data-mode=light] .highlight .nc{color:#953800}html[data-mode=light] .highlight .no{color:#953800}html[data-mode=light] .highlight .nn{color:#953800}html[data-mode=light] .highlight .sr{color:#116329}html[data-mode=light] .highlight .na{color:#116329}html[data-mode=light] .highlight .nt{color:#116329}html[data-mode=light] .highlight .gi{color:#116329;background-color:#dafbe1}html[data-mode=light] .highlight .kc{color:#0550ae}html[data-mode=light] .highlight .l,html[data-mode=light] .highlight .ld,html[data-mode=light] .highlight .m,html[data-mode=light] .highlight .mb,html[data-mode=light] .highlight .mf,html[data-mode=light] .highlight .mh,html[data-mode=light] .highlight .mi,html[data-mode=light] .highlight .il,html[data-mode=light] .highlight .mo,html[data-mode=light] .highlight .mx{color:#0550ae}html[data-mode=light] .highlight .sb{color:#0550ae}html[data-mode=light] .highlight .bp{color:#0550ae}html[data-mode=light] .highlight .ne{color:#0550ae}html[data-mode=light] .highlight .nl{color:#0550ae}html[data-mode=light] .highlight .py{color:#0550ae}html[data-mode=light] .highlight .nv,html[data-mode=light] .highlight .vc,html[data-mode=light] .highlight .vg,html[data-mode=light] .highlight .vi,html[data-mode=light] .highlight .vm{color:#0550ae}html[data-mode=light] .highlight .o,html[data-mode=light] .highlight .ow{color:#0550ae}html[data-mode=light] .highlight .gh{color:#0550ae;font-weight:bold}html[data-mode=light] .highlight .gu{color:#0550ae;font-weight:bold}html[data-mode=light] .highlight .s,html[data-mode=light] .highlight .sa,html[data-mode=light] .highlight .sc,html[data-mode=light] .highlight .dl,html[data-mode=light] .highlight .sd,html[data-mode=light] .highlight .s2,html[data-mode=light] .highlight .se,html[data-mode=light] .highlight .sh,html[data-mode=light] .highlight .sx,html[data-mode=light] .highlight .s1,html[data-mode=light] .highlight .ss{color:#0a3069}html[data-mode=light] .highlight .nd{color:#8250df}html[data-mode=light] .highlight .nf,html[data-mode=light] .highlight .fm{color:#8250df}html[data-mode=light] .highlight .err{color:#f6f8fa;background-color:#82071e}html[data-mode=light] .highlight .c,html[data-mode=light] .highlight .ch,html[data-mode=light] .highlight .cd,html[data-mode=light] .highlight .cm,html[data-mode=light] .highlight .cp,html[data-mode=light] .highlight .cpf,html[data-mode=light] .highlight .c1,html[data-mode=light] .highlight .cs{color:#68717a}html[data-mode=light] .highlight .gl{color:#68717a}html[data-mode=light] .highlight .gt{color:#68717a}html[data-mode=light] .highlight .ni{color:#24292f}html[data-mode=light] .highlight .si{color:#24292f}html[data-mode=light] .highlight .ge{color:#24292f;font-style:italic}html[data-mode=light] .highlight .gs{color:#24292f;font-weight:bold}}div[class^=language-],figure.highlight,.highlight{background-color:var(--highlight-bg-color)}td.rouge-code{padding-left:1rem;padding-right:1.5rem}.highlighter-rouge{color:var(--highlighter-rouge-color);margin-top:.5rem;margin-bottom:1.2em}.highlight{overflow:auto;padding-bottom:.75rem}.highlight pre{margin-bottom:0;font-size:.85rem;line-height:1.4rem;word-wrap:normal}.highlight table td:first-child{display:inline-block;margin-left:1rem;margin-right:.75rem}.highlight table td:last-child{padding-right:2rem !important}.highlight table td pre{overflow:visible;word-break:normal}.highlight .lineno{text-align:right;color:var(--highlight-lineno-color);-webkit-user-select:none;-moz-user-select:none;-o-user-select:none;-ms-user-select:none;user-select:none}code{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;color:var(--code-color)}code.highlighter-rouge{font-size:.85rem;padding:3px 5px;word-break:break-word;border-radius:4px;background-color:var(--inline-code-bg)}code.filepath{background-color:inherit;color:var(--filepath-text-color);font-weight:600;padding:0}a>code.highlighter-rouge{padding-bottom:0;color:inherit}a:hover>code.highlighter-rouge{border-bottom:none}blockquote code{color:inherit}td.rouge-code a{color:inherit !important;border-bottom:none !important;pointer-events:none}div[class^=language-]{box-shadow:var(--language-border-color) 0 0 0 1px}.content>div[class^=language-]{margin-left:-1rem;margin-right:-1rem;border-radius:0}div[class^=language-] .highlight{border-top-left-radius:0;border-top-right-radius:0}div.nolineno td:first-child,div.language-plaintext td:first-child,div.language-console td:first-child,div.language-terminal td:first-child{padding:0 !important;margin-right:0}div.nolineno td:first-child .lineno,div.language-plaintext td:first-child .lineno,div.language-console td:first-child .lineno,div.language-terminal td:first-child .lineno{display:none}.code-header{display:flex;justify-content:space-between;align-items:center;height:2.25rem;margin-left:.75rem;margin-right:.25rem}.code-header span{line-height:2.25rem}.code-header span i{font-size:1rem;width:1.75rem;color:var(--code-header-icon-color)}.code-header span i.small{font-size:70%}[file] .code-header span>i{position:relative;top:1px}.code-header span::after{content:attr(data-label-text);font-size:.85rem;font-weight:600;color:var(--code-header-text-color)}.code-header button{border:1px solid rgba(0,0,0,0);height:2.25rem;width:2.25rem;padding:0;background-color:inherit}.code-header button i{color:var(--code-header-icon-color)}.code-header button[timeout]:hover{border-color:var(--clipboard-checked-color)}.code-header button[timeout] i{color:var(--clipboard-checked-color)}.code-header button:focus{outline:none}.code-header button:not([timeout]):hover{background-color:rgba(128,128,128,.37)}.code-header button:not([timeout]):hover i{color:#fff}@media all and (min-width: 576px){.content>div[class^=language-]{margin-left:0;margin-right:0;border-radius:.625rem}div[class^=language-] .code-header{margin-left:0;margin-right:0}div[class^=language-] .code-header::before{content:"";display:inline-block;margin-left:1rem;width:.75rem;height:.75rem;border-radius:50%;background-color:var(--code-header-muted-color);box-shadow:1.25rem 0 0 var(--code-header-muted-color),2.5rem 0 0 var(--code-header-muted-color)}div[class^=language-] .code-header span{margin-left:-0.875rem}}html{font-size:16px}@media(prefers-color-scheme: light){html:not([data-mode]),html[data-mode=light]{--main-bg: white;--mask-bg: #c1c3c5;--main-border-color: #f3f3f3;--text-color: #34343c;--text-muted-color: #757575;--text-muted-hightlight-color: inherit;--heading-color: #2a2a2a;--label-color: #585858;--blockquote-border-color: #eeeeee;--blockquote-text-color: #757575;--link-color: #0056b2;--link-underline-color: #dee2e6;--button-bg: #ffffff;--btn-border-color: #e9ecef;--btn-backtotop-color: #686868;--btn-backtotop-border-color: #f1f1f1;--btn-box-shadow: #eaeaea;--checkbox-color: #c5c5c5;--checkbox-checked-color: #07a8f7;--img-bg: radial-gradient( circle, rgb(255, 255, 255) 0%, rgb(239, 239, 239) 100% );--shimmer-bg: linear-gradient( 90deg, rgba(250, 250, 250, 0) 0%, rgba(232, 230, 230, 1) 50%, rgba(250, 250, 250, 0) 100% );--site-title-color: rgb(113, 113, 113);--site-subtitle-color: #717171;--sidebar-bg: #f6f8fa;--sidebar-border-color: #efefef;--sidebar-muted-color: #545454;--sidebar-active-color: #1d1d1d;--sidebar-hover-bg: rgb(223, 233, 241, 0.64);--sidebar-btn-bg: white;--sidebar-btn-color: #8e8e8e;--avatar-border-color: white;--topbar-bg: rgb(255, 255, 255, 0.7);--topbar-text-color: rgb(78, 78, 78);--search-border-color: rgb(240, 240, 240);--search-icon-color: #c2c6cc;--input-focus-border-color: #b8b8b8;--post-list-text-color: dimgray;--btn-patinator-text-color: #555555;--btn-paginator-hover-color: var(--sidebar-bg);--toc-highlight: #0550ae;--btn-share-color: gray;--btn-share-hover-color: #0d6efd;--card-bg: white;--card-hovor-bg: #e2e2e2;--card-shadow: rgb(104, 104, 104, 0.05) 0 2px 6px 0, rgba(211, 209, 209, 0.15) 0 0 0 1px;--footnote-target-bg: lightcyan;--tb-odd-bg: #fbfcfd;--tb-border-color: #eaeaea;--dash-color: silver;--kbd-wrap-color: #bdbdbd;--kbd-text-color: var(--text-color);--kbd-bg-color: white;--prompt-text-color: rgb(46, 46, 46, 0.77);--prompt-tip-bg: rgb(123, 247, 144, 0.2);--prompt-tip-icon-color: #03b303;--prompt-info-bg: #e1f5fe;--prompt-info-icon-color: #0070cb;--prompt-warning-bg: rgb(255, 243, 205);--prompt-warning-icon-color: #ef9c03;--prompt-danger-bg: rgb(248, 215, 218, 0.56);--prompt-danger-icon-color: #df3c30;--tag-border: #dee2e6;--tag-shadow: var(--btn-border-color);--tag-hover: rgb(222, 226, 230);--search-tag-bg: #f8f9fa;--categories-border: rgba(0, 0, 0, 0.125);--categories-hover-bg: var(--btn-border-color);--categories-icon-hover-color: darkslategray;--timeline-color: rgba(0, 0, 0, 0.075);--timeline-node-bg: #c2c6cc;--timeline-year-dot-color: #ffffff}html:not([data-mode]) [class^=prompt-],html[data-mode=light] [class^=prompt-]{--link-underline-color: rgb(219, 216, 216)}html:not([data-mode]) .dark,html[data-mode=light] .dark{display:none}html[data-mode=dark]{--main-bg: rgb(27, 27, 30);--mask-bg: rgb(68, 69, 70);--main-border-color: rgb(44, 45, 45);--text-color: rgb(175, 176, 177);--text-muted-color: #868686;--text-muted-hightlight-color: #aeaeae;--heading-color: #cccccc;--label-color: #a7a7a7;--blockquote-border-color: rgb(66, 66, 66);--blockquote-text-color: #868686;--link-color: rgb(138, 180, 248);--link-underline-color: rgb(82, 108, 150);--button-bg: #1e1e1e;--btn-border-color: #2e2f31;--btn-backtotop-color: var(--text-color);--btn-backtotop-border-color: #212122;--btn-box-shadow: var(--main-bg);--card-header-bg: #292929;--checkbox-color: rgb(118, 120, 121);--checkbox-checked-color: var(--link-color);--img-bg: radial-gradient(circle, rgb(22, 22, 24) 0%, rgb(32, 32, 32) 100%);--shimmer-bg: linear-gradient( 90deg, rgba(255, 255, 255, 0) 0%, rgba(58, 55, 55, 0.4) 50%, rgba(255, 255, 255, 0) 100% );--site-title-color: #717070;--site-subtitle-color: #868686;--sidebar-bg: #1e1e1e;--sidebar-border-color: #292929;--sidebar-muted-color: #868686;--sidebar-active-color: rgb(255, 255, 255, 0.95);--sidebar-hover-bg: #262626;--sidebar-btn-bg: #232328;--sidebar-btn-color: #787878;--avatar-border-color: rgb(206, 206, 206, 0.9);--topbar-bg: rgb(27, 27, 30, 0.64);--topbar-text-color: var(--text-color);--search-border-color: rgb(55, 55, 55);--search-icon-color: rgb(100, 102, 105);--input-focus-border-color: rgb(112, 114, 115);--post-list-text-color: rgb(175, 176, 177);--btn-patinator-text-color: var(--text-color);--btn-paginator-hover-color: #2e2e2e;--toc-highlight: rgb(116, 178, 243);--tag-hover: rgb(43, 56, 62);--tb-odd-bg: #252526;--tb-even-bg: rgb(31, 31, 34);--tb-border-color: var(--tb-odd-bg);--footnote-target-bg: rgb(63, 81, 181);--btn-share-color: #6c757d;--btn-share-hover-color: #bfc1ca;--card-bg: #1e1e1e;--card-hovor-bg: #464d51;--card-shadow: rgb(21, 21, 21, 0.72) 0 6px 18px 0, rgb(137, 135, 135, 0.24) 0 0 0 1px;--kbd-wrap-color: #6a6a6a;--kbd-text-color: #d3d3d3;--kbd-bg-color: #242424;--prompt-text-color: rgb(216, 212, 212, 0.75);--prompt-tip-bg: rgb(22, 60, 36, 0.64);--prompt-tip-icon-color: rgb(15, 164, 15, 0.81);--prompt-info-bg: rgb(7, 59, 104, 0.8);--prompt-info-icon-color: #0075d1;--prompt-warning-bg: rgb(90, 69, 3, 0.88);--prompt-warning-icon-color: rgb(255, 165, 0, 0.8);--prompt-danger-bg: rgb(86, 28, 8, 0.8);--prompt-danger-icon-color: #cd0202;--tag-border: rgb(59, 79, 88);--tag-shadow: rgb(32, 33, 33);--dash-color: rgb(63, 65, 68);--search-tag-bg: #292828;--categories-border: rgb(64, 66, 69, 0.5);--categories-hover-bg: rgb(73, 75, 76);--categories-icon-hover-color: white;--timeline-node-bg: rgb(150, 152, 156);--timeline-color: rgb(63, 65, 68);--timeline-year-dot-color: var(--timeline-color);color-scheme:dark}html[data-mode=dark] .light{display:none}html[data-mode=dark] hr{border-color:var(--main-border-color)}html[data-mode=dark] .categories.card,html[data-mode=dark] .list-group-item{background-color:var(--card-bg)}html[data-mode=dark] .categories .card-header{background-color:var(--card-header-bg)}html[data-mode=dark] .categories .list-group-item{border-left:none;border-right:none;padding-left:2rem;border-color:var(--categories-border)}html[data-mode=dark] .categories .list-group-item:last-child{border-bottom-color:var(--card-bg)}html[data-mode=dark] #archives li:nth-child(odd){background-image:linear-gradient(to left, rgb(26, 26, 30), rgb(39, 39, 45), rgb(39, 39, 45), rgb(39, 39, 45), rgb(26, 26, 30))}html[data-mode=dark] #disqus_thread{color-scheme:none}}@media(prefers-color-scheme: dark){html:not([data-mode]),html[data-mode=dark]{--main-bg: rgb(27, 27, 30);--mask-bg: rgb(68, 69, 70);--main-border-color: rgb(44, 45, 45);--text-color: rgb(175, 176, 177);--text-muted-color: #868686;--text-muted-hightlight-color: #aeaeae;--heading-color: #cccccc;--label-color: #a7a7a7;--blockquote-border-color: rgb(66, 66, 66);--blockquote-text-color: #868686;--link-color: rgb(138, 180, 248);--link-underline-color: rgb(82, 108, 150);--button-bg: #1e1e1e;--btn-border-color: #2e2f31;--btn-backtotop-color: var(--text-color);--btn-backtotop-border-color: #212122;--btn-box-shadow: var(--main-bg);--card-header-bg: #292929;--checkbox-color: rgb(118, 120, 121);--checkbox-checked-color: var(--link-color);--img-bg: radial-gradient(circle, rgb(22, 22, 24) 0%, rgb(32, 32, 32) 100%);--shimmer-bg: linear-gradient( 90deg, rgba(255, 255, 255, 0) 0%, rgba(58, 55, 55, 0.4) 50%, rgba(255, 255, 255, 0) 100% );--site-title-color: #717070;--site-subtitle-color: #868686;--sidebar-bg: #1e1e1e;--sidebar-border-color: #292929;--sidebar-muted-color: #868686;--sidebar-active-color: rgb(255, 255, 255, 0.95);--sidebar-hover-bg: #262626;--sidebar-btn-bg: #232328;--sidebar-btn-color: #787878;--avatar-border-color: rgb(206, 206, 206, 0.9);--topbar-bg: rgb(27, 27, 30, 0.64);--topbar-text-color: var(--text-color);--search-border-color: rgb(55, 55, 55);--search-icon-color: rgb(100, 102, 105);--input-focus-border-color: rgb(112, 114, 115);--post-list-text-color: rgb(175, 176, 177);--btn-patinator-text-color: var(--text-color);--btn-paginator-hover-color: #2e2e2e;--toc-highlight: rgb(116, 178, 243);--tag-hover: rgb(43, 56, 62);--tb-odd-bg: #252526;--tb-even-bg: rgb(31, 31, 34);--tb-border-color: var(--tb-odd-bg);--footnote-target-bg: rgb(63, 81, 181);--btn-share-color: #6c757d;--btn-share-hover-color: #bfc1ca;--card-bg: #1e1e1e;--card-hovor-bg: #464d51;--card-shadow: rgb(21, 21, 21, 0.72) 0 6px 18px 0, rgb(137, 135, 135, 0.24) 0 0 0 1px;--kbd-wrap-color: #6a6a6a;--kbd-text-color: #d3d3d3;--kbd-bg-color: #242424;--prompt-text-color: rgb(216, 212, 212, 0.75);--prompt-tip-bg: rgb(22, 60, 36, 0.64);--prompt-tip-icon-color: rgb(15, 164, 15, 0.81);--prompt-info-bg: rgb(7, 59, 104, 0.8);--prompt-info-icon-color: #0075d1;--prompt-warning-bg: rgb(90, 69, 3, 0.88);--prompt-warning-icon-color: rgb(255, 165, 0, 0.8);--prompt-danger-bg: rgb(86, 28, 8, 0.8);--prompt-danger-icon-color: #cd0202;--tag-border: rgb(59, 79, 88);--tag-shadow: rgb(32, 33, 33);--dash-color: rgb(63, 65, 68);--search-tag-bg: #292828;--categories-border: rgb(64, 66, 69, 0.5);--categories-hover-bg: rgb(73, 75, 76);--categories-icon-hover-color: white;--timeline-node-bg: rgb(150, 152, 156);--timeline-color: rgb(63, 65, 68);--timeline-year-dot-color: var(--timeline-color);color-scheme:dark}html:not([data-mode]) .light,html[data-mode=dark] .light{display:none}html:not([data-mode]) hr,html[data-mode=dark] hr{border-color:var(--main-border-color)}html:not([data-mode]) .categories.card,html:not([data-mode]) .list-group-item,html[data-mode=dark] .categories.card,html[data-mode=dark] .list-group-item{background-color:var(--card-bg)}html:not([data-mode]) .categories .card-header,html[data-mode=dark] .categories .card-header{background-color:var(--card-header-bg)}html:not([data-mode]) .categories .list-group-item,html[data-mode=dark] .categories .list-group-item{border-left:none;border-right:none;padding-left:2rem;border-color:var(--categories-border)}html:not([data-mode]) .categories .list-group-item:last-child,html[data-mode=dark] .categories .list-group-item:last-child{border-bottom-color:var(--card-bg)}html:not([data-mode]) #archives li:nth-child(odd),html[data-mode=dark] #archives li:nth-child(odd){background-image:linear-gradient(to left, rgb(26, 26, 30), rgb(39, 39, 45), rgb(39, 39, 45), rgb(39, 39, 45), rgb(26, 26, 30))}html:not([data-mode]) #disqus_thread,html[data-mode=dark] #disqus_thread{color-scheme:none}html[data-mode=light]{--main-bg: white;--mask-bg: #c1c3c5;--main-border-color: #f3f3f3;--text-color: #34343c;--text-muted-color: #757575;--text-muted-hightlight-color: inherit;--heading-color: #2a2a2a;--label-color: #585858;--blockquote-border-color: #eeeeee;--blockquote-text-color: #757575;--link-color: #0056b2;--link-underline-color: #dee2e6;--button-bg: #ffffff;--btn-border-color: #e9ecef;--btn-backtotop-color: #686868;--btn-backtotop-border-color: #f1f1f1;--btn-box-shadow: #eaeaea;--checkbox-color: #c5c5c5;--checkbox-checked-color: #07a8f7;--img-bg: radial-gradient( circle, rgb(255, 255, 255) 0%, rgb(239, 239, 239) 100% );--shimmer-bg: linear-gradient( 90deg, rgba(250, 250, 250, 0) 0%, rgba(232, 230, 230, 1) 50%, rgba(250, 250, 250, 0) 100% );--site-title-color: rgb(113, 113, 113);--site-subtitle-color: #717171;--sidebar-bg: #f6f8fa;--sidebar-border-color: #efefef;--sidebar-muted-color: #545454;--sidebar-active-color: #1d1d1d;--sidebar-hover-bg: rgb(223, 233, 241, 0.64);--sidebar-btn-bg: white;--sidebar-btn-color: #8e8e8e;--avatar-border-color: white;--topbar-bg: rgb(255, 255, 255, 0.7);--topbar-text-color: rgb(78, 78, 78);--search-border-color: rgb(240, 240, 240);--search-icon-color: #c2c6cc;--input-focus-border-color: #b8b8b8;--post-list-text-color: dimgray;--btn-patinator-text-color: #555555;--btn-paginator-hover-color: var(--sidebar-bg);--toc-highlight: #0550ae;--btn-share-color: gray;--btn-share-hover-color: #0d6efd;--card-bg: white;--card-hovor-bg: #e2e2e2;--card-shadow: rgb(104, 104, 104, 0.05) 0 2px 6px 0, rgba(211, 209, 209, 0.15) 0 0 0 1px;--footnote-target-bg: lightcyan;--tb-odd-bg: #fbfcfd;--tb-border-color: #eaeaea;--dash-color: silver;--kbd-wrap-color: #bdbdbd;--kbd-text-color: var(--text-color);--kbd-bg-color: white;--prompt-text-color: rgb(46, 46, 46, 0.77);--prompt-tip-bg: rgb(123, 247, 144, 0.2);--prompt-tip-icon-color: #03b303;--prompt-info-bg: #e1f5fe;--prompt-info-icon-color: #0070cb;--prompt-warning-bg: rgb(255, 243, 205);--prompt-warning-icon-color: #ef9c03;--prompt-danger-bg: rgb(248, 215, 218, 0.56);--prompt-danger-icon-color: #df3c30;--tag-border: #dee2e6;--tag-shadow: var(--btn-border-color);--tag-hover: rgb(222, 226, 230);--search-tag-bg: #f8f9fa;--categories-border: rgba(0, 0, 0, 0.125);--categories-hover-bg: var(--btn-border-color);--categories-icon-hover-color: darkslategray;--timeline-color: rgba(0, 0, 0, 0.075);--timeline-node-bg: #c2c6cc;--timeline-year-dot-color: #ffffff}html[data-mode=light] [class^=prompt-]{--link-underline-color: rgb(219, 216, 216)}html[data-mode=light] .dark{display:none}}body{background:var(--main-bg);padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);color:var(--text-color);-webkit-font-smoothing:antialiased;font-family:"Source Sans Pro","Microsoft Yahei",sans-serif}h1{font-size:1.92rem}h2{font-size:1.54rem}h3{font-size:1.36rem}h4{font-size:1.18rem}h5{font-size:1rem}a{text-decoration:none}img{max-width:100%;height:auto;transition:all .35s ease-in-out}.blur img{-webkit-filter:blur(20px);filter:blur(20px)}blockquote{border-left:5px solid var(--blockquote-border-color);padding-left:1rem;color:var(--blockquote-text-color)}blockquote>p:last-child{margin-bottom:0}blockquote[class^=prompt-]{border-left:0;position:relative;padding:1rem 1rem 1rem 3rem;color:var(--prompt-text-color)}blockquote[class^=prompt-]::before{text-align:center;width:3rem;position:absolute;left:.25rem;margin-top:.4rem;text-rendering:auto;-webkit-font-smoothing:antialiased}blockquote.prompt-tip{background-color:var(--prompt-tip-bg)}blockquote.prompt-tip::before{content:"";color:var(--prompt-tip-icon-color);font:var(--fa-font-regular)}blockquote.prompt-info{background-color:var(--prompt-info-bg)}blockquote.prompt-info::before{content:"";color:var(--prompt-info-icon-color);font:var(--fa-font-solid)}blockquote.prompt-warning{background-color:var(--prompt-warning-bg)}blockquote.prompt-warning::before{content:"";color:var(--prompt-warning-icon-color);font:var(--fa-font-solid)}blockquote.prompt-danger{background-color:var(--prompt-danger-bg)}blockquote.prompt-danger::before{content:"";color:var(--prompt-danger-icon-color);font:var(--fa-font-solid)}kbd{font-family:inherit;display:inline-block;vertical-align:middle;line-height:1.3rem;min-width:1.75rem;text-align:center;margin:0 .3rem;padding-top:.1rem;color:var(--kbd-text-color);background-color:var(--kbd-bg-color);border-radius:.25rem;border:solid 1px var(--kbd-wrap-color);box-shadow:inset 0 -2px 0 var(--kbd-wrap-color)}footer{background-color:var(--main-bg);height:5rem;border-top:1px solid var(--main-border-color)}footer p{text-align:center;margin-bottom:0}.access{top:2rem;transition:top .2s ease-in-out;margin-top:3rem;margin-bottom:4rem}.access:only-child{position:-webkit-sticky;position:sticky}.access>section{padding-left:1rem;border-left:1px solid var(--main-border-color)}.access>section:not(:last-child){margin-bottom:4rem}.access .content{font-size:.9rem}#panel-wrapper .panel-heading{font-family:inherit;line-height:inherit;color:var(--label-color);font-size:inherit;font-weight:600}#panel-wrapper .post-tag{line-height:1.05rem;font-size:.85rem;border-radius:.8rem;padding:.3rem .5rem;margin:0 .35rem .5rem 0}#panel-wrapper .post-tag:hover{transition:all .3s ease-in}#access-lastmod a{color:inherit}.footnotes>ol{padding-left:2rem;margin-top:.5rem}.footnotes>ol>li:not(:last-child){margin-bottom:.3rem}.footnotes>ol>li>p{margin-left:.25em;margin-top:0;margin-bottom:0}a.footnote{margin-left:1px;margin-right:1px;padding-left:2px;padding-right:2px;border-bottom-style:none !important}a.reversefootnote{font-size:.6rem;line-height:1;position:relative;bottom:.25em;margin-left:.25em;border-bottom-style:none !important}.table-wrapper{overflow-x:auto;margin-bottom:1.5rem}.table-wrapper>table{min-width:100%;overflow-x:auto;border-spacing:0}.table-wrapper>table thead{border-bottom:solid 2px rgba(210,215,217,.75)}.table-wrapper>table tbody tr{border-bottom:1px solid var(--tb-border-color)}.table-wrapper>table tbody tr:nth-child(2n){background-color:var(--tb-even-bg)}.table-wrapper>table tbody tr:nth-child(2n+1){background-color:var(--tb-odd-bg)}.preview-img{aspect-ratio:40/21;width:100%;height:100%;overflow:hidden}.preview-img:not(.no-bg){background:var(--img-bg)}.preview-img img{height:100%;-o-object-fit:cover;object-fit:cover}#post-list .preview-img img{width:100%}.post-preview{border:0;background:var(--card-bg);box-shadow:var(--card-shadow)}.post-preview::before{content:"";width:100%;height:100%;position:absolute;background-color:var(--card-hovor-bg);opacity:0;transition:opacity .35s ease-in-out}.post-preview:hover::before{opacity:.3}main{line-height:1.75}main h1{margin-top:2rem;margin-bottom:1.5rem}main p>a.popup:not(.normal):not(.left):not(.right){position:relative;left:50%;transform:translateX(-50%)}.content{font-size:1.08rem;margin-top:2rem;overflow-wrap:break-word}.content a.popup{margin-top:.5rem;margin-bottom:.5rem;cursor:zoom-in}.content ol:not([class]),.content ol.task-list,.content ul:not([class]),.content ul.task-list{-webkit-padding-start:1.75rem;padding-inline-start:1.75rem}.content ol:not([class]) li,.content ol.task-list li,.content ul:not([class]) li,.content ul.task-list li{margin:.25rem 0;padding-left:.25rem}.content ol:not([class]) ol,.content ol:not([class]) ul,.content ol.task-list ol,.content ol.task-list ul,.content ul:not([class]) ol,.content ul:not([class]) ul,.content ul.task-list ol,.content ul.task-list ul{-webkit-padding-start:1.25rem;padding-inline-start:1.25rem;margin:.5rem 0}.content ul.task-list{-webkit-padding-start:1.25rem;padding-inline-start:1.25rem}.content ul.task-list li{list-style-type:none;padding-left:0}.content ul.task-list li>i{width:2rem;margin-left:-1.25rem;color:var(--checkbox-color)}.content ul.task-list li>i.checked{color:var(--checkbox-checked-color)}.content ul.task-list li ul{-webkit-padding-start:1.75rem;padding-inline-start:1.75rem}.content ul.task-list input[type=checkbox]{margin:0 .5rem .2rem -1.3rem;vertical-align:middle}.content dl>dd{margin-left:1rem}.content ::marker{color:var(--text-muted-color)}.post-tag{display:inline-block;min-width:2rem;text-align:center;border-radius:.5rem;border:1px solid var(--btn-border-color);padding:0 .4rem;color:var(--text-muted-color);line-height:1.3rem}.post-tag:not(:last-child){margin-right:.2rem}.rounded-10{border-radius:10px !important}.img-link{color:rgba(0,0,0,0);display:inline-flex}.shimmer{overflow:hidden;position:relative;background:var(--img-bg)}.shimmer::before{content:"";position:absolute;background:var(--shimmer-bg);height:100%;width:100%;-webkit-animation:shimmer 1.3s infinite;animation:shimmer 1.3s infinite}@-webkit-keyframes shimmer{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}}@keyframes shimmer{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}}.embed-video{width:100%;height:100%;margin-bottom:1rem}.embed-video.youtube,.embed-video.bilibili{aspect-ratio:16/9}.embed-video.twitch{aspect-ratio:310/189}.btn-lang{border:1px solid !important;padding:1px 3px;border-radius:3px;color:var(--link-color)}.btn-lang:focus{box-shadow:none}.loaded{display:block !important}.d-flex.loaded{display:flex !important}.unloaded{display:none !important}.visible{visibility:visible !important}.hidden{visibility:hidden !important}.flex-grow-1{flex-grow:1 !important}.btn-box-shadow{box-shadow:var(--card-shadow)}.text-muted{color:var(--text-muted-color) !important}.tooltip-inner{font-size:.7rem;max-width:220px;text-align:left}.btn.btn-outline-primary:not(.disabled):hover{border-color:#007bff !important}.disabled{color:#cec4c4;pointer-events:auto;cursor:not-allowed}.hide-border-bottom{border-bottom:none !important}.input-focus{box-shadow:none;border-color:var(--input-focus-border-color) !important;background:center !important;transition:background-color .15s ease-in-out,border-color .15s ease-in-out}.left{float:left;margin:.75rem 1rem 1rem 0}.right{float:right;margin:.75rem 0 1rem 1rem}figure .mfp-title{text-align:center;padding-right:0;margin-top:.5rem}.mfp-img{transition:none}.mermaid{text-align:center}mjx-container{overflow-y:hidden;min-width:auto !important}#sidebar{padding-left:0;padding-right:0;position:fixed;top:0;left:0;height:100%;overflow-y:auto;width:260px;z-index:99;background:var(--sidebar-bg);border-right:1px solid var(--sidebar-border-color);-ms-overflow-style:none;scrollbar-width:none}#sidebar::-webkit-scrollbar{display:none}#sidebar .sidebar-bottom .mode-toggle:hover,#sidebar .sidebar-bottom a:hover,#sidebar .site-title a:hover{color:var(--sidebar-active-color)}#sidebar #avatar{display:block;width:7rem;height:7rem;overflow:hidden;box-shadow:var(--avatar-border-color) 0 0 0 2px;transform:translateZ(0)}#sidebar #avatar img{transition:transform .5s}#sidebar #avatar img:hover{transform:scale(1.2)}#sidebar .profile-wrapper{margin-top:2.5rem;margin-bottom:2.5rem;padding-left:2.5rem;padding-right:1.25rem;width:100%}#sidebar .site-title{font-family:inherit;font-weight:900;font-size:1.75rem;line-height:1.2;letter-spacing:.25px;margin-top:1.25rem;margin-bottom:.5rem}#sidebar .site-title a{color:var(--site-title-color)}#sidebar .site-subtitle{font-size:95%;color:var(--site-subtitle-color);margin-top:.25rem;word-spacing:1px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#sidebar ul{margin-bottom:2rem}#sidebar ul li.nav-item{opacity:.9;width:100%;padding-left:1.5rem;padding-right:1.5rem}#sidebar ul li.nav-item a.nav-link{padding-top:.6rem;padding-bottom:.6rem;display:flex;align-items:center;border-radius:.75rem;font-weight:600}#sidebar ul li.nav-item a.nav-link:hover{background-color:var(--sidebar-hover-bg)}#sidebar ul li.nav-item a.nav-link i{font-size:95%;opacity:.8;margin-right:1.5rem}#sidebar ul li.nav-item a.nav-link span{font-size:90%;letter-spacing:.2px}#sidebar ul li.nav-item.active .nav-link{color:var(--sidebar-active-color);background-color:var(--sidebar-hover-bg)}#sidebar ul li.nav-item.active .nav-link span{opacity:1}#sidebar ul li.nav-item:not(:first-child){margin-top:.25rem}#sidebar .sidebar-bottom{padding-left:2rem;padding-right:1rem;margin-bottom:1.5rem}#sidebar .sidebar-bottom .mode-toggle,#sidebar .sidebar-bottom a{width:1.75rem;height:1.75rem;margin-bottom:.5rem;border-radius:50%;color:var(--sidebar-btn-color);background-color:var(--sidebar-btn-bg);text-align:center;display:flex;align-items:center;justify-content:center;box-shadow:var(--sidebar-border-color) 0 0 0 1px}#sidebar .sidebar-bottom .mode-toggle:hover,#sidebar .sidebar-bottom a:hover{background-color:var(--sidebar-hover-bg)}#sidebar .sidebar-bottom a:not(:last-child){margin-right:.8rem}#sidebar .sidebar-bottom i{line-height:1.75rem}#sidebar .sidebar-bottom .mode-toggle{padding:0;border:0}#sidebar .sidebar-bottom .icon-border{margin-left:calc((.8rem - 3px)/2);margin-right:calc((.8rem - 3px)/2);background-color:var(--sidebar-btn-color);content:"";width:3px;height:3px;border-radius:50%;margin-bottom:.5rem}@media(hover: hover){#sidebar ul>li:last-child::after{transition:top .5s ease}.nav-link{transition:background-color .3s ease-in-out}.post-preview{transition:background-color .35s ease-in-out}}#search-result-wrapper{display:none;height:100%;width:100%;overflow:auto}#search-result-wrapper .content{margin-top:2rem}#topbar-wrapper{height:3rem;background-color:var(--topbar-bg)}#topbar button i{color:#999}#topbar #breadcrumb{font-size:1rem;color:var(--text-muted-color);padding-left:.5rem}#topbar #breadcrumb span:not(:last-child)::after{content:"›";padding:0 .3rem}::-webkit-input-placeholder{color:var(--text-muted-color) !important}::-moz-placeholder{color:var(--text-muted-color) !important}:-ms-input-placeholder{color:var(--text-muted-color) !important}::-ms-input-placeholder{color:var(--text-muted-color) !important}::placeholder{color:var(--text-muted-color) !important}:focus::-webkit-input-placeholder{opacity:.6}:focus::-moz-placeholder{opacity:.6}:focus:-ms-input-placeholder{opacity:.6}:focus::-ms-input-placeholder{opacity:.6}:focus::placeholder{opacity:.6}search{display:flex;width:100%;border-radius:1rem;border:1px solid var(--search-border-color);background:var(--main-bg);padding:0 .5rem}search i{z-index:2;font-size:.9rem;color:var(--search-icon-color)}#sidebar-trigger,#search-trigger{display:none}#search-cancel{color:var(--link-color);display:none;white-space:nowrap}#search-input{background:center;border:0;border-radius:0;padding:.18rem .3rem;color:var(--text-color);height:auto}#search-input:focus{box-shadow:none}#search-hints{padding:0 1rem}#search-hints h4{margin-bottom:1.5rem}#search-hints .post-tag{display:inline-block;line-height:1rem;font-size:1rem;background:var(--search-tag-bg);border:none;padding:.5rem;margin:0 1.25rem 1rem 0}#search-hints .post-tag::before{content:"#";color:var(--text-muted-color);padding-right:.2rem}#search-results{padding-bottom:3rem}#search-results a{font-size:1.4rem;line-height:2.5rem}#search-results>article{width:100%}#search-results>article:not(:last-child){margin-bottom:1rem}#search-results>article i{color:#818182;margin-right:.15rem;font-size:80%}#search-results>article>p{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}#topbar-title{display:none;font-size:1.1rem;font-weight:600;font-family:sans-serif;color:var(--topbar-text-color);text-align:center;width:70%;overflow:hidden;text-overflow:ellipsis;word-break:keep-all;white-space:nowrap}#mask{display:none;position:fixed;inset:0 0 0 0;height:100%;width:100%;z-index:1}[sidebar-display] #mask{display:block !important}#main-wrapper{position:relative;padding-left:0;padding-right:0}#main-wrapper>.container{min-height:100vh}#topbar-wrapper.row,#main-wrapper>.container>.row,#search-result-wrapper>.row{margin-left:0;margin-right:0}#tail-wrapper>:not(script){margin-top:3rem}#back-to-top{display:none;z-index:1;cursor:pointer;position:fixed;right:1rem;bottom:4.625rem;background:var(--button-bg);color:var(--btn-backtotop-color);padding:0;width:2.75rem;height:2.75rem;border-radius:50%;border:1px solid var(--btn-backtotop-border-color);transition:transform .2s ease-out;-webkit-transition:transform .2s ease-out}#back-to-top:hover{transform:translate3d(0, -5px, 0);-webkit-transform:translate3d(0, -5px, 0)}#back-to-top i{line-height:2.75rem;position:relative;bottom:2px}@-webkit-keyframes popup{from{opacity:0;bottom:0}}@keyframes popup{from{opacity:0;bottom:0}}#notification .toast-header{background:none;border-bottom:none;color:inherit}#notification .toast-body{font-family:Lato,sans-serif;line-height:1.25rem}#notification .toast-body button{font-size:90%;min-width:4rem}#notification.toast.show{display:block;min-width:20rem;border-radius:.5rem;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background-color:rgba(255,255,255,.5);color:rgba(27,27,30,.7294117647);position:fixed;left:50%;bottom:20%;transform:translateX(-50%);-webkit-animation:popup .8s;animation:popup .8s}@media all and (max-width: 576px){main .content>blockquote[class^=prompt-]{margin-left:-1rem;margin-right:-1rem;border-radius:0;max-width:none}#avatar{width:5rem;height:5rem}}@media all and (max-width: 768px){#main-wrapper>.container,#topbar{max-width:100%}#main-wrapper>.container{padding-left:0;padding-right:0}}@media all and (max-width: 849px){footer{transition:transform .4s ease;height:6rem;padding:1.5rem 0}[sidebar-display] #sidebar{transform:translateX(0)}[sidebar-display] #main-wrapper{transform:translateX(260px)}[sidebar-display] #back-to-top{visibility:hidden}#sidebar{transition:transform .4s ease;transform:translateX(-260px);-webkit-transform:translateX(-260px)}#main-wrapper{transition:transform .4s ease}#topbar,#main-wrapper>.container{max-width:100%}#search-result-wrapper{width:100%}#breadcrumb,search{display:none}#topbar-wrapper{transition:transform .4s ease,top .2s ease;left:0}main,#panel-wrapper{margin-top:0}#topbar-title,#sidebar-trigger,#search-trigger{display:block}#search-result-wrapper .content{letter-spacing:0}#tags{justify-content:center !important}h1.dynamic-title{display:none}h1.dynamic-title~.content{margin-top:2.5rem}}@media all and (min-width: 850px){html{overflow-y:scroll}#main-wrapper{margin-left:260px}#sidebar .profile-wrapper{margin-top:3rem}#search-hints{display:none}search{max-width:200px}#search-result-wrapper{max-width:1250px;justify-content:start !important}main h1{margin-top:3rem}div.content .table-wrapper>table{min-width:70%}#back-to-top{right:5%;bottom:3.625rem}#topbar-title{text-align:left}}@media all and (min-width: 992px)and (max-width: 1199px){#main-wrapper>.container .col-lg-11{flex:0 0 96%;max-width:96%}}@media all and (min-width: 850px)and (max-width: 1199px){#search-results>div{max-width:700px}#breadcrumb{width:65%;overflow:hidden;text-overflow:ellipsis;word-break:keep-all;white-space:nowrap}}@media all and (max-width: 1199px){#panel-wrapper{display:none}#main-wrapper>.container>div.row{justify-content:center !important}}@media all and (min-width: 1200px){search{margin-right:4rem}#search-input{transition:all .3s ease-in-out}#search-results>article{width:45%}#search-results>article:nth-child(odd){margin-right:1.5rem}#search-results>article:nth-child(even){margin-left:1.5rem}#search-results>article:last-child:nth-child(odd){position:relative;right:24.3%}.content{font-size:1.03rem}}@media all and (min-width: 1400px){#back-to-top{right:calc((100vw - 260px - 1140px)/2 + 3rem)}}@media all and (min-width: 1650px){#main-wrapper{margin-left:300px}#topbar-wrapper{left:300px}search{margin-right:calc(112.5px - .75rem)}#main-wrapper>.container{max-width:1250px;padding-left:1.75rem !important;padding-right:1.75rem !important}main.col-12,#tail-wrapper{padding-right:4.5rem !important}#back-to-top{right:calc((100vw - 300px - 1250px)/2 + 2rem)}#sidebar{width:300px}#sidebar .profile-wrapper{margin-top:3.5rem;margin-bottom:2.5rem;padding-left:3.5rem}#sidebar ul li.nav-item{padding-left:2.75rem;padding-right:2.75rem}#sidebar .sidebar-bottom{padding-left:2.75rem;margin-bottom:1.75rem}#sidebar .sidebar-bottom a:not(:last-child){margin-right:1rem}#sidebar .sidebar-bottom .icon-border{margin-left:calc((1rem - 3px)/2);margin-right:calc((1rem - 3px)/2)}}#post-list{margin-top:2rem}#post-list .card-wrapper:hover{text-decoration:none}#post-list .card-wrapper:not(:last-child){margin-bottom:1.25rem}#post-list .card{border:0;background:none}#post-list .card .preview-img img,#post-list .card .preview-img{border-radius:.625rem .625rem 0 0}#post-list .card .card-body{height:100%;padding:1rem}#post-list .card .card-body .card-title{color:var(--heading-color) !important;font-size:1.25rem}#post-list .card .card-body .post-meta,#post-list .card .card-body .card-text.content{color:var(--text-muted-color) !important}#post-list .card .card-body .card-text.content p{line-height:1.5;margin:0}#post-list .card .card-body .post-meta i:not(:first-child){margin-left:1.5rem}#post-list .card .card-body .post-meta em{color:inherit}#post-list .card .card-body .post-meta>div:first-child{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pagination{color:var(--text-color);font-family:Lato,sans-serif;justify-content:space-evenly}.pagination a:hover{text-decoration:none}.pagination .page-item .page-link{color:var(--btn-patinator-text-color);padding:0 .6rem;display:-webkit-box;-webkit-box-pack:center;-webkit-box-align:center;border-radius:.5rem;border:0;background-color:inherit}.pagination .page-item.active .page-link{background-color:var(--btn-paginator-hover-color)}.pagination .page-item:not(.active) .page-link:hover{box-shadow:inset var(--btn-border-color) 0 0 0 1px}.pagination .page-item.disabled{cursor:not-allowed}.pagination .page-item.disabled .page-link{color:rgba(108,117,125,.57)}@media all and (min-width: 768px){#post-list .card .preview-img,#post-list .card .preview-img img{border-radius:0 .625rem .625rem 0}#post-list .card .card-body{padding:1.75rem 1.75rem 1.25rem 1.75rem}#post-list .card .card-body .card-text{display:inherit !important}#post-list .card .card-body .post-meta i:not(:first-child){margin-left:1.75rem}}@media all and (max-width: 830px){.pagination .page-item:not(:first-child):not(:last-child){display:none}}@media all and (min-width: 831px){#post-list{margin-top:2.5rem}.pagination{font-size:.85rem;justify-content:center}.pagination .page-item:not(:last-child){margin-right:.7rem}.pagination .page-index{display:none}}.post-navigation .btn.disabled,.post-navigation .btn{width:50%;position:relative;border-color:var(--btn-border-color)}h1+.post-meta>span+span::before{content:"•";padding-left:.25rem;padding-right:.25rem}h1+.post-meta em a{color:inherit}.post-tail-wrapper{margin-top:6rem;border-bottom:1px double var(--main-border-color)}.post-tail-wrapper .license-wrapper{line-height:1.2rem}.post-tail-wrapper .share-wrapper{vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.post-tail-wrapper .share-wrapper .share-icons>*,.post-tail-wrapper .share-wrapper .share-icons i{font-size:1.125rem}.post-tail-wrapper .share-wrapper .share-icons{display:flex}.post-tail-wrapper .share-wrapper .share-icons i{color:var(--btn-share-color)}.post-tail-wrapper .share-wrapper .share-icons>*{margin-left:.5rem}.post-tail-wrapper .share-wrapper .share-icons button{padding:0;border:none;line-height:inherit}.share-mastodon{--wc-stm-font-family: $font-family-base;--wc-stm-dialog-background-color: var(--card-bg);--wc-stm-form-button-border: 1px solid var(--btn-border-color);--wc-stm-form-submit-background-color: var(--sidebar-btn-bg);--wc-stm-form-cancel-background-color: var(--sidebar-btn-bg);--wc-stm-form-button-background-color-hover: #007bff;--wc-stm-form-button-color-hover: white;font-size:1rem}.post-tags{line-height:2rem}.post-navigation .btn:not(:hover){color:var(--link-color)}.post-navigation .btn:hover:not(.disabled)::before{color:#f5f5f5}.post-navigation .btn.disabled{pointer-events:auto;cursor:not-allowed;background:none;color:gray}.post-navigation .btn.btn-outline-primary.disabled:focus{box-shadow:none}.post-navigation .btn::before{color:var(--text-muted-color);font-size:.65rem;text-transform:uppercase;content:attr(aria-label)}.post-navigation .btn:first-child{border-radius:.625rem 0 0 .625rem;left:.5px}.post-navigation .btn:last-child{border-radius:0 .625rem .625rem 0;right:.5px}.post-navigation p{font-size:1.1rem;line-height:1.5rem;margin-top:.3rem;white-space:normal}@media(hover: hover){.post-navigation .btn,.post-navigation .btn::before{transition:all .35s ease-in-out}}@-webkit-keyframes fade-up{from{opacity:0;position:relative;top:2rem}to{opacity:1;position:relative;top:0}}@keyframes fade-up{from{opacity:0;position:relative;top:2rem}to{opacity:1;position:relative;top:0}}#toc-wrapper{border-left:1px solid rgba(158,158,158,.17);position:-webkit-sticky;position:sticky;top:4rem;transition:top .2s ease-in-out;-webkit-animation:fade-up .8s;animation:fade-up .8s}#toc-wrapper ul{list-style:none;font-size:.85rem;line-height:1.25;padding-left:0}#toc-wrapper ul li:not(:last-child){margin:.4rem 0}#toc-wrapper ul li a{padding:.2rem 0 .2rem 1.25rem}#toc-wrapper ul .toc-link{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#toc-wrapper ul .toc-link:hover{color:var(--toc-highlight);text-decoration:none}#toc-wrapper ul .toc-link::before{display:none}#toc-wrapper ul .is-active-link{color:var(--toc-highlight) !important;font-weight:600}#toc-wrapper ul .is-active-link::before{display:inline-block;width:1px;left:-1px;height:1.25rem;background-color:var(--toc-highlight) !important}#toc-wrapper ul ul{padding-left:.75rem}#related-posts>h3{color:var(--label-color);font-size:1.1rem;font-weight:600}#related-posts time{color:var(--text-muted-color)}#related-posts p{font-size:.9rem;margin-bottom:.5rem;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}#disqus_thread{min-height:8.5rem}.utterances{max-width:100%}.post-tail-wrapper .share-wrapper .share-icons>*:hover i{color:var(--btn-share-hover-color) !important}.share-label{color:inherit;font-size:inherit;font-weight:400}.share-label::after{content:":"}@media all and (max-width: 576px){.post-tail-bottom{flex-wrap:wrap-reverse !important}.post-tail-bottom>div:first-child{width:100%;margin-top:1rem}}@media all and (max-width: 768px){.content>p>img{max-width:calc(100% + 1rem)}}@media all and (max-width: 849px){.post-navigation{padding-left:0;padding-right:0;margin-left:-0.5rem;margin-right:-0.5rem}}.tag{border-radius:.7em;padding:6px 8px 7px;margin-right:.8rem;line-height:3rem;letter-spacing:0;border:1px solid var(--tag-border) !important;box-shadow:0 0 3px 0 var(--tag-shadow)}.tag span{margin-left:.6em;font-size:.7em;font-family:Oswald,sans-serif}#archives{letter-spacing:.03rem}#archives ul li::before,#archives .year:first-child::before,#archives .year::before{content:"";width:4px;position:relative;float:left;background-color:var(--timeline-color)}#archives .year{height:3.5rem;font-size:1.5rem;position:relative;left:2px;margin-left:-4px}#archives .year::before{height:72px;left:79px;bottom:16px}#archives .year:first-child::before{height:32px;top:24px}#archives .year::after{content:"";display:inline-block;position:relative;border-radius:50%;width:12px;height:12px;left:21.5px;border:3px solid;background-color:var(--timeline-year-dot-color);border-color:var(--timeline-node-bg);box-shadow:0 0 2px 0 #c2c6cc;z-index:1}#archives ul li{font-size:1.1rem;line-height:3rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#archives ul li:nth-child(odd){background-color:var(--main-bg, #ffffff);background-image:linear-gradient(to left, #ffffff, #fbfbfb, #fbfbfb, #fbfbfb, #ffffff)}#archives ul li::before{top:0;left:77px;height:3.1rem}#archives ul:last-child li:last-child::before{height:1.5rem}#archives .date{white-space:nowrap;display:inline-block;position:relative;right:.5rem}#archives .date.month{width:1.4rem;text-align:center}#archives .date.day{font-size:85%;font-family:Lato,sans-serif}#archives a{margin-left:2.5rem;position:relative;top:.1rem}#archives a:hover{border-bottom:none}#archives a::before{content:"";display:inline-block;position:relative;border-radius:50%;width:8px;height:8px;float:left;top:1.35rem;left:71px;background-color:var(--timeline-node-bg);box-shadow:0 0 3px 0 #c2c6cc;z-index:1}@media all and (max-width: 576px){#archives{margin-top:-1rem}#archives ul{letter-spacing:0}}.categories i{color:gray}.categories{margin-bottom:2rem;border-color:var(--categories-border)}.categories .card-header{padding:.75rem;border-radius:calc(.625rem - 1px);border-bottom:0}.categories .card-header.hide-border-bottom{border-bottom-left-radius:0;border-bottom-right-radius:0}.categories i{font-size:86%}.categories .list-group-item{border-left:none;border-right:none;padding-left:2rem}.categories .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.categories .list-group-item:last-child{border-bottom:0}.category-trigger{width:1.7rem;height:1.7rem;border-radius:50%;text-align:center;color:#6c757d !important}.category-trigger i{position:relative;height:.7rem;width:1rem;transition:transform 300ms ease}.category-trigger:hover i{color:var(--categories-icon-hover-color)}@media(hover: hover){.category-trigger:hover{background-color:var(--categories-hover-bg)}}.rotate{transform:rotate(-90deg)}.dash{margin:0 .5rem .6rem .5rem;border-bottom:2px dotted var(--dash-color)}#page-category ul>li,#page-tag ul>li{line-height:1.5rem;padding:.6rem 0}#page-category ul>li::before,#page-tag ul>li::before{background:#999;width:5px;height:5px;border-radius:50%;display:block;content:"";position:relative;top:.6rem;margin-right:.5rem}#page-category ul>li>a,#page-tag ul>li>a{font-size:1.1rem}#page-tag h1>i{font-size:1.2rem}#page-category h1>i{font-size:1.25rem}#page-category a:hover,#page-tag a:hover,#access-lastmod a:hover{margin-bottom:-1px}@media all and (max-width: 576px){#page-category ul>li::before,#page-tag ul>li::before{margin:0 .5rem}#page-category ul>li>a,#page-tag ul>li>a{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}/*# sourceMappingURL=jekyll-theme-chirpy.css.map */ \ No newline at end of file diff --git a/assets/css/jekyll-theme-chirpy.css.map b/assets/css/jekyll-theme-chirpy.css.map new file mode 100644 index 0000000..1a5f681 --- /dev/null +++ b/assets/css/jekyll-theme-chirpy.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/addon/module.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/addon/variables.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/addon/syntax.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/colors/syntax-light.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/colors/syntax-dark.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/addon/commons.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/colors/typography-light.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/colors/typography-dark.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/layout/home.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/layout/post.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/layout/tags.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/layout/archives.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/layout/categories.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/layout/category-tag.scss"],"names":[],"mappings":"CAMA,iCACE,2BACA,gBACA,YCuBoB,kCDnBpB,gCACE,kBACA,sBAEA,wDACE,aAMJ,4CACE,cAGF,qBACE,4CACE,kBACA,UACA,2DAIA,oEACE,mBACA,UACA,0DAMR,sCACE,4BACA,uCAGF,+DACE,mBACA,cACA,mBAGF,2QACE,yBACA,gCACA,qBAGF,4CACE,wBAGF,sEACE,oDAGF,4EACE,+BAGF,gFACE,yBACA,sBACA,qBACA,iBAGF,6LACE,mBAGF,yFACE,eAGF,4EACE,kBAGF,wMACE,cC5EY,QDgFZ,oBACE,cACA,kBACA,kBACA,cACA,UACA,cAIJ,iDACE,iCACA,yBACA,sBACA,qBACA,iBAGF,iHACE,oBACA,gBACA,uBACA,qBACA,4BAGF,mFACE,yCACA,gBAGF,kFACE,iBAGF,2BACE,gBAIA,mCACE,2CACA,uBACA,0BACA,kBACA,8CEvIF,oCACE,4CCHF,iCACA,8BACA,mCACA,kCACA,0BACA,sBACA,kCACA,mCACA,kCACA,mCAEA,8EACE,0BAKF,oFACE,YAGF,sFACE,SAGF,0IAEE,cACA,yBAGF,ogBAOE,cAGF,0EACE,cAGF,0EACE,cACA,yBAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cACA,yBAGF,0EACE,cAGF,guBAUE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,kXAKE,cAGF,kJAEE,cAGF,0EACE,cACA,iBAGF,0EACE,cACA,iBAGF,4yBAWE,cAGF,0EACE,cAGF,oJAEE,cAGF,4EACE,cACA,yBAGF,glBAQE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cACA,kBAGF,0EACE,cACA,iBDrMA,qBETF,iCACA,8BACA,mCACA,kCACA,0BACA,sBACA,kCACA,mCACA,kCACA,mCACA,+BAEA,oCACE,cAKF,yCACE,YAGF,0CACE,SAGF,mEAEE,cACA,yBAGF,qCACE,cACA,yBAGF,4PAOE,cAGF,oCACE,cAGF,oCACE,cAGF,uEAEE,cAGF,uEAEE,cAGF,oCACE,cAGF,oCACE,cACA,yBAGF,oCACE,cACA,yBACA,iBAGF,mLAKE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,mUASE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,+RAQE,cAGF,oCACE,eF9IF,mCACE,2CEfF,iCACA,8BACA,mCACA,kCACA,0BACA,sBACA,kCACA,mCACA,kCACA,mCACA,+BAEA,yEACE,cAKF,mFACE,YAGF,qFACE,SAGF,wIAEE,cACA,yBAGF,2EACE,cACA,yBAGF,+fAOE,cAGF,yEACE,cAGF,yEACE,cAGF,gJAEE,cAGF,gJAEE,cAGF,yEACE,cAGF,yEACE,cACA,yBAGF,yEACE,cACA,yBACA,iBAGF,2WAKE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,+oBASE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,skBAQE,cAGF,yEACE,cFxIA,sBCnBF,iCACA,8BACA,mCACA,kCACA,0BACA,sBACA,kCACA,mCACA,kCACA,mCAEA,uCACE,0BAKF,0CACE,YAGF,2CACE,SAGF,qEAEE,cACA,yBAGF,kQAOE,cAGF,qCACE,cAGF,qCACE,cACA,yBAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cACA,yBAGF,qCACE,cAGF,gXAUE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,yLAKE,cAGF,yEAEE,cAGF,qCACE,cACA,iBAGF,qCACE,cACA,iBAGF,sZAWE,cAGF,qCACE,cAGF,0EAEE,cAGF,sCACE,cACA,yBAGF,wSAQE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cACA,kBAGF,qCACE,cACA,kBDlLJ,kDACE,2CAGF,cACE,kBACA,qBAGF,mBACE,qCACA,iBACA,oBAGF,WAQE,cACA,sBAEA,eACE,gBACA,UDtCa,OCuCb,mBACA,iBAKE,gCACE,qBACA,iBACA,oBAGF,+BACE,8BAGF,wBACE,iBACA,kBAKN,mBACE,iBACA,oCACA,yBACA,sBACA,oBACA,qBACA,iBAIJ,KACE,qBACA,iBACA,aACA,wBAEA,uBACE,UDhFa,OCiFb,gBACA,sBACA,kBACA,uCAGF,cACE,yBACA,iCACA,gBACA,UAGF,yBACE,iBACA,cAGF,+BACE,mBAGF,gBACE,cAWF,gBACE,yBACA,8BACA,oBAIJ,sBAIE,kDAEA,+BFIA,YEHiB,MFIjB,aEJiB,MAEf,gBAGF,iCACE,yBACA,0BAUA,2IACE,qBACA,eAEA,2KACE,aAMR,aAGE,aACA,8BACA,mBACA,ODlKmB,QCmKnB,mBACA,oBAGA,kBACE,YDxKiB,QC2KjB,oBACE,eACA,MD1KY,QC2KZ,oCAEA,0BACE,cAIK,2BACP,kBACA,QAIF,yBACE,8BACA,iBACA,gBACA,oCAKJ,oBAIE,+BACA,ODzMiB,QC0MjB,MD1MiB,QC2MjB,UACA,yBAEA,sBACE,oCAIA,mCACE,4CAGF,+BACE,qCAIJ,0BACE,aAGF,yCACE,uCAEA,2CACE,WAMR,kCAEI,+BFzGF,YE0GmB,EFzGnB,aEyGmB,EAEf,cDrPQ,QCwPV,mCF/GF,YEgHmB,EF/GnB,aE+GmB,EAIf,2CACE,WACA,qBACA,YALW,KAMX,MD1PQ,OC2PR,OD3PQ,OC4PR,kBACA,gDACA,gGAMF,wCAEE,uBG7RR,KAuBE,eAtBA,oCACE,4CCEF,iBACA,mBACA,6BAGA,sBACA,4BACA,uCACA,yBACA,uBACA,mCACA,iCACA,sBACA,gCACA,qBACA,4BACA,+BACA,sCACA,0BACA,0BACA,kCACA,oFAKA,2HAQA,uCACA,+BACA,sBACA,gCACA,+BACA,gCACA,6CACA,wBACA,6BACA,6BAGA,qCACA,qCACA,0CACA,6BACA,oCAGA,gCACA,oCACA,+CAGA,yBACA,wBACA,iCACA,iBACA,yBACA,yFAEA,gCACA,qBACA,2BACA,qBACA,0BACA,oCACA,sBACA,2CACA,yCACA,iCACA,0BACA,kCACA,wCACA,qCACA,6CACA,oCAGA,sBACA,sCACA,gCACA,yBAWA,0CACA,+CACA,6CAGA,uCACA,4BACA,mCAhBA,8EACE,2CAGF,wDACE,aD1FA,qBEHF,2BACA,2BACA,qCAGA,iCACA,4BACA,uCACA,yBACA,uBACA,2CACA,iCACA,iCACA,0CACA,qBACA,4BACA,yCACA,sCACA,iCACA,0BACA,qCACA,4CACA,4EACA,0HAQA,4BACA,+BACA,sBACA,gCACA,+BACA,iDACA,4BACA,0BACA,6BACA,+CAGA,mCACA,uCACA,uCACA,wCACA,+CAGA,2CACA,8CACA,qCAGA,oCACA,6BACA,qBACA,8BACA,oCACA,uCACA,2BACA,iCACA,mBACA,yBACA,sFAEA,0BACA,0BACA,wBACA,8CACA,uCACA,gDACA,uCACA,kCACA,0CACA,mDACA,wCACA,oCAGA,8BACA,8BACA,8BACA,yBAGA,0CACA,uCACA,qCAGA,uCACA,kCACA,iDA4CA,kBA1CA,4BACE,aAGF,wBACE,sCAIF,4EAEE,gCAIA,8CACE,uCAGF,kDACE,iBACA,kBACA,kBACA,sCAEA,6DACE,mCAKN,iDACE,+HAaF,oCACE,mBFtIF,mCACE,2CETF,2BACA,2BACA,qCAGA,iCACA,4BACA,uCACA,yBACA,uBACA,2CACA,iCACA,iCACA,0CACA,qBACA,4BACA,yCACA,sCACA,iCACA,0BACA,qCACA,4CACA,4EACA,0HAQA,4BACA,+BACA,sBACA,gCACA,+BACA,iDACA,4BACA,0BACA,6BACA,+CAGA,mCACA,uCACA,uCACA,wCACA,+CAGA,2CACA,8CACA,qCAGA,oCACA,6BACA,qBACA,8BACA,oCACA,uCACA,2BACA,iCACA,mBACA,yBACA,sFAEA,0BACA,0BACA,wBACA,8CACA,uCACA,gDACA,uCACA,kCACA,0CACA,mDACA,wCACA,oCAGA,8BACA,8BACA,8BACA,yBAGA,0CACA,uCACA,qCAGA,uCACA,kCACA,iDA4CA,kBA1CA,yDACE,aAGF,iDACE,sCAIF,0JAEE,gCAIA,6FACE,uCAGF,qGACE,iBACA,kBACA,kBACA,sCAEA,2HACE,mCAKN,mGACE,+HAaF,yEACE,kBFhIA,sBCdF,iBACA,mBACA,6BAGA,sBACA,4BACA,uCACA,yBACA,uBACA,mCACA,iCACA,sBACA,gCACA,qBACA,4BACA,+BACA,sCACA,0BACA,0BACA,kCACA,oFAKA,2HAQA,uCACA,+BACA,sBACA,gCACA,+BACA,gCACA,6CACA,wBACA,6BACA,6BAGA,qCACA,qCACA,0CACA,6BACA,oCAGA,gCACA,oCACA,+CAGA,yBACA,wBACA,iCACA,iBACA,yBACA,yFAEA,gCACA,qBACA,2BACA,qBACA,0BACA,oCACA,sBACA,2CACA,yCACA,iCACA,0BACA,kCACA,wCACA,qCACA,6CACA,oCAGA,sBACA,sCACA,gCACA,yBAWA,0CACA,+CACA,6CAGA,uCACA,4BACA,mCAhBA,uCACE,2CAGF,4BACE,cDvEJ,KACE,0BACA,kHAEA,wBACA,mCACA,YJHiB,+CISjB,GAeI,kBAfJ,GAeI,kBAfJ,GAeI,kBAfJ,GAeI,kBAfJ,GAiBI,eAKN,EAGE,qBAGF,IACE,eACA,YACA,gCAEA,UAGE,0BACA,kBAIJ,WACE,qDACA,kBACA,mCAEA,wBACE,gBAGF,2BACE,cACA,kBACA,4BACA,+BAIA,mCACE,kBACA,WACA,kBACA,YACA,iBACA,oBACA,mCLqFJ,sBACE,sCAEA,8BACE,QKrFmB,ILsFnB,mCACA,4BANJ,uBACE,uCAEA,+BACE,QKpFoB,ILqFpB,oCACA,0BANJ,0BACE,0CAEA,kCACE,QKnFuB,ILoFvB,uCACA,0BANJ,yBACE,yCAEA,iCACE,QKlFsB,ILmFtB,sCACA,0BKjFN,IACE,oBACA,qBACA,sBACA,mBACA,kBACA,kBACA,eACA,kBACA,4BACA,qCACA,qBACA,uCACA,gDAGF,OACE,gCACA,OJtHc,KIuHd,8CAYA,SACE,kBACA,gBAcJ,QACE,SACA,+BACA,gBACA,mBAEA,mBACE,wBACA,gBAGF,gBACE,kBACA,+CAEA,iCACE,mBAIJ,iBACE,gBAMF,8BACE,oBACA,oBLbF,MADwD,mBAExD,UKciB,QLbjB,YAH2C,IKmB3C,yBACE,oBACA,iBACA,oBACA,oBACA,wBAEA,+BACE,2BAMJ,kBAOE,cAIJ,cACE,kBACA,iBAGE,kCACE,oBAKF,mBACE,kBACA,aACA,gBAMK,WLtFT,YKuFiB,ILtFjB,aKsFiB,IL7EjB,aK8EiB,IL7EjB,cK6EiB,IAEf,oCASO,kBACP,gBACA,cACA,kBACA,aACA,kBACA,oCAOJ,eACE,gBACA,qBAEA,qBACE,eACA,gBACA,iBAEA,2BACE,8CAQA,8BACE,+CAEA,4CACE,mCAGF,8CACE,kCAaV,aACE,mBACA,WACA,YACA,gBAIA,yBACE,yBAGF,iBACE,YACA,oBACA,iBAIS,4BACP,WAKN,cAGE,SACA,0BACA,8BAEA,sBAGE,WACA,WACA,YACA,kBACA,sCACA,UACA,oCAIA,4BACE,WAKN,KACE,iBAEA,QACE,gBACA,qBAKE,mDLvLJ,kBACA,SACA,2BKkNF,SACE,kBACA,gBACA,yBAGE,iBL5PF,WK+PmB,ML9PnB,cK8PmB,MAEf,eAcF,8FAEE,8BACA,6BAEA,0GACE,gBACA,oBAGF,oNAEE,8BACA,6BACA,eAKN,sBACE,8BACA,6BAEA,yBACE,qBACA,eAGA,2BACE,WACA,qBACA,4BAEA,mCACE,oCAIJ,4BACE,8BACA,6BAIJ,2CACE,6BACA,sBAIJ,eACE,iBAGF,kBACE,8BAQJ,UACE,qBACA,eACA,kBACA,oBACA,yCACA,gBACA,8BACA,mBAEA,2BACE,mBAIJ,YACE,8BAGF,UACE,oBACA,oBAGF,SACE,gBACA,kBACA,yBAEA,iBACE,WACA,kBACA,6BACA,YACA,WACA,wCACA,gCAGF,2BACE,GACE,4BAGF,KACE,4BAIJ,mBACE,GACE,4BAGF,KACE,4BAKN,aACE,WACA,YACA,mBAIA,2CAEE,kBAGF,oBACE,qBAKJ,UACE,4BACA,gBACA,kBACA,wBAEA,gBACE,gBAMJ,QACE,yBAES,eACP,wBAIJ,UACE,wBAGF,SACE,8BAGF,QACE,6BAGF,aACE,uBAGF,gBACE,8BAIF,YACE,yCAIF,eACE,gBACA,gBACA,gBAKA,8CACE,gCAIJ,UACE,cACA,oBACA,mBAGF,oBACE,8BAGF,aACE,gBACA,wDACA,6BACA,2EAGF,MACE,WACA,0BAGF,OACE,YACA,0BAOF,kBACE,kBACA,gBACA,iBAGF,SACE,gBAIF,SACE,kBAIF,cACE,kBACA,0BASF,SLngBE,aKogBe,ELngBf,cKmgBe,EAEf,eACA,MACA,OACA,YACA,gBACA,MJ1qBc,MI2qBd,WACA,6BACA,mDAQA,wBACA,qBANA,4BACE,aAQA,0GACE,kCAQJ,iBACE,cACA,WACA,YACA,gBACA,gDACA,wBAEA,qBACE,yBAEA,2BACE,qBAKN,0BLnkBA,WKokBiB,OLnkBjB,cKmkBiB,OAGf,oBACA,sBACA,WAGF,qBACE,oBACA,gBACA,kBACA,gBACA,qBACA,mBACA,oBAEA,uBAIE,8BAIJ,wBACE,cACA,iCACA,kBACA,iBACA,yBACA,sBACA,qBACA,iBAGF,YACE,mBAEA,wBACE,WACA,WACA,oBACA,qBAEA,mCLvmBJ,YKwmBqB,MLvmBrB,eKumBqB,MAEf,aACA,mBACA,qBACA,gBAEA,yCACE,yCAGF,qCACE,cACA,WACA,oBAGF,wCACE,cACA,oBAKF,yCACE,kCACA,yCAEA,8CACE,UAKN,0CACE,kBAKN,yBACE,kBACA,mBACA,qBAIA,iEACE,MAHS,QAIT,OAJS,QAKT,cApJG,MAqJH,kBACA,+BACA,uCACA,kBACA,aACA,mBACA,uBACA,iDAEA,6EACE,yCASF,4CACE,aJv0BK,MI20BT,2BACE,YA/BS,QAkCX,sCACE,UACA,SAOF,sCLrsBF,YKusBmB,sBLtsBnB,aKssBmB,sBAEf,0CACA,WACA,MAjMa,IAkMb,OAlMa,IAmMb,kBACA,cAnMG,MAwMT,qBACE,iCACE,wBAGF,UACE,4CAGF,cACE,8CAIJ,uBACE,aACA,YACA,WACA,cAEA,gCACE,gBAMJ,gBACE,OJ73Bc,KI83Bd,kCAIA,iBACE,WAGF,oBACE,eACA,8BACA,mBAQI,iDACE,YACA,gBAOV,4BL7vBE,yCKiwBF,mBLjwBE,yCKqwBF,uBLrwBE,yCKywBF,wBLzwBE,yCK6wBF,cL7wBE,yCKixBF,kCL7wBE,WKixBF,yBLjxBE,WKqxBF,6BLrxBE,WKyxBF,8BLzxBE,WK6xBF,oBL7xBE,WKiyBF,OACE,aACA,WACA,mBACA,4CACA,0BACA,gBAEA,SACE,UACA,gBACA,+BAIJ,iCAEE,aAIF,eACE,wBACA,aACA,mBAKF,cACE,kBACA,SACA,gBACA,qBACA,wBACA,YAEA,oBACE,gBAIJ,cACE,eAEA,iBACE,qBAGF,wBACE,qBACA,iBACA,eACA,gCACA,YACA,cACA,wBAEA,gCACE,YACA,8BACA,oBAON,gBACE,oBAEA,kBASE,iBACA,mBAGF,wBACE,WAEA,yCACE,mBAIF,0BACE,cACA,oBACA,cAGF,0BACE,gBACA,uBACA,oBACA,qBACA,4BAKN,cACE,aACA,iBACA,gBACA,uBACA,+BACA,kBACA,UACA,gBACA,uBACA,oBACA,mBAGF,MACE,aACA,eACA,cACA,YACA,WACA,UAES,wBACP,yBAMJ,cACE,kBLp7BA,aKs7Be,ELr7Bf,cKq7Be,EAEf,yBACE,iBAIJ,8ELv8BE,YK08Be,ELz8Bf,aKy8Be,EAIf,2BACE,gBAMJ,aACE,aACA,UACA,eACA,eACA,WACA,gBACA,4BACA,iCACA,UACA,MJvmCc,QIwmCd,OJxmCc,QIymCd,kBACA,mDACA,kCACA,0CAEA,mBACE,kCACA,0CAGF,eACE,YJpnCY,QIqnCZ,kBACA,WAKF,yBACE,KACE,UACA,UAIJ,iBACE,KACE,UACA,UAIJ,4BACE,gBACA,mBACA,cAGF,0BACE,4BACA,oBAEA,iCACE,cACA,eAKF,yBACE,cACA,gBACA,oBACA,mCACA,2BACA,sCACA,iCACA,eACA,SACA,WACA,2BACA,4BACA,oBAcN,kCAGM,yCLhjCJ,YKijCqB,MLhjCrB,aKgjCqB,MAEf,gBACA,eAKN,QACE,WACA,aAIJ,kCACE,iCACE,eAOF,yBL9jCA,aKgkCiB,EL/jCjB,cK+jCiB,GAKnB,kCAWE,OAJI,WALM,mBAYR,OJxuCkB,KIyuClB,iBAIA,2BACE,wBAGF,gCACE,4BAGF,+BACE,kBAIJ,SAzBI,WALM,mBAiCR,6BACA,qCAGF,cAhCI,WALM,mBAyCV,iCAEE,eAGF,uBACE,WAGF,mBAEE,aAGF,gBApDI,2CAuDF,OAGF,oBAEE,aAGF,+CAGE,cAGF,gCACE,iBAGF,MACE,kCAGF,iBACE,aAEA,0BACE,mBAMN,kCAEE,KACE,kBAGF,cACE,YJv0CY,MI20CZ,0BACE,gBAIJ,cACE,aAGF,OACE,UJ70Ce,MIg1CjB,uBACE,UJ90CqB,OI+0CrB,iCAIA,QACE,gBAIJ,iCACE,cAIF,aACE,SACA,gBAGF,cACE,iBAKJ,yDACE,oCACE,aACA,eAKJ,yDACE,oBACE,gBAGF,YACE,UACA,gBACA,uBACA,oBACA,oBAKJ,mCACE,eACE,aAGF,iCACE,mCAMJ,mCACE,OACE,kBAGF,cACE,+BAGF,wBACE,UAEA,uCACE,oBAGF,wCACE,mBAGF,kDACE,kBACA,YAIJ,SACE,mBAIJ,mCACE,aACE,+CAIJ,mCAGE,cACE,YJ97CkB,MIi8CpB,gBACE,KJl8CkB,MIq8CpB,OACE,oCAKF,yBACE,UJl8CqB,OIm8CrB,gCACA,iCAGF,0BAEE,gCAGF,aACE,8CAKF,SACE,MJ79CkB,MI+9ClB,0BACE,kBACA,qBACA,oBAIA,wBLx0CJ,aKy0CqB,QLx0CrB,cKw0CqB,QAInB,yBACE,qBACA,sBAEA,4CACE,aJ9+CQ,KIi/CV,sCL/1CJ,YKg2CqB,qBL/1CrB,aK+1CqB,sBGv/CvB,WACE,gBAGE,+BACE,qBAGF,0CACE,sBAIJ,iBACE,SACA,gBAEA,gEACE,kCAWF,4BACE,YACA,aAEA,wCAGE,sCACA,kBAGF,sFACE,yCAMA,iDAGE,gBACA,SAQA,2DACE,mBAIJ,0CAGE,cAGF,uDACE,cACA,mBACA,gBACA,uBAOV,YACE,wBACA,4BACA,6BAEA,oBACE,qBAIA,kCACE,sCACA,gBACA,oBACA,wBACA,yBACA,oBACA,SACA,yBAIA,yCACE,kDAMA,qDACE,mDAKN,gCACE,mBAEA,2CACE,4BAOR,kCACE,gEACE,kCAKE,4BACE,wCAEA,uCACE,2BAKE,2DACE,qBAUd,kCAGM,0DACE,cAOR,kCACE,WACE,kBAGF,YACE,iBACA,uBAGE,wCACE,mBAIJ,wBACE,cCrLN,qDACE,UACA,kBACA,qCAUA,gCANA,YACA,aAFc,OAGd,cAH4B,OAiB1B,mBACE,cAKN,mBAGE,gBACA,kDAEA,oCACE,mBAmBF,kCACE,sBACA,yBACA,sBACA,qBACA,iBAEA,kGACE,mBAGF,+CACE,aAEA,iDACE,6BAKF,iDAGE,kBASF,sDACE,UACA,YACA,oBAQR,gBAEE,wCACA,iDACA,+DACA,6DACA,6DACA,qDACA,wCAEA,eAGF,WACE,iBAeE,kCACE,wBAIA,mDACE,cAIJ,+BAGE,oBACA,mBACA,gBACA,WAGF,yDACE,gBAGF,8BACE,8BACA,iBACA,yBACA,yBAGF,kCACE,kCACA,UAGF,iCACE,kCACA,WAIJ,mBACE,iBACA,mBACA,iBACA,mBAIJ,qBAEI,oDAEE,iCAKN,2BACE,KACE,UACA,kBACA,SAGF,GACE,UACA,kBACA,OAIJ,mBACE,KACE,UACA,kBACA,SAGF,GACE,UACA,kBACA,OAIJ,aACE,4CACA,wBACA,gBACA,SACA,+BACA,8BACA,sBAEA,gBACE,gBACA,iBACA,iBACA,eAGE,oCACE,eAGF,qBACE,8BAMJ,0BACE,cACA,mBACA,gBACA,uBAEA,gCACE,2BACA,qBAGF,kCACE,aAIJ,gCACE,sCACA,gBAEA,wCACE,qBACA,UACA,UACA,eACA,iDAIJ,mBACE,oBAQJ,kBTxGA,MADwD,mBAExD,USwGiB,OTvGjB,YSuGyB,IAGzB,oBAIE,8BAGF,iBACE,gBACA,oBACA,gBACA,uBACA,oBACA,qBACA,4BAWJ,eACE,kBAGF,YACE,eAGF,yDACE,8CAGF,aTjJE,MSkJ6B,QTjJ7B,USiJe,QThJf,YSgJwB,IAExB,oBACE,YAIJ,kCACE,kBACE,kCAEA,kCACE,WACA,iBAKN,kCACE,eACE,6BAKJ,kCACE,iBACE,eACA,gBACA,oBACA,sBC9VJ,KACE,mBACA,oBACA,mBACA,iBACA,iBACA,8CACA,uCAEA,UACE,iBACA,eACA,8BCZJ,UACE,sBAIA,oFACE,WACA,MAJe,IAKf,kBACA,WACA,uCAGF,gBACE,cACA,iBACA,kBACA,SACA,iBAEA,wBAGE,YACA,UACA,YAGF,oCAGE,YACA,SAIF,uBACE,WACA,qBACA,kBACA,kBACA,WACA,YACA,YACA,iBACA,gDACA,qCACA,6BACA,UAKF,gBACE,iBACA,iBACA,mBACA,gBACA,uBAEA,+BACE,yCACA,uFAUF,wBAGE,MACA,UACA,cAIJ,8CACE,cAIJ,gBACE,mBACA,qBACA,kBACA,YAEA,sBACE,aACA,kBAGF,oBACE,cACA,4BAIJ,YAEE,mBACA,kBACA,UAEA,kBACE,mBAGF,oBAEE,WACA,qBACA,kBACA,kBACA,UACA,WACA,WACA,YACA,UACA,yCACA,6BACA,UAKN,kCACE,UACE,iBAEA,aACE,kBCxIN,cACE,WAGF,YACE,mBACA,sCAOA,yBAGE,eACA,cAHS,oBAIT,gBAEA,4CACE,4BACA,6BAIJ,cAGE,cAGF,6BACE,iBACA,kBACA,kBAEA,yCACE,yBACA,0BAGF,wCACE,gBAKN,kBACE,aACA,cACA,kBACA,kBACA,yBAEA,oBACE,kBACA,aACA,WACA,gCAIA,0BACE,yCAMN,qBACE,wBACE,6CAIJ,QACE,yBC7EF,MACE,2BACA,2CAKA,qCACE,mBACA,gBAGA,qDACE,gBACA,UACA,WACA,kBACA,cACA,WACA,kBACA,UACA,mBAIF,yCAGE,iBAMN,eACE,iBAGF,oBACE,kBAMA,iEAGE,mBAIJ,kCAIM,qDACE,eAGF,yCACE,mBACA,gBACA","sourcesContent":["/*\n* Mainly scss modules, only imported to `assets/css/main.scss`\n*/\n\n/* ---------- scss placeholder --------- */\n\n%heading {\n color: var(--heading-color);\n font-weight: 400;\n font-family: $font-family-heading;\n}\n\n%section {\n main & {\n margin-top: 2.5rem;\n margin-bottom: 1.25rem;\n\n &:focus {\n outline: none; /* avoid outline in Safari */\n }\n }\n}\n\n%anchor {\n .anchor {\n font-size: 80%;\n }\n\n @media (hover: hover) {\n .anchor {\n visibility: hidden;\n opacity: 0;\n transition: opacity 0.25s ease-in, visibility 0s ease-in 0.25s;\n }\n\n &:hover {\n .anchor {\n visibility: visible;\n opacity: 1;\n transition: opacity 0.25s ease-in, visibility 0s ease-in 0s;\n }\n }\n }\n}\n\n%tag-hover {\n background: var(--tag-hover);\n transition: background 0.35s ease-in-out;\n}\n\n%table-cell {\n padding: 0.4rem 1rem;\n font-size: 95%;\n white-space: nowrap;\n}\n\n%link-hover {\n color: #d2603a !important;\n border-bottom: 1px solid #d2603a;\n text-decoration: none;\n}\n\n%link-color {\n color: var(--link-color);\n}\n\n%link-underline {\n border-bottom: 1px solid var(--link-underline-color);\n}\n\n%clickable-transition {\n transition: all 0.3s ease-in-out;\n}\n\n%no-cursor {\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n%no-bottom-border {\n border-bottom: none;\n}\n\n%cursor-pointer {\n cursor: pointer;\n}\n\n%normal-font-style {\n font-style: normal;\n}\n\n%rounded {\n border-radius: $base-radius;\n}\n\n%img-caption {\n + em {\n display: block;\n text-align: center;\n font-style: normal;\n font-size: 80%;\n padding: 0;\n color: #6d6c6c;\n }\n}\n\n%sidebar-links {\n color: var(--sidebar-muted-color);\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n%text-clip {\n display: -webkit-box;\n overflow: hidden;\n text-overflow: ellipsis;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n%text-highlight {\n color: var(--text-muted-hightlight-color);\n font-weight: 600;\n}\n\n%text-sm {\n font-size: 0.85rem;\n}\n\n%text-xs {\n font-size: 0.8rem;\n}\n\n%sup-fn-target {\n &:target {\n background-color: var(--footnote-target-bg);\n width: -moz-fit-content;\n width: -webkit-fit-content;\n width: fit-content;\n transition: background-color 1.75s ease-in-out;\n }\n}\n\n/* ---------- scss mixin --------- */\n\n@mixin mt-mb($value) {\n margin-top: $value;\n margin-bottom: $value;\n}\n\n@mixin ml-mr($value) {\n margin-left: $value;\n margin-right: $value;\n}\n\n@mixin pt-pb($val) {\n padding-top: $val;\n padding-bottom: $val;\n}\n\n@mixin pl-pr($val) {\n padding-left: $val;\n padding-right: $val;\n}\n\n@mixin placeholder {\n color: var(--text-muted-color) !important;\n}\n\n@mixin placeholder-focus {\n opacity: 0.6;\n}\n\n@mixin label($font-size: 1rem, $font-weight: 600, $color: var(--label-color)) {\n color: $color;\n font-size: $font-size;\n font-weight: $font-weight;\n}\n\n@mixin align-center {\n position: relative;\n left: 50%;\n transform: translateX(-50%);\n}\n\n@mixin prompt($type, $fa-content, $fa-style: 'solid') {\n &.prompt-#{$type} {\n background-color: var(--prompt-#{$type}-bg);\n\n &::before {\n content: $fa-content;\n color: var(--prompt-#{$type}-icon-color);\n font: var(--fa-font-#{$fa-style});\n }\n }\n}\n","/*\n * The SCSS variables\n */\n\n/* sidebar */\n\n$sidebar-width: 260px !default; /* the basic width */\n$sidebar-width-large: 300px !default; /* screen width: >= 1650px */\n$sb-btn-gap: 0.8rem !default;\n$sb-btn-gap-lg: 1rem !default;\n\n/* other framework sizes */\n\n$topbar-height: 3rem !default;\n$search-max-width: 200px !default;\n$footer-height: 5rem !default;\n$footer-height-large: 6rem !default; /* screen width: < 850px */\n$main-content-max-width: 1250px !default;\n$base-radius: 0.625rem !default;\n$back2top-size: 2.75rem !default;\n\n/* syntax highlight */\n\n$code-font-size: 0.85rem !default;\n$code-header-height: 2.25rem !default;\n$code-dot-size: 0.75rem !default;\n$code-dot-gap: 0.5rem !default;\n$code-icon-width: 1.75rem !default;\n\n/* fonts */\n\n$font-family-base: 'Source Sans Pro', 'Microsoft Yahei', sans-serif !default;\n$font-family-heading: Lato, 'Microsoft Yahei', sans-serif !default;\n","/*\n* The syntax highlight.\n*/\n\n@import 'colors/syntax-light';\n@import 'colors/syntax-dark';\n\nhtml {\n @media (prefers-color-scheme: light) {\n &:not([data-mode]),\n &[data-mode='light'] {\n @include light-syntax;\n }\n\n &[data-mode='dark'] {\n @include dark-syntax;\n }\n }\n\n @media (prefers-color-scheme: dark) {\n &:not([data-mode]),\n &[data-mode='dark'] {\n @include dark-syntax;\n }\n\n &[data-mode='light'] {\n @include light-syntax;\n }\n }\n}\n\n/* -- code snippets -- */\n\n%code-snippet-bg {\n background-color: var(--highlight-bg-color);\n}\n\n%code-snippet-padding {\n padding-left: 1rem;\n padding-right: 1.5rem;\n}\n\n.highlighter-rouge {\n color: var(--highlighter-rouge-color);\n margin-top: 0.5rem;\n margin-bottom: 1.2em; /* Override BS Inline-code style */\n}\n\n.highlight {\n @extend %rounded;\n @extend %code-snippet-bg;\n\n @at-root figure#{&} {\n @extend %code-snippet-bg;\n }\n\n overflow: auto;\n padding-bottom: 0.75rem;\n\n pre {\n margin-bottom: 0;\n font-size: $code-font-size;\n line-height: 1.4rem;\n word-wrap: normal; /* Fixed Safari overflow-x */\n }\n\n table {\n td {\n &:first-child {\n display: inline-block;\n margin-left: 1rem;\n margin-right: 0.75rem;\n }\n\n &:last-child {\n padding-right: 2rem !important;\n }\n\n pre {\n overflow: visible; /* Fixed iOS safari overflow-x */\n word-break: normal; /* Fixed iOS safari linenos code break */\n }\n }\n }\n\n .lineno {\n text-align: right;\n color: var(--highlight-lineno-color);\n -webkit-user-select: none;\n -moz-user-select: none;\n -o-user-select: none;\n -ms-user-select: none;\n user-select: none;\n }\n} /* .highlight */\n\ncode {\n -webkit-hyphens: none;\n -ms-hyphens: none;\n hyphens: none;\n color: var(--code-color);\n\n &.highlighter-rouge {\n font-size: $code-font-size;\n padding: 3px 5px;\n word-break: break-word;\n border-radius: 4px;\n background-color: var(--inline-code-bg);\n }\n\n &.filepath {\n background-color: inherit;\n color: var(--filepath-text-color);\n font-weight: 600;\n padding: 0;\n }\n\n a > &.highlighter-rouge {\n padding-bottom: 0; /* show link's underlinke */\n color: inherit;\n }\n\n a:hover > &.highlighter-rouge {\n border-bottom: none;\n }\n\n blockquote & {\n color: inherit;\n }\n}\n\ntd.rouge-code {\n @extend %code-snippet-padding;\n\n /*\n Prevent some browser extends from\n changing the URL string of code block.\n */\n a {\n color: inherit !important;\n border-bottom: none !important;\n pointer-events: none;\n }\n}\n\ndiv[class^='language-'] {\n @extend %rounded;\n @extend %code-snippet-bg;\n\n box-shadow: var(--language-border-color) 0 0 0 1px;\n\n .content > & {\n @include ml-mr(-1rem);\n\n border-radius: 0;\n }\n\n .highlight {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n }\n}\n\n/* Hide line numbers for default, console, and terminal code snippets */\ndiv {\n &.nolineno,\n &.language-plaintext,\n &.language-console,\n &.language-terminal {\n td:first-child {\n padding: 0 !important;\n margin-right: 0;\n\n .lineno {\n display: none;\n }\n }\n }\n}\n\n.code-header {\n @extend %no-cursor;\n\n display: flex;\n justify-content: space-between;\n align-items: center;\n height: $code-header-height;\n margin-left: 0.75rem;\n margin-right: 0.25rem;\n\n /* the label block */\n span {\n line-height: $code-header-height;\n\n /* label icon */\n i {\n font-size: 1rem;\n width: $code-icon-width;\n color: var(--code-header-icon-color);\n\n &.small {\n font-size: 70%;\n }\n }\n\n @at-root [file] #{&} > i {\n position: relative;\n top: 1px; /* center the file icon */\n }\n\n /* label text */\n &::after {\n content: attr(data-label-text);\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--code-header-text-color);\n }\n }\n\n /* clipboard */\n button {\n @extend %cursor-pointer;\n @extend %rounded;\n\n border: 1px solid transparent;\n height: $code-header-height;\n width: $code-header-height;\n padding: 0;\n background-color: inherit;\n\n i {\n color: var(--code-header-icon-color);\n }\n\n &[timeout] {\n &:hover {\n border-color: var(--clipboard-checked-color);\n }\n\n i {\n color: var(--clipboard-checked-color);\n }\n }\n\n &:focus {\n outline: none;\n }\n\n &:not([timeout]):hover {\n background-color: rgba(128, 128, 128, 0.37);\n\n i {\n color: white;\n }\n }\n }\n}\n\n@media all and (min-width: 576px) {\n div[class^='language-'] {\n .content > & {\n @include ml-mr(0);\n\n border-radius: $base-radius;\n }\n\n .code-header {\n @include ml-mr(0);\n\n $dot-margin: 1rem;\n\n &::before {\n content: '';\n display: inline-block;\n margin-left: $dot-margin;\n width: $code-dot-size;\n height: $code-dot-size;\n border-radius: 50%;\n background-color: var(--code-header-muted-color);\n box-shadow: ($code-dot-size + $code-dot-gap) 0 0\n var(--code-header-muted-color),\n ($code-dot-size + $code-dot-gap) * 2 0 0\n var(--code-header-muted-color);\n }\n\n span {\n // center the text of label\n margin-left: calc(($dot-margin + $code-dot-size) / 2 * -1);\n }\n }\n }\n}\n","/*\n * The syntax light mode code snippet colors.\n */\n\n@mixin light-syntax {\n /* --- custom light colors --- */\n --language-border-color: #ececec;\n --highlight-bg-color: #f6f8fa;\n --highlighter-rouge-color: #3f596f;\n --highlight-lineno-color: #9e9e9e;\n --inline-code-bg: #f6f6f7;\n --code-color: #3a3a3a;\n --code-header-text-color: #a3a3a3;\n --code-header-muted-color: #e5e5e5;\n --code-header-icon-color: #c9c8c8;\n --clipboard-checked-color: #43c743;\n\n [class^='prompt-'] {\n --inline-code-bg: #fbfafa;\n }\n\n /* --- Syntax highlight theme from `rougify style github` --- */\n\n .highlight table td {\n padding: 5px;\n }\n\n .highlight table pre {\n margin: 0;\n }\n\n .highlight,\n .highlight .w {\n color: #24292f;\n background-color: #f6f8fa;\n }\n\n .highlight .k,\n .highlight .kd,\n .highlight .kn,\n .highlight .kp,\n .highlight .kr,\n .highlight .kt,\n .highlight .kv {\n color: #cf222e;\n }\n\n .highlight .gr {\n color: #f6f8fa;\n }\n\n .highlight .gd {\n color: #82071e;\n background-color: #ffebe9;\n }\n\n .highlight .nb {\n color: #953800;\n }\n\n .highlight .nc {\n color: #953800;\n }\n\n .highlight .no {\n color: #953800;\n }\n\n .highlight .nn {\n color: #953800;\n }\n\n .highlight .sr {\n color: #116329;\n }\n\n .highlight .na {\n color: #116329;\n }\n\n .highlight .nt {\n color: #116329;\n }\n\n .highlight .gi {\n color: #116329;\n background-color: #dafbe1;\n }\n\n .highlight .kc {\n color: #0550ae;\n }\n\n .highlight .l,\n .highlight .ld,\n .highlight .m,\n .highlight .mb,\n .highlight .mf,\n .highlight .mh,\n .highlight .mi,\n .highlight .il,\n .highlight .mo,\n .highlight .mx {\n color: #0550ae;\n }\n\n .highlight .sb {\n color: #0550ae;\n }\n\n .highlight .bp {\n color: #0550ae;\n }\n\n .highlight .ne {\n color: #0550ae;\n }\n\n .highlight .nl {\n color: #0550ae;\n }\n\n .highlight .py {\n color: #0550ae;\n }\n\n .highlight .nv,\n .highlight .vc,\n .highlight .vg,\n .highlight .vi,\n .highlight .vm {\n color: #0550ae;\n }\n\n .highlight .o,\n .highlight .ow {\n color: #0550ae;\n }\n\n .highlight .gh {\n color: #0550ae;\n font-weight: bold;\n }\n\n .highlight .gu {\n color: #0550ae;\n font-weight: bold;\n }\n\n .highlight .s,\n .highlight .sa,\n .highlight .sc,\n .highlight .dl,\n .highlight .sd,\n .highlight .s2,\n .highlight .se,\n .highlight .sh,\n .highlight .sx,\n .highlight .s1,\n .highlight .ss {\n color: #0a3069;\n }\n\n .highlight .nd {\n color: #8250df;\n }\n\n .highlight .nf,\n .highlight .fm {\n color: #8250df;\n }\n\n .highlight .err {\n color: #f6f8fa;\n background-color: #82071e;\n }\n\n .highlight .c,\n .highlight .ch,\n .highlight .cd,\n .highlight .cm,\n .highlight .cp,\n .highlight .cpf,\n .highlight .c1,\n .highlight .cs {\n color: #68717a;\n }\n\n .highlight .gl {\n color: #68717a;\n }\n\n .highlight .gt {\n color: #68717a;\n }\n\n .highlight .ni {\n color: #24292f;\n }\n\n .highlight .si {\n color: #24292f;\n }\n\n .highlight .ge {\n color: #24292f;\n font-style: italic;\n }\n\n .highlight .gs {\n color: #24292f;\n font-weight: bold;\n }\n} /* light-syntax */\n","/*\n * The syntax dark mode styles.\n */\n\n@mixin dark-syntax {\n --language-border-color: #2d2d2d;\n --highlight-bg-color: #151515;\n --highlighter-rouge-color: #c9def1;\n --highlight-lineno-color: #808080;\n --inline-code-bg: #323238;\n --code-color: #b0b0b0;\n --code-header-text-color: #6a6a6a;\n --code-header-muted-color: #353535;\n --code-header-icon-color: #565656;\n --clipboard-checked-color: #2bcc2b;\n --filepath-text-color: #cacaca;\n\n .highlight .gp {\n color: #87939d;\n }\n\n /* --- Syntax highlight theme from `rougify style base16.dark` --- */\n\n .highlight table td {\n padding: 5px;\n }\n\n .highlight table pre {\n margin: 0;\n }\n\n .highlight,\n .highlight .w {\n color: #d0d0d0;\n background-color: #151515;\n }\n\n .highlight .err {\n color: #151515;\n background-color: #ac4142;\n }\n\n .highlight .c,\n .highlight .ch,\n .highlight .cd,\n .highlight .cm,\n .highlight .cpf,\n .highlight .c1,\n .highlight .cs {\n color: #848484;\n }\n\n .highlight .cp {\n color: #f4bf75;\n }\n\n .highlight .nt {\n color: #f4bf75;\n }\n\n .highlight .o,\n .highlight .ow {\n color: #d0d0d0;\n }\n\n .highlight .p,\n .highlight .pi {\n color: #d0d0d0;\n }\n\n .highlight .gi {\n color: #90a959;\n }\n\n .highlight .gd {\n color: #f08a8b;\n background-color: #320000;\n }\n\n .highlight .gh {\n color: #6a9fb5;\n background-color: #151515;\n font-weight: bold;\n }\n\n .highlight .k,\n .highlight .kn,\n .highlight .kp,\n .highlight .kr,\n .highlight .kv {\n color: #aa759f;\n }\n\n .highlight .kc {\n color: #d28445;\n }\n\n .highlight .kt {\n color: #d28445;\n }\n\n .highlight .kd {\n color: #d28445;\n }\n\n .highlight .s,\n .highlight .sb,\n .highlight .sc,\n .highlight .dl,\n .highlight .sd,\n .highlight .s2,\n .highlight .sh,\n .highlight .sx,\n .highlight .s1 {\n color: #90a959;\n }\n\n .highlight .sa {\n color: #aa759f;\n }\n\n .highlight .sr {\n color: #75b5aa;\n }\n\n .highlight .si {\n color: #b76d45;\n }\n\n .highlight .se {\n color: #b76d45;\n }\n\n .highlight .nn {\n color: #f4bf75;\n }\n\n .highlight .nc {\n color: #f4bf75;\n }\n\n .highlight .no {\n color: #f4bf75;\n }\n\n .highlight .na {\n color: #6a9fb5;\n }\n\n .highlight .m,\n .highlight .mb,\n .highlight .mf,\n .highlight .mh,\n .highlight .mi,\n .highlight .il,\n .highlight .mo,\n .highlight .mx {\n color: #90a959;\n }\n\n .highlight .ss {\n color: #90a959;\n }\n}\n","/* The common styles */\n\nhtml {\n @media (prefers-color-scheme: light) {\n &:not([data-mode]),\n &[data-mode='light'] {\n @include light-scheme;\n }\n\n &[data-mode='dark'] {\n @include dark-scheme;\n }\n }\n\n @media (prefers-color-scheme: dark) {\n &:not([data-mode]),\n &[data-mode='dark'] {\n @include dark-scheme;\n }\n\n &[data-mode='light'] {\n @include light-scheme;\n }\n }\n\n font-size: 16px;\n}\n\nbody {\n background: var(--main-bg);\n padding: env(safe-area-inset-top) env(safe-area-inset-right)\n env(safe-area-inset-bottom) env(safe-area-inset-left);\n color: var(--text-color);\n -webkit-font-smoothing: antialiased;\n font-family: $font-family-base;\n}\n\n/* --- Typography --- */\n\n@for $i from 1 through 5 {\n h#{$i} {\n @extend %heading;\n\n @if $i > 1 {\n @extend %section;\n @extend %anchor;\n }\n\n @if $i < 5 {\n $factor: 0.18rem;\n\n @if $i == 1 {\n $factor: 0.23rem;\n }\n\n font-size: 1rem + (5 - $i) * $factor;\n } @else {\n font-size: 1rem;\n }\n }\n}\n\na {\n @extend %link-color;\n\n text-decoration: none;\n}\n\nimg {\n max-width: 100%;\n height: auto;\n transition: all 0.35s ease-in-out;\n\n .blur & {\n $blur: 20px;\n\n -webkit-filter: blur($blur);\n filter: blur($blur);\n }\n}\n\nblockquote {\n border-left: 5px solid var(--blockquote-border-color);\n padding-left: 1rem;\n color: var(--blockquote-text-color);\n\n > p:last-child {\n margin-bottom: 0;\n }\n\n &[class^='prompt-'] {\n border-left: 0;\n position: relative;\n padding: 1rem 1rem 1rem 3rem;\n color: var(--prompt-text-color);\n\n @extend %rounded;\n\n &::before {\n text-align: center;\n width: 3rem;\n position: absolute;\n left: 0.25rem;\n margin-top: 0.4rem;\n text-rendering: auto;\n -webkit-font-smoothing: antialiased;\n }\n }\n\n @include prompt('tip', '\\f0eb', 'regular');\n @include prompt('info', '\\f06a');\n @include prompt('warning', '\\f06a');\n @include prompt('danger', '\\f071');\n}\n\nkbd {\n font-family: inherit;\n display: inline-block;\n vertical-align: middle;\n line-height: 1.3rem;\n min-width: 1.75rem;\n text-align: center;\n margin: 0 0.3rem;\n padding-top: 0.1rem;\n color: var(--kbd-text-color);\n background-color: var(--kbd-bg-color);\n border-radius: 0.25rem;\n border: solid 1px var(--kbd-wrap-color);\n box-shadow: inset 0 -2px 0 var(--kbd-wrap-color);\n}\n\nfooter {\n background-color: var(--main-bg);\n height: $footer-height;\n border-top: 1px solid var(--main-border-color);\n\n @extend %text-xs;\n\n a {\n @extend %text-highlight;\n\n &:hover {\n @extend %link-hover;\n }\n }\n\n p {\n text-align: center;\n margin-bottom: 0;\n }\n}\n\n/* fontawesome icons */\ni {\n &.far,\n &.fas {\n @extend %no-cursor;\n }\n}\n\n/* --- Panels --- */\n\n.access {\n top: 2rem;\n transition: top 0.2s ease-in-out;\n margin-top: 3rem;\n margin-bottom: 4rem;\n\n &:only-child {\n position: -webkit-sticky;\n position: sticky;\n }\n\n > section {\n padding-left: 1rem;\n border-left: 1px solid var(--main-border-color);\n\n &:not(:last-child) {\n margin-bottom: 4rem;\n }\n }\n\n .content {\n font-size: 0.9rem;\n }\n}\n\n#panel-wrapper {\n /* the headings */\n .panel-heading {\n font-family: inherit;\n line-height: inherit;\n\n @include label(inherit);\n }\n\n .post-tag {\n line-height: 1.05rem;\n font-size: 0.85rem;\n border-radius: 0.8rem;\n padding: 0.3rem 0.5rem;\n margin: 0 0.35rem 0.5rem 0;\n\n &:hover {\n transition: all 0.3s ease-in;\n }\n }\n}\n\n#access-lastmod {\n a {\n &:hover {\n @extend %link-hover;\n }\n\n @extend %no-bottom-border;\n\n color: inherit;\n }\n}\n\n.footnotes > ol {\n padding-left: 2rem;\n margin-top: 0.5rem;\n\n > li {\n &:not(:last-child) {\n margin-bottom: 0.3rem;\n }\n\n @extend %sup-fn-target;\n\n > p {\n margin-left: 0.25em;\n margin-top: 0;\n margin-bottom: 0;\n }\n }\n}\n\n.footnote {\n @at-root a#{&} {\n @include ml-mr(1px);\n @include pl-pr(2px);\n\n border-bottom-style: none !important;\n }\n}\n\nsup {\n @extend %sup-fn-target;\n}\n\n.reversefootnote {\n @at-root a#{&} {\n font-size: 0.6rem;\n line-height: 1;\n position: relative;\n bottom: 0.25em;\n margin-left: 0.25em;\n border-bottom-style: none !important;\n }\n}\n\n/* --- Begin of Markdown table style --- */\n\n/* it will be created by Liquid */\n.table-wrapper {\n overflow-x: auto;\n margin-bottom: 1.5rem;\n\n > table {\n min-width: 100%;\n overflow-x: auto;\n border-spacing: 0;\n\n thead {\n border-bottom: solid 2px rgba(210, 215, 217, 0.75);\n\n th {\n @extend %table-cell;\n }\n }\n\n tbody {\n tr {\n border-bottom: 1px solid var(--tb-border-color);\n\n &:nth-child(2n) {\n background-color: var(--tb-even-bg);\n }\n\n &:nth-child(2n + 1) {\n background-color: var(--tb-odd-bg);\n }\n\n td {\n @extend %table-cell;\n }\n }\n } /* tbody */\n } /* table */\n}\n\n/* --- post --- */\n\n.preview-img {\n aspect-ratio: 40 / 21;\n width: 100%;\n height: 100%;\n overflow: hidden;\n\n @extend %rounded;\n\n &:not(.no-bg) {\n background: var(--img-bg);\n }\n\n img {\n height: 100%;\n -o-object-fit: cover;\n object-fit: cover;\n\n @extend %rounded;\n\n @at-root #post-list & {\n width: 100%;\n }\n }\n}\n\n.post-preview {\n @extend %rounded;\n\n border: 0;\n background: var(--card-bg);\n box-shadow: var(--card-shadow);\n\n &::before {\n @extend %rounded;\n\n content: '';\n width: 100%;\n height: 100%;\n position: absolute;\n background-color: var(--card-hovor-bg);\n opacity: 0;\n transition: opacity 0.35s ease-in-out;\n }\n\n &:hover {\n &::before {\n opacity: 0.3;\n }\n }\n}\n\nmain {\n line-height: 1.75;\n\n h1 {\n margin-top: 2rem;\n margin-bottom: 1.5rem;\n }\n\n p {\n > a.popup {\n &:not(.normal):not(.left):not(.right) {\n @include align-center;\n }\n }\n }\n\n .categories,\n #tags,\n #archives {\n a:not(:hover) {\n @extend %no-bottom-border;\n }\n }\n}\n\n.post-meta {\n @extend %text-sm;\n\n a {\n &:not([class]):hover {\n @extend %link-hover;\n }\n }\n\n em {\n @extend %normal-font-style;\n }\n}\n\n.content {\n font-size: 1.08rem;\n margin-top: 2rem;\n overflow-wrap: break-word;\n\n a {\n &.popup {\n @extend %no-cursor;\n @extend %img-caption;\n @include mt-mb(0.5rem);\n\n cursor: zoom-in;\n }\n\n &:not(.img-link) {\n @extend %link-underline;\n\n &:hover {\n @extend %link-hover;\n }\n }\n }\n\n ol,\n ul {\n &:not([class]),\n &.task-list {\n -webkit-padding-start: 1.75rem;\n padding-inline-start: 1.75rem;\n\n li {\n margin: 0.25rem 0;\n padding-left: 0.25rem;\n }\n\n ol,\n ul {\n -webkit-padding-start: 1.25rem;\n padding-inline-start: 1.25rem;\n margin: 0.5rem 0;\n }\n }\n }\n\n ul.task-list {\n -webkit-padding-start: 1.25rem;\n padding-inline-start: 1.25rem;\n\n li {\n list-style-type: none;\n padding-left: 0;\n\n /* checkbox icon */\n > i {\n width: 2rem;\n margin-left: -1.25rem;\n color: var(--checkbox-color);\n\n &.checked {\n color: var(--checkbox-checked-color);\n }\n }\n\n ul {\n -webkit-padding-start: 1.75rem;\n padding-inline-start: 1.75rem;\n }\n }\n\n input[type='checkbox'] {\n margin: 0 0.5rem 0.2rem -1.3rem;\n vertical-align: middle;\n }\n } /* ul */\n\n dl > dd {\n margin-left: 1rem;\n }\n\n ::marker {\n color: var(--text-muted-color);\n }\n} /* .content */\n\n.tag:hover {\n @extend %tag-hover;\n}\n\n.post-tag {\n display: inline-block;\n min-width: 2rem;\n text-align: center;\n border-radius: 0.5rem;\n border: 1px solid var(--btn-border-color);\n padding: 0 0.4rem;\n color: var(--text-muted-color);\n line-height: 1.3rem;\n\n &:not(:last-child) {\n margin-right: 0.2rem;\n }\n}\n\n.rounded-10 {\n border-radius: 10px !important;\n}\n\n.img-link {\n color: transparent;\n display: inline-flex;\n}\n\n.shimmer {\n overflow: hidden;\n position: relative;\n background: var(--img-bg);\n\n &::before {\n content: '';\n position: absolute;\n background: var(--shimmer-bg);\n height: 100%;\n width: 100%;\n -webkit-animation: shimmer 1.3s infinite;\n animation: shimmer 1.3s infinite;\n }\n\n @-webkit-keyframes shimmer {\n 0% {\n transform: translateX(-100%);\n }\n\n 100% {\n transform: translateX(100%);\n }\n }\n\n @keyframes shimmer {\n 0% {\n transform: translateX(-100%);\n }\n\n 100% {\n transform: translateX(100%);\n }\n }\n}\n\n.embed-video {\n width: 100%;\n height: 100%;\n margin-bottom: 1rem;\n\n @extend %rounded;\n\n &.youtube,\n &.bilibili {\n aspect-ratio: 16 / 9;\n }\n\n &.twitch {\n aspect-ratio: 310 / 189;\n }\n}\n\n/* --- buttons --- */\n.btn-lang {\n border: 1px solid !important;\n padding: 1px 3px;\n border-radius: 3px;\n color: var(--link-color);\n\n &:focus {\n box-shadow: none;\n }\n}\n\n/* --- Effects classes --- */\n\n.loaded {\n display: block !important;\n\n @at-root .d-flex#{&} {\n display: flex !important;\n }\n}\n\n.unloaded {\n display: none !important;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.hidden {\n visibility: hidden !important;\n}\n\n.flex-grow-1 {\n flex-grow: 1 !important;\n}\n\n.btn-box-shadow {\n box-shadow: var(--card-shadow);\n}\n\n/* overwrite bootstrap muted */\n.text-muted {\n color: var(--text-muted-color) !important;\n}\n\n/* Overwrite bootstrap tooltip */\n.tooltip-inner {\n font-size: 0.7rem;\n max-width: 220px;\n text-align: left;\n}\n\n/* Overwrite bootstrap outline button */\n.btn.btn-outline-primary {\n &:not(.disabled):hover {\n border-color: #007bff !important;\n }\n}\n\n.disabled {\n color: rgb(206, 196, 196);\n pointer-events: auto;\n cursor: not-allowed;\n}\n\n.hide-border-bottom {\n border-bottom: none !important;\n}\n\n.input-focus {\n box-shadow: none;\n border-color: var(--input-focus-border-color) !important;\n background: center !important;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;\n}\n\n.left {\n float: left;\n margin: 0.75rem 1rem 1rem 0;\n}\n\n.right {\n float: right;\n margin: 0.75rem 0 1rem 1rem;\n}\n\n/* --- Overriding --- */\n\n/* magnific-popup */\n\nfigure .mfp-title {\n text-align: center;\n padding-right: 0;\n margin-top: 0.5rem;\n}\n\n.mfp-img {\n transition: none;\n}\n\n/* mermaid */\n.mermaid {\n text-align: center;\n}\n\n/* MathJax */\nmjx-container {\n overflow-y: hidden;\n min-width: auto !important;\n}\n\n/* --- sidebar layout --- */\n\n$sidebar-display: 'sidebar-display';\n$btn-border-width: 3px;\n$btn-mb: 0.5rem;\n\n#sidebar {\n @include pl-pr(0);\n\n position: fixed;\n top: 0;\n left: 0;\n height: 100%;\n overflow-y: auto;\n width: $sidebar-width;\n z-index: 99;\n background: var(--sidebar-bg);\n border-right: 1px solid var(--sidebar-border-color);\n\n /* Hide scrollbar for Chrome, Safari and Opera */\n &::-webkit-scrollbar {\n display: none;\n }\n\n /* Hide scrollbar for IE, Edge and Firefox */\n -ms-overflow-style: none; /* IE and Edge */\n scrollbar-width: none; /* Firefox */\n\n %sidebar-link-hover {\n &:hover {\n color: var(--sidebar-active-color);\n }\n }\n\n a {\n @extend %sidebar-links;\n }\n\n #avatar {\n display: block;\n width: 7rem;\n height: 7rem;\n overflow: hidden;\n box-shadow: var(--avatar-border-color) 0 0 0 2px;\n transform: translateZ(0); /* fixed the zoom in Safari */\n\n img {\n transition: transform 0.5s;\n\n &:hover {\n transform: scale(1.2);\n }\n }\n }\n\n .profile-wrapper {\n @include mt-mb(2.5rem);\n @extend %clickable-transition;\n\n padding-left: 2.5rem;\n padding-right: 1.25rem;\n width: 100%;\n }\n\n .site-title {\n font-family: inherit;\n font-weight: 900;\n font-size: 1.75rem;\n line-height: 1.2;\n letter-spacing: 0.25px;\n margin-top: 1.25rem;\n margin-bottom: 0.5rem;\n\n a {\n @extend %clickable-transition;\n @extend %sidebar-link-hover;\n\n color: var(--site-title-color);\n }\n }\n\n .site-subtitle {\n font-size: 95%;\n color: var(--site-subtitle-color);\n margin-top: 0.25rem;\n word-spacing: 1px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n }\n\n ul {\n margin-bottom: 2rem;\n\n li.nav-item {\n opacity: 0.9;\n width: 100%;\n padding-left: 1.5rem;\n padding-right: 1.5rem;\n\n a.nav-link {\n @include pt-pb(0.6rem);\n\n display: flex;\n align-items: center;\n border-radius: 0.75rem;\n font-weight: 600;\n\n &:hover {\n background-color: var(--sidebar-hover-bg);\n }\n\n i {\n font-size: 95%;\n opacity: 0.8;\n margin-right: 1.5rem;\n }\n\n span {\n font-size: 90%;\n letter-spacing: 0.2px;\n }\n }\n\n &.active {\n .nav-link {\n color: var(--sidebar-active-color);\n background-color: var(--sidebar-hover-bg);\n\n span {\n opacity: 1;\n }\n }\n }\n\n &:not(:first-child) {\n margin-top: 0.25rem;\n }\n }\n }\n\n .sidebar-bottom {\n padding-left: 2rem;\n padding-right: 1rem;\n margin-bottom: 1.5rem;\n\n $btn-size: 1.75rem;\n\n %button {\n width: $btn-size;\n height: $btn-size;\n margin-bottom: $btn-mb; // multi line gap\n border-radius: 50%;\n color: var(--sidebar-btn-color);\n background-color: var(--sidebar-btn-bg);\n text-align: center;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: var(--sidebar-border-color) 0 0 0 1px;\n\n &:hover {\n background-color: var(--sidebar-hover-bg);\n }\n }\n\n a {\n @extend %button;\n @extend %sidebar-link-hover;\n @extend %clickable-transition;\n\n &:not(:last-child) {\n margin-right: $sb-btn-gap;\n }\n }\n\n i {\n line-height: $btn-size;\n }\n\n .mode-toggle {\n padding: 0;\n border: 0;\n\n @extend %button;\n @extend %sidebar-links;\n @extend %sidebar-link-hover;\n }\n\n .icon-border {\n @extend %no-cursor;\n @include ml-mr(calc(($sb-btn-gap - $btn-border-width) / 2));\n\n background-color: var(--sidebar-btn-color);\n content: '';\n width: $btn-border-width;\n height: $btn-border-width;\n border-radius: 50%;\n margin-bottom: $btn-mb;\n }\n } /* .sidebar-bottom */\n} /* #sidebar */\n\n@media (hover: hover) {\n #sidebar ul > li:last-child::after {\n transition: top 0.5s ease;\n }\n\n .nav-link {\n transition: background-color 0.3s ease-in-out;\n }\n\n .post-preview {\n transition: background-color 0.35s ease-in-out;\n }\n}\n\n#search-result-wrapper {\n display: none;\n height: 100%;\n width: 100%;\n overflow: auto;\n\n .content {\n margin-top: 2rem;\n }\n}\n\n/* --- top-bar --- */\n\n#topbar-wrapper {\n height: $topbar-height;\n background-color: var(--topbar-bg);\n}\n\n#topbar {\n button i {\n color: #999999;\n }\n\n #breadcrumb {\n font-size: 1rem;\n color: var(--text-muted-color);\n padding-left: 0.5rem;\n\n a:hover {\n @extend %link-hover;\n }\n\n span {\n &:not(:last-child) {\n &::after {\n content: '›';\n padding: 0 0.3rem;\n }\n }\n }\n }\n} /* #topbar */\n\n::-webkit-input-placeholder {\n @include placeholder;\n}\n\n::-moz-placeholder {\n @include placeholder;\n}\n\n:-ms-input-placeholder {\n @include placeholder;\n}\n\n::-ms-input-placeholder {\n @include placeholder;\n}\n\n::placeholder {\n @include placeholder;\n}\n\n:focus::-webkit-input-placeholder {\n @include placeholder-focus;\n}\n\n:focus::-moz-placeholder {\n @include placeholder-focus;\n}\n\n:focus:-ms-input-placeholder {\n @include placeholder-focus;\n}\n\n:focus::-ms-input-placeholder {\n @include placeholder-focus;\n}\n\n:focus::placeholder {\n @include placeholder-focus;\n}\n\nsearch {\n display: flex;\n width: 100%;\n border-radius: 1rem;\n border: 1px solid var(--search-border-color);\n background: var(--main-bg);\n padding: 0 0.5rem;\n\n i {\n z-index: 2;\n font-size: 0.9rem;\n color: var(--search-icon-color);\n }\n}\n\n#sidebar-trigger,\n#search-trigger {\n display: none;\n}\n\n/* 'Cancel' link */\n#search-cancel {\n color: var(--link-color);\n display: none;\n white-space: nowrap;\n\n @extend %cursor-pointer;\n}\n\n#search-input {\n background: center;\n border: 0;\n border-radius: 0;\n padding: 0.18rem 0.3rem;\n color: var(--text-color);\n height: auto;\n\n &:focus {\n box-shadow: none;\n }\n}\n\n#search-hints {\n padding: 0 1rem;\n\n h4 {\n margin-bottom: 1.5rem;\n }\n\n .post-tag {\n display: inline-block;\n line-height: 1rem;\n font-size: 1rem;\n background: var(--search-tag-bg);\n border: none;\n padding: 0.5rem;\n margin: 0 1.25rem 1rem 0;\n\n &::before {\n content: '#';\n color: var(--text-muted-color);\n padding-right: 0.2rem;\n }\n\n @extend %link-color;\n }\n}\n\n#search-results {\n padding-bottom: 3rem;\n\n a {\n &:hover {\n @extend %link-hover;\n }\n\n @extend %link-color;\n @extend %no-bottom-border;\n @extend %heading;\n\n font-size: 1.4rem;\n line-height: 2.5rem;\n }\n\n > article {\n width: 100%;\n\n &:not(:last-child) {\n margin-bottom: 1rem;\n }\n\n /* icons */\n i {\n color: #818182;\n margin-right: 0.15rem;\n font-size: 80%;\n }\n\n > p {\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n }\n }\n} /* #search-results */\n\n#topbar-title {\n display: none;\n font-size: 1.1rem;\n font-weight: 600;\n font-family: sans-serif;\n color: var(--topbar-text-color);\n text-align: center;\n width: 70%;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: keep-all;\n white-space: nowrap;\n}\n\n#mask {\n display: none;\n position: fixed;\n inset: 0 0 0 0;\n height: 100%;\n width: 100%;\n z-index: 1;\n\n @at-root [#{$sidebar-display}] & {\n display: block !important;\n }\n}\n\n/* --- basic wrappers --- */\n\n#main-wrapper {\n position: relative;\n\n @include pl-pr(0);\n\n > .container {\n min-height: 100vh;\n }\n}\n\n#topbar-wrapper.row,\n#main-wrapper > .container > .row,\n#search-result-wrapper > .row {\n @include ml-mr(0);\n}\n\n#tail-wrapper {\n > :not(script) {\n margin-top: 3rem;\n }\n}\n\n/* --- button back-to-top --- */\n\n#back-to-top {\n display: none;\n z-index: 1;\n cursor: pointer;\n position: fixed;\n right: 1rem;\n bottom: calc($footer-height-large - $back2top-size / 2);\n background: var(--button-bg);\n color: var(--btn-backtotop-color);\n padding: 0;\n width: $back2top-size;\n height: $back2top-size;\n border-radius: 50%;\n border: 1px solid var(--btn-backtotop-border-color);\n transition: transform 0.2s ease-out;\n -webkit-transition: transform 0.2s ease-out;\n\n &:hover {\n transform: translate3d(0, -5px, 0);\n -webkit-transform: translate3d(0, -5px, 0);\n }\n\n i {\n line-height: $back2top-size;\n position: relative;\n bottom: 2px;\n }\n}\n\n#notification {\n @-webkit-keyframes popup {\n from {\n opacity: 0;\n bottom: 0;\n }\n }\n\n @keyframes popup {\n from {\n opacity: 0;\n bottom: 0;\n }\n }\n\n .toast-header {\n background: none;\n border-bottom: none;\n color: inherit;\n }\n\n .toast-body {\n font-family: Lato, sans-serif;\n line-height: 1.25rem;\n\n button {\n font-size: 90%;\n min-width: 4rem;\n }\n }\n\n &.toast {\n &.show {\n display: block;\n min-width: 20rem;\n border-radius: 0.5rem;\n -webkit-backdrop-filter: blur(10px);\n backdrop-filter: blur(10px);\n background-color: rgba(255, 255, 255, 0.5);\n color: #1b1b1eba;\n position: fixed;\n left: 50%;\n bottom: 20%;\n transform: translateX(-50%);\n -webkit-animation: popup 0.8s;\n animation: popup 0.8s;\n }\n }\n}\n\n/*\n Responsive Design:\n\n {sidebar, content, panel} >= 1200px screen width\n {sidebar, content} >= 850px screen width\n {content} <= 849px screen width\n\n*/\n\n@media all and (max-width: 576px) {\n main {\n .content {\n > blockquote[class^='prompt-'] {\n @include ml-mr(-1rem);\n\n border-radius: 0;\n max-width: none;\n }\n }\n }\n\n #avatar {\n width: 5rem;\n height: 5rem;\n }\n}\n\n@media all and (max-width: 768px) {\n %full-width {\n max-width: 100%;\n }\n\n #topbar {\n @extend %full-width;\n }\n\n #main-wrapper > .container {\n @extend %full-width;\n @include pl-pr(0);\n }\n}\n\n/* hide sidebar and panel */\n@media all and (max-width: 849px) {\n @mixin slide($append: null) {\n $basic: transform 0.4s ease;\n\n @if $append {\n transition: $basic, $append;\n } @else {\n transition: $basic;\n }\n }\n\n footer {\n @include slide;\n\n height: $footer-height-large;\n padding: 1.5rem 0;\n }\n\n [#{$sidebar-display}] {\n #sidebar {\n transform: translateX(0);\n }\n\n #main-wrapper {\n transform: translateX($sidebar-width);\n }\n\n #back-to-top {\n visibility: hidden;\n }\n }\n\n #sidebar {\n @include slide;\n\n transform: translateX(-$sidebar-width); /* hide */\n -webkit-transform: translateX(-$sidebar-width);\n }\n\n #main-wrapper {\n @include slide;\n }\n\n #topbar,\n #main-wrapper > .container {\n max-width: 100%;\n }\n\n #search-result-wrapper {\n width: 100%;\n }\n\n #breadcrumb,\n search {\n display: none;\n }\n\n #topbar-wrapper {\n @include slide(top 0.2s ease);\n\n left: 0;\n }\n\n main,\n #panel-wrapper {\n margin-top: 0;\n }\n\n #topbar-title,\n #sidebar-trigger,\n #search-trigger {\n display: block;\n }\n\n #search-result-wrapper .content {\n letter-spacing: 0;\n }\n\n #tags {\n justify-content: center !important;\n }\n\n h1.dynamic-title {\n display: none;\n\n ~ .content {\n margin-top: 2.5rem;\n }\n }\n} /* max-width: 849px */\n\n/* Sidebar is visible */\n@media all and (min-width: 850px) {\n /* Solved jumping scrollbar */\n html {\n overflow-y: scroll;\n }\n\n #main-wrapper {\n margin-left: $sidebar-width;\n }\n\n #sidebar {\n .profile-wrapper {\n margin-top: 3rem;\n }\n }\n\n #search-hints {\n display: none;\n }\n\n search {\n max-width: $search-max-width;\n }\n\n #search-result-wrapper {\n max-width: $main-content-max-width;\n justify-content: start !important;\n }\n\n main {\n h1 {\n margin-top: 3rem;\n }\n }\n\n div.content .table-wrapper > table {\n min-width: 70%;\n }\n\n /* button 'back-to-Top' position */\n #back-to-top {\n right: 5%;\n bottom: calc($footer-height - $back2top-size / 2);\n }\n\n #topbar-title {\n text-align: left;\n }\n}\n\n/* Pad horizontal */\n@media all and (min-width: 992px) and (max-width: 1199px) {\n #main-wrapper > .container .col-lg-11 {\n flex: 0 0 96%;\n max-width: 96%;\n }\n}\n\n/* Compact icons in sidebar & panel hidden */\n@media all and (min-width: 850px) and (max-width: 1199px) {\n #search-results > div {\n max-width: 700px;\n }\n\n #breadcrumb {\n width: 65%;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: keep-all;\n white-space: nowrap;\n }\n}\n\n/* panel hidden */\n@media all and (max-width: 1199px) {\n #panel-wrapper {\n display: none;\n }\n\n #main-wrapper > .container > div.row {\n justify-content: center !important;\n }\n}\n\n/* --- desktop mode, both sidebar and panel are visible --- */\n\n@media all and (min-width: 1200px) {\n search {\n margin-right: 4rem;\n }\n\n #search-input {\n transition: all 0.3s ease-in-out;\n }\n\n #search-results > article {\n width: 45%;\n\n &:nth-child(odd) {\n margin-right: 1.5rem;\n }\n\n &:nth-child(even) {\n margin-left: 1.5rem;\n }\n\n &:last-child:nth-child(odd) {\n position: relative;\n right: 24.3%;\n }\n }\n\n .content {\n font-size: 1.03rem;\n }\n}\n\n@media all and (min-width: 1400px) {\n #back-to-top {\n right: calc((100vw - $sidebar-width - 1140px) / 2 + 3rem);\n }\n}\n\n@media all and (min-width: 1650px) {\n $icon-gap: 1rem;\n\n #main-wrapper {\n margin-left: $sidebar-width-large;\n }\n\n #topbar-wrapper {\n left: $sidebar-width-large;\n }\n\n search {\n margin-right: calc(\n $main-content-max-width / 4 - $search-max-width - 0.75rem\n );\n }\n\n #main-wrapper > .container {\n max-width: $main-content-max-width;\n padding-left: 1.75rem !important;\n padding-right: 1.75rem !important;\n }\n\n main.col-12,\n #tail-wrapper {\n padding-right: 4.5rem !important;\n }\n\n #back-to-top {\n right: calc(\n (100vw - $sidebar-width-large - $main-content-max-width) / 2 + 2rem\n );\n }\n\n #sidebar {\n width: $sidebar-width-large;\n\n .profile-wrapper {\n margin-top: 3.5rem;\n margin-bottom: 2.5rem;\n padding-left: 3.5rem;\n }\n\n ul {\n li.nav-item {\n @include pl-pr(2.75rem);\n }\n }\n\n .sidebar-bottom {\n padding-left: 2.75rem;\n margin-bottom: 1.75rem;\n\n a:not(:last-child) {\n margin-right: $sb-btn-gap-lg;\n }\n\n .icon-border {\n @include ml-mr(calc(($sb-btn-gap-lg - $btn-border-width) / 2));\n }\n }\n }\n} /* min-width: 1650px */\n","/*\n * The syntax light mode typography colors\n */\n\n@mixin light-scheme {\n /* Framework color */\n --main-bg: white;\n --mask-bg: #c1c3c5;\n --main-border-color: #f3f3f3;\n\n /* Common color */\n --text-color: #34343c;\n --text-muted-color: #757575;\n --text-muted-hightlight-color: inherit;\n --heading-color: #2a2a2a;\n --label-color: #585858;\n --blockquote-border-color: #eeeeee;\n --blockquote-text-color: #757575;\n --link-color: #0056b2;\n --link-underline-color: #dee2e6;\n --button-bg: #ffffff;\n --btn-border-color: #e9ecef;\n --btn-backtotop-color: #686868;\n --btn-backtotop-border-color: #f1f1f1;\n --btn-box-shadow: #eaeaea;\n --checkbox-color: #c5c5c5;\n --checkbox-checked-color: #07a8f7;\n --img-bg: radial-gradient(\n circle,\n rgb(255, 255, 255) 0%,\n rgb(239, 239, 239) 100%\n );\n --shimmer-bg: linear-gradient(\n 90deg,\n rgba(250, 250, 250, 0) 0%,\n rgba(232, 230, 230, 1) 50%,\n rgba(250, 250, 250, 0) 100%\n );\n\n /* Sidebar */\n --site-title-color: rgb(113, 113, 113);\n --site-subtitle-color: #717171;\n --sidebar-bg: #f6f8fa;\n --sidebar-border-color: #efefef;\n --sidebar-muted-color: #545454;\n --sidebar-active-color: #1d1d1d;\n --sidebar-hover-bg: rgb(223, 233, 241, 0.64);\n --sidebar-btn-bg: white;\n --sidebar-btn-color: #8e8e8e;\n --avatar-border-color: white;\n\n /* Topbar */\n --topbar-bg: rgb(255, 255, 255, 0.7);\n --topbar-text-color: rgb(78, 78, 78);\n --search-border-color: rgb(240, 240, 240);\n --search-icon-color: #c2c6cc;\n --input-focus-border-color: #b8b8b8;\n\n /* Home page */\n --post-list-text-color: dimgray;\n --btn-patinator-text-color: #555555;\n --btn-paginator-hover-color: var(--sidebar-bg);\n\n /* Posts */\n --toc-highlight: #0550ae;\n --btn-share-color: gray;\n --btn-share-hover-color: #0d6efd;\n --card-bg: white;\n --card-hovor-bg: #e2e2e2;\n --card-shadow: rgb(104, 104, 104, 0.05) 0 2px 6px 0,\n rgba(211, 209, 209, 0.15) 0 0 0 1px;\n --footnote-target-bg: lightcyan;\n --tb-odd-bg: #fbfcfd;\n --tb-border-color: #eaeaea;\n --dash-color: silver;\n --kbd-wrap-color: #bdbdbd;\n --kbd-text-color: var(--text-color);\n --kbd-bg-color: white;\n --prompt-text-color: rgb(46, 46, 46, 0.77);\n --prompt-tip-bg: rgb(123, 247, 144, 0.2);\n --prompt-tip-icon-color: #03b303;\n --prompt-info-bg: #e1f5fe;\n --prompt-info-icon-color: #0070cb;\n --prompt-warning-bg: rgb(255, 243, 205);\n --prompt-warning-icon-color: #ef9c03;\n --prompt-danger-bg: rgb(248, 215, 218, 0.56);\n --prompt-danger-icon-color: #df3c30;\n\n /* Tags */\n --tag-border: #dee2e6;\n --tag-shadow: var(--btn-border-color);\n --tag-hover: rgb(222, 226, 230);\n --search-tag-bg: #f8f9fa;\n\n [class^='prompt-'] {\n --link-underline-color: rgb(219, 216, 216);\n }\n\n .dark {\n display: none;\n }\n\n /* Categories */\n --categories-border: rgba(0, 0, 0, 0.125);\n --categories-hover-bg: var(--btn-border-color);\n --categories-icon-hover-color: darkslategray;\n\n /* Archive */\n --timeline-color: rgba(0, 0, 0, 0.075);\n --timeline-node-bg: #c2c6cc;\n --timeline-year-dot-color: #ffffff;\n} /* light-scheme */\n","/*\n * The main dark mode styles\n */\n\n@mixin dark-scheme {\n /* Framework color */\n --main-bg: rgb(27, 27, 30);\n --mask-bg: rgb(68, 69, 70);\n --main-border-color: rgb(44, 45, 45);\n\n /* Common color */\n --text-color: rgb(175, 176, 177);\n --text-muted-color: #868686;\n --text-muted-hightlight-color: #aeaeae;\n --heading-color: #cccccc;\n --label-color: #a7a7a7;\n --blockquote-border-color: rgb(66, 66, 66);\n --blockquote-text-color: #868686;\n --link-color: rgb(138, 180, 248);\n --link-underline-color: rgb(82, 108, 150);\n --button-bg: #1e1e1e;\n --btn-border-color: #2e2f31;\n --btn-backtotop-color: var(--text-color);\n --btn-backtotop-border-color: #212122;\n --btn-box-shadow: var(--main-bg);\n --card-header-bg: #292929;\n --checkbox-color: rgb(118, 120, 121);\n --checkbox-checked-color: var(--link-color);\n --img-bg: radial-gradient(circle, rgb(22, 22, 24) 0%, rgb(32, 32, 32) 100%);\n --shimmer-bg: linear-gradient(\n 90deg,\n rgba(255, 255, 255, 0) 0%,\n rgba(58, 55, 55, 0.4) 50%,\n rgba(255, 255, 255, 0) 100%\n );\n\n /* Sidebar */\n --site-title-color: #717070;\n --site-subtitle-color: #868686;\n --sidebar-bg: #1e1e1e;\n --sidebar-border-color: #292929;\n --sidebar-muted-color: #868686;\n --sidebar-active-color: rgb(255, 255, 255, 0.95);\n --sidebar-hover-bg: #262626;\n --sidebar-btn-bg: #232328;\n --sidebar-btn-color: #787878;\n --avatar-border-color: rgb(206, 206, 206, 0.9);\n\n /* Topbar */\n --topbar-bg: rgb(27, 27, 30, 0.64);\n --topbar-text-color: var(--text-color);\n --search-border-color: rgb(55, 55, 55);\n --search-icon-color: rgb(100, 102, 105);\n --input-focus-border-color: rgb(112, 114, 115);\n\n /* Home page */\n --post-list-text-color: rgb(175, 176, 177);\n --btn-patinator-text-color: var(--text-color);\n --btn-paginator-hover-color: #2e2e2e;\n\n /* Posts */\n --toc-highlight: rgb(116, 178, 243);\n --tag-hover: rgb(43, 56, 62);\n --tb-odd-bg: #252526; /* odd rows of the posts' table */\n --tb-even-bg: rgb(31, 31, 34); /* even rows of the posts' table */\n --tb-border-color: var(--tb-odd-bg);\n --footnote-target-bg: rgb(63, 81, 181);\n --btn-share-color: #6c757d;\n --btn-share-hover-color: #bfc1ca;\n --card-bg: #1e1e1e;\n --card-hovor-bg: #464d51;\n --card-shadow: rgb(21, 21, 21, 0.72) 0 6px 18px 0,\n rgb(137, 135, 135, 0.24) 0 0 0 1px;\n --kbd-wrap-color: #6a6a6a;\n --kbd-text-color: #d3d3d3;\n --kbd-bg-color: #242424;\n --prompt-text-color: rgb(216, 212, 212, 0.75);\n --prompt-tip-bg: rgb(22, 60, 36, 0.64);\n --prompt-tip-icon-color: rgb(15, 164, 15, 0.81);\n --prompt-info-bg: rgb(7, 59, 104, 0.8);\n --prompt-info-icon-color: #0075d1;\n --prompt-warning-bg: rgb(90, 69, 3, 0.88);\n --prompt-warning-icon-color: rgb(255, 165, 0, 0.8);\n --prompt-danger-bg: rgb(86, 28, 8, 0.8);\n --prompt-danger-icon-color: #cd0202;\n\n /* tags */\n --tag-border: rgb(59, 79, 88);\n --tag-shadow: rgb(32, 33, 33);\n --dash-color: rgb(63, 65, 68);\n --search-tag-bg: #292828;\n\n /* categories */\n --categories-border: rgb(64, 66, 69, 0.5);\n --categories-hover-bg: rgb(73, 75, 76);\n --categories-icon-hover-color: white;\n\n /* archives */\n --timeline-node-bg: rgb(150, 152, 156);\n --timeline-color: rgb(63, 65, 68);\n --timeline-year-dot-color: var(--timeline-color);\n\n .light {\n display: none;\n }\n\n hr {\n border-color: var(--main-border-color);\n }\n\n /* categories */\n .categories.card,\n .list-group-item {\n background-color: var(--card-bg);\n }\n\n .categories {\n .card-header {\n background-color: var(--card-header-bg);\n }\n\n .list-group-item {\n border-left: none;\n border-right: none;\n padding-left: 2rem;\n border-color: var(--categories-border);\n\n &:last-child {\n border-bottom-color: var(--card-bg);\n }\n }\n }\n\n #archives li:nth-child(odd) {\n background-image: linear-gradient(\n to left,\n rgb(26, 26, 30),\n rgb(39, 39, 45),\n rgb(39, 39, 45),\n rgb(39, 39, 45),\n rgb(26, 26, 30)\n );\n }\n\n color-scheme: dark;\n\n /* stylelint-disable-next-line selector-id-pattern */\n #disqus_thread {\n color-scheme: none;\n }\n} /* dark-scheme */\n","/*\n Style for Homepage\n*/\n\n#post-list {\n margin-top: 2rem;\n\n .card-wrapper {\n &:hover {\n text-decoration: none;\n }\n\n &:not(:last-child) {\n margin-bottom: 1.25rem;\n }\n }\n\n .card {\n border: 0;\n background: none;\n\n %img-radius {\n border-radius: $base-radius $base-radius 0 0;\n }\n\n .preview-img {\n @extend %img-radius;\n\n img {\n @extend %img-radius;\n }\n }\n\n .card-body {\n height: 100%;\n padding: 1rem;\n\n .card-title {\n @extend %text-clip;\n\n color: var(--heading-color) !important;\n font-size: 1.25rem;\n }\n\n %muted {\n color: var(--text-muted-color) !important;\n }\n\n .card-text.content {\n @extend %muted;\n\n p {\n @extend %text-clip;\n\n line-height: 1.5;\n margin: 0;\n }\n }\n\n .post-meta {\n @extend %muted;\n\n i {\n &:not(:first-child) {\n margin-left: 1.5rem;\n }\n }\n\n em {\n @extend %normal-font-style;\n\n color: inherit;\n }\n\n > div:first-child {\n display: block;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n }\n }\n }\n} /* #post-list */\n\n.pagination {\n color: var(--text-color);\n font-family: Lato, sans-serif;\n justify-content: space-evenly;\n\n a:hover {\n text-decoration: none;\n }\n\n .page-item {\n .page-link {\n color: var(--btn-patinator-text-color);\n padding: 0 0.6rem;\n display: -webkit-box;\n -webkit-box-pack: center;\n -webkit-box-align: center;\n border-radius: 0.5rem;\n border: 0;\n background-color: inherit;\n }\n\n &.active {\n .page-link {\n background-color: var(--btn-paginator-hover-color);\n }\n }\n\n &:not(.active) {\n .page-link {\n &:hover {\n box-shadow: inset var(--btn-border-color) 0 0 0 1px;\n }\n }\n }\n\n &.disabled {\n cursor: not-allowed;\n\n .page-link {\n color: rgba(108, 117, 125, 0.57);\n }\n }\n } /* .page-item */\n} /* .pagination */\n\n/* Tablet */\n@media all and (min-width: 768px) {\n %img-radius {\n border-radius: 0 $base-radius $base-radius 0;\n }\n\n #post-list {\n .card {\n .card-body {\n padding: 1.75rem 1.75rem 1.25rem 1.75rem;\n\n .card-text {\n display: inherit !important;\n }\n\n .post-meta {\n i {\n &:not(:first-child) {\n margin-left: 1.75rem;\n }\n }\n }\n }\n }\n }\n}\n\n/* Hide SideBar and TOC */\n@media all and (max-width: 830px) {\n .pagination {\n .page-item {\n &:not(:first-child):not(:last-child) {\n display: none;\n }\n }\n }\n}\n\n/* Sidebar is visible */\n@media all and (min-width: 831px) {\n #post-list {\n margin-top: 2.5rem;\n }\n\n .pagination {\n font-size: 0.85rem;\n justify-content: center;\n\n .page-item {\n &:not(:last-child) {\n margin-right: 0.7rem;\n }\n }\n\n .page-index {\n display: none;\n }\n } /* .pagination */\n}\n","/*\n Post-specific style\n*/\n\n%btn-post-nav {\n width: 50%;\n position: relative;\n border-color: var(--btn-border-color);\n}\n\n@mixin dot($pl: 0.25rem, $pr: 0.25rem) {\n content: '\\2022';\n padding-left: $pl;\n padding-right: $pr;\n}\n\nh1 + .post-meta {\n > span + span::before {\n @include dot;\n }\n\n em,\n time {\n @extend %text-highlight;\n }\n\n em {\n a {\n color: inherit;\n }\n }\n}\n\n.post-tail-wrapper {\n @extend %text-sm;\n\n margin-top: 6rem;\n border-bottom: 1px double var(--main-border-color);\n\n .license-wrapper {\n line-height: 1.2rem;\n\n > a {\n @extend %text-highlight;\n\n &:hover {\n @extend %link-hover;\n }\n }\n\n span:last-child {\n @extend %text-sm;\n }\n } /* .license-wrapper */\n\n .post-meta a:not(:hover) {\n @extend %link-underline;\n }\n\n .share-wrapper {\n vertical-align: middle;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n\n %icon-size {\n font-size: 1.125rem;\n }\n\n .share-icons {\n display: flex;\n\n i {\n color: var(--btn-share-color);\n\n @extend %icon-size;\n }\n\n > * {\n @extend %icon-size;\n\n margin-left: 0.5rem;\n\n &:hover {\n i {\n @extend %btn-share-hovor;\n }\n }\n }\n\n button {\n padding: 0;\n border: none;\n line-height: inherit;\n\n @extend %cursor-pointer;\n }\n } /* .share-icons */\n } /* .share-wrapper */\n}\n\n.share-mastodon {\n /* See: https://github.com/justinribeiro/share-to-mastodon#properties */\n --wc-stm-font-family: $font-family-base;\n --wc-stm-dialog-background-color: var(--card-bg);\n --wc-stm-form-button-border: 1px solid var(--btn-border-color);\n --wc-stm-form-submit-background-color: var(--sidebar-btn-bg);\n --wc-stm-form-cancel-background-color: var(--sidebar-btn-bg);\n --wc-stm-form-button-background-color-hover: #007bff;\n --wc-stm-form-button-color-hover: white;\n\n font-size: 1rem;\n}\n\n.post-tags {\n line-height: 2rem;\n\n .post-tag {\n &:hover {\n @extend %link-hover;\n @extend %tag-hover;\n @extend %no-bottom-border;\n }\n }\n}\n\n.post-navigation {\n .btn {\n @extend %btn-post-nav;\n\n &:not(:hover) {\n color: var(--link-color);\n }\n\n &:hover {\n &:not(.disabled)::before {\n color: whitesmoke;\n }\n }\n\n &.disabled {\n @extend %btn-post-nav;\n\n pointer-events: auto;\n cursor: not-allowed;\n background: none;\n color: gray;\n }\n\n &.btn-outline-primary.disabled:focus {\n box-shadow: none;\n }\n\n &::before {\n color: var(--text-muted-color);\n font-size: 0.65rem;\n text-transform: uppercase;\n content: attr(aria-label);\n }\n\n &:first-child {\n border-radius: $base-radius 0 0 $base-radius;\n left: 0.5px;\n }\n\n &:last-child {\n border-radius: 0 $base-radius $base-radius 0;\n right: 0.5px;\n }\n }\n\n p {\n font-size: 1.1rem;\n line-height: 1.5rem;\n margin-top: 0.3rem;\n white-space: normal;\n }\n} /* .post-navigation */\n\n@media (hover: hover) {\n .post-navigation {\n .btn,\n .btn::before {\n transition: all 0.35s ease-in-out;\n }\n }\n}\n\n@-webkit-keyframes fade-up {\n from {\n opacity: 0;\n position: relative;\n top: 2rem;\n }\n\n to {\n opacity: 1;\n position: relative;\n top: 0;\n }\n}\n\n@keyframes fade-up {\n from {\n opacity: 0;\n position: relative;\n top: 2rem;\n }\n\n to {\n opacity: 1;\n position: relative;\n top: 0;\n }\n}\n\n#toc-wrapper {\n border-left: 1px solid rgba(158, 158, 158, 0.17);\n position: -webkit-sticky;\n position: sticky;\n top: 4rem;\n transition: top 0.2s ease-in-out;\n -webkit-animation: fade-up 0.8s;\n animation: fade-up 0.8s;\n\n ul {\n list-style: none;\n font-size: 0.85rem;\n line-height: 1.25;\n padding-left: 0;\n\n li {\n &:not(:last-child) {\n margin: 0.4rem 0;\n }\n\n a {\n padding: 0.2rem 0 0.2rem 1.25rem;\n }\n }\n\n /* Overwrite TOC plugin style */\n\n .toc-link {\n display: block;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n\n &:hover {\n color: var(--toc-highlight);\n text-decoration: none;\n }\n\n &::before {\n display: none;\n }\n }\n\n .is-active-link {\n color: var(--toc-highlight) !important;\n font-weight: 600;\n\n &::before {\n display: inline-block;\n width: 1px;\n left: -1px;\n height: 1.25rem;\n background-color: var(--toc-highlight) !important;\n }\n }\n\n ul {\n padding-left: 0.75rem;\n }\n }\n}\n\n/* --- Related Posts --- */\n\n#related-posts {\n > h3 {\n @include label(1.1rem, 600);\n }\n\n time {\n @extend %normal-font-style;\n @extend %text-xs;\n\n color: var(--text-muted-color);\n }\n\n p {\n font-size: 0.9rem;\n margin-bottom: 0.5rem;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n\n .card {\n h4 {\n @extend %text-clip;\n }\n }\n}\n\n/* stylelint-disable-next-line selector-id-pattern */\n#disqus_thread {\n min-height: 8.5rem;\n}\n\n.utterances {\n max-width: 100%;\n}\n\n%btn-share-hovor {\n color: var(--btn-share-hover-color) !important;\n}\n\n.share-label {\n @include label(inherit, 400, inherit);\n\n &::after {\n content: ':';\n }\n}\n\n@media all and (max-width: 576px) {\n .post-tail-bottom {\n flex-wrap: wrap-reverse !important;\n\n > div:first-child {\n width: 100%;\n margin-top: 1rem;\n }\n }\n}\n\n@media all and (max-width: 768px) {\n .content > p > img {\n max-width: calc(100% + 1rem);\n }\n}\n\n/* Hide SideBar and TOC */\n@media all and (max-width: 849px) {\n .post-navigation {\n padding-left: 0;\n padding-right: 0;\n margin-left: -0.5rem;\n margin-right: -0.5rem;\n }\n}\n","/*\n Styles for Tab Tags\n*/\n\n.tag {\n border-radius: 0.7em;\n padding: 6px 8px 7px;\n margin-right: 0.8rem;\n line-height: 3rem;\n letter-spacing: 0;\n border: 1px solid var(--tag-border) !important;\n box-shadow: 0 0 3px 0 var(--tag-shadow);\n\n span {\n margin-left: 0.6em;\n font-size: 0.7em;\n font-family: Oswald, sans-serif;\n }\n}\n","/*\n Style for Archives\n*/\n\n#archives {\n letter-spacing: 0.03rem;\n\n $timeline-width: 4px;\n\n %timeline {\n content: '';\n width: $timeline-width;\n position: relative;\n float: left;\n background-color: var(--timeline-color);\n }\n\n .year {\n height: 3.5rem;\n font-size: 1.5rem;\n position: relative;\n left: 2px;\n margin-left: -$timeline-width;\n\n &::before {\n @extend %timeline;\n\n height: 72px;\n left: 79px;\n bottom: 16px;\n }\n\n &:first-child::before {\n @extend %timeline;\n\n height: 32px;\n top: 24px;\n }\n\n /* Year dot */\n &::after {\n content: '';\n display: inline-block;\n position: relative;\n border-radius: 50%;\n width: 12px;\n height: 12px;\n left: 21.5px;\n border: 3px solid;\n background-color: var(--timeline-year-dot-color);\n border-color: var(--timeline-node-bg);\n box-shadow: 0 0 2px 0 #c2c6cc;\n z-index: 1;\n }\n }\n\n ul {\n li {\n font-size: 1.1rem;\n line-height: 3rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n\n &:nth-child(odd) {\n background-color: var(--main-bg, #ffffff);\n background-image: linear-gradient(\n to left,\n #ffffff,\n #fbfbfb,\n #fbfbfb,\n #fbfbfb,\n #ffffff\n );\n }\n\n &::before {\n @extend %timeline;\n\n top: 0;\n left: 77px;\n height: 3.1rem;\n }\n }\n\n &:last-child li:last-child::before {\n height: 1.5rem;\n }\n } /* #archives ul */\n\n .date {\n white-space: nowrap;\n display: inline-block;\n position: relative;\n right: 0.5rem;\n\n &.month {\n width: 1.4rem;\n text-align: center;\n }\n\n &.day {\n font-size: 85%;\n font-family: Lato, sans-serif;\n }\n }\n\n a {\n /* post title in Archvies */\n margin-left: 2.5rem;\n position: relative;\n top: 0.1rem;\n\n &:hover {\n border-bottom: none;\n }\n\n &::before {\n /* the dot before post title */\n content: '';\n display: inline-block;\n position: relative;\n border-radius: 50%;\n width: 8px;\n height: 8px;\n float: left;\n top: 1.35rem;\n left: 71px;\n background-color: var(--timeline-node-bg);\n box-shadow: 0 0 3px 0 #c2c6cc;\n z-index: 1;\n }\n }\n} /* #archives */\n\n@media all and (max-width: 576px) {\n #archives {\n margin-top: -1rem;\n\n ul {\n letter-spacing: 0;\n }\n }\n}\n","/*\n Style for Tab Categories\n*/\n\n%category-icon-color {\n color: gray;\n}\n\n.categories {\n margin-bottom: 2rem;\n border-color: var(--categories-border);\n\n &.card,\n .list-group {\n @extend %rounded;\n }\n\n .card-header {\n $radius: calc($base-radius - 1px);\n\n padding: 0.75rem;\n border-radius: $radius;\n border-bottom: 0;\n\n &.hide-border-bottom {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n }\n }\n\n i {\n @extend %category-icon-color;\n\n font-size: 86%; /* fontawesome icons */\n }\n\n .list-group-item {\n border-left: none;\n border-right: none;\n padding-left: 2rem;\n\n &:first-child {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n }\n\n &:last-child {\n border-bottom: 0;\n }\n }\n} /* .categories */\n\n.category-trigger {\n width: 1.7rem;\n height: 1.7rem;\n border-radius: 50%;\n text-align: center;\n color: #6c757d !important;\n\n i {\n position: relative;\n height: 0.7rem;\n width: 1rem;\n transition: transform 300ms ease;\n }\n\n &:hover {\n i {\n color: var(--categories-icon-hover-color);\n }\n }\n}\n\n/* only works on desktop */\n@media (hover: hover) {\n .category-trigger:hover {\n background-color: var(--categories-hover-bg);\n }\n}\n\n.rotate {\n transform: rotate(-90deg);\n}\n","/*\n Style for page Category and Tag\n*/\n\n.dash {\n margin: 0 0.5rem 0.6rem 0.5rem;\n border-bottom: 2px dotted var(--dash-color);\n}\n\n#page-category,\n#page-tag {\n ul > li {\n line-height: 1.5rem;\n padding: 0.6rem 0;\n\n /* dot */\n &::before {\n background: #999999;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n display: block;\n content: '';\n position: relative;\n top: 0.6rem;\n margin-right: 0.5rem;\n }\n\n /* post's title */\n > a {\n @extend %no-bottom-border;\n\n font-size: 1.1rem;\n }\n }\n}\n\n/* tag icon */\n#page-tag h1 > i {\n font-size: 1.2rem;\n}\n\n#page-category h1 > i {\n font-size: 1.25rem;\n}\n\n#page-category,\n#page-tag,\n#access-lastmod {\n a:hover {\n @extend %link-hover;\n\n margin-bottom: -1px; /* Avoid jumping */\n }\n}\n\n@media all and (max-width: 576px) {\n #page-category,\n #page-tag {\n ul > li {\n &::before {\n margin: 0 0.5rem;\n }\n\n > a {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n }\n }\n}\n"],"file":"jekyll-theme-chirpy.css"} \ No newline at end of file diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..138227b --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1 @@ +#search-results a,h5,h4,h3,h2,h1{color:var(--heading-color);font-weight:400;font-family:Lato,"Microsoft Yahei",sans-serif}main h5,main h4,main h3,main h2{margin-top:2.5rem;margin-bottom:1.25rem}main h5:focus,main h4:focus,main h3:focus,main h2:focus{outline:none}h5 .anchor,h4 .anchor,h3 .anchor,h2 .anchor{font-size:80%}@media(hover: hover){h5 .anchor,h4 .anchor,h3 .anchor,h2 .anchor{visibility:hidden;opacity:0;transition:opacity .25s ease-in,visibility 0s ease-in .25s}h5:hover .anchor,h4:hover .anchor,h3:hover .anchor,h2:hover .anchor{visibility:visible;opacity:1;transition:opacity .25s ease-in,visibility 0s ease-in 0s}}.post-tags .post-tag:hover,.tag:hover{background:var(--tag-hover);transition:background .35s ease-in-out}.table-wrapper>table tbody tr td,.table-wrapper>table thead th{padding:.4rem 1rem;font-size:95%;white-space:nowrap}#page-category a:hover,#page-tag a:hover,.post-tags .post-tag:hover,.post-tail-wrapper .license-wrapper>a:hover,#search-results a:hover,#topbar #breadcrumb a:hover,.content a:not(.img-link):hover,.post-meta a:not([class]):hover,#access-lastmod a:hover,footer a:hover{color:#d2603a !important;border-bottom:1px solid #d2603a;text-decoration:none}#search-results a,#search-hints .post-tag,a{color:var(--link-color)}.post-tail-wrapper .post-meta a:not(:hover),.content a:not(.img-link){border-bottom:1px solid var(--link-underline-color)}#sidebar .sidebar-bottom a,#sidebar .site-title a,#sidebar .profile-wrapper{transition:all .3s ease-in-out}#sidebar .sidebar-bottom .icon-border,.content a.popup,i.far,i.fas,.code-header{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#page-category ul>li>a,#page-tag ul>li>a,.post-tags .post-tag:hover,#search-results a,main .categories a:not(:hover),main #tags a:not(:hover),main #archives a:not(:hover),#access-lastmod a{border-bottom:none}.post-tail-wrapper .share-wrapper .share-icons button,#search-cancel,.code-header button{cursor:pointer}#related-posts time,#post-list .card .card-body .post-meta em,.post-meta em{font-style:normal}.categories.card,.categories .list-group,.embed-video,.post-preview::before,.post-preview,.preview-img img,.preview-img,blockquote[class^=prompt-],.code-header button,div[class^=language-],.highlight{border-radius:.625rem}.content a.popup+em{display:block;text-align:center;font-style:normal;font-size:80%;padding:0;color:#6d6c6c}#sidebar .sidebar-bottom .mode-toggle,#sidebar a{color:var(--sidebar-muted-color);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#related-posts .card h4,#post-list .card .card-body .card-text.content p,#post-list .card .card-body .card-title{display:-webkit-box;overflow:hidden;text-overflow:ellipsis;-webkit-line-clamp:2;-webkit-box-orient:vertical}.post-tail-wrapper .license-wrapper>a,h1+.post-meta em,h1+.post-meta time,footer a{color:var(--text-muted-hightlight-color);font-weight:600}.post-tail-wrapper .license-wrapper span:last-child,.post-tail-wrapper,.post-meta{font-size:.85rem}#related-posts time,footer{font-size:.8rem}sup:target,.footnotes>ol>li:target{background-color:var(--footnote-target-bg);width:-moz-fit-content;width:-webkit-fit-content;width:fit-content;transition:background-color 1.75s ease-in-out}@media(prefers-color-scheme: light){html:not([data-mode]),html[data-mode=light]{--language-border-color: #ececec;--highlight-bg-color: #f6f8fa;--highlighter-rouge-color: #3f596f;--highlight-lineno-color: #9e9e9e;--inline-code-bg: #f6f6f7;--code-color: #3a3a3a;--code-header-text-color: #a3a3a3;--code-header-muted-color: #e5e5e5;--code-header-icon-color: #c9c8c8;--clipboard-checked-color: #43c743}html:not([data-mode]) [class^=prompt-],html[data-mode=light] [class^=prompt-]{--inline-code-bg: #fbfafa}html:not([data-mode]) .highlight table td,html[data-mode=light] .highlight table td{padding:5px}html:not([data-mode]) .highlight table pre,html[data-mode=light] .highlight table pre{margin:0}html:not([data-mode]) .highlight,html:not([data-mode]) .highlight .w,html[data-mode=light] .highlight,html[data-mode=light] .highlight .w{color:#24292f;background-color:#f6f8fa}html:not([data-mode]) .highlight .k,html:not([data-mode]) .highlight .kd,html:not([data-mode]) .highlight .kn,html:not([data-mode]) .highlight .kp,html:not([data-mode]) .highlight .kr,html:not([data-mode]) .highlight .kt,html:not([data-mode]) .highlight .kv,html[data-mode=light] .highlight .k,html[data-mode=light] .highlight .kd,html[data-mode=light] .highlight .kn,html[data-mode=light] .highlight .kp,html[data-mode=light] .highlight .kr,html[data-mode=light] .highlight .kt,html[data-mode=light] .highlight .kv{color:#cf222e}html:not([data-mode]) .highlight .gr,html[data-mode=light] .highlight .gr{color:#f6f8fa}html:not([data-mode]) .highlight .gd,html[data-mode=light] .highlight .gd{color:#82071e;background-color:#ffebe9}html:not([data-mode]) .highlight .nb,html[data-mode=light] .highlight .nb{color:#953800}html:not([data-mode]) .highlight .nc,html[data-mode=light] .highlight .nc{color:#953800}html:not([data-mode]) .highlight .no,html[data-mode=light] .highlight .no{color:#953800}html:not([data-mode]) .highlight .nn,html[data-mode=light] .highlight .nn{color:#953800}html:not([data-mode]) .highlight .sr,html[data-mode=light] .highlight .sr{color:#116329}html:not([data-mode]) .highlight .na,html[data-mode=light] .highlight .na{color:#116329}html:not([data-mode]) .highlight .nt,html[data-mode=light] .highlight .nt{color:#116329}html:not([data-mode]) .highlight .gi,html[data-mode=light] .highlight .gi{color:#116329;background-color:#dafbe1}html:not([data-mode]) .highlight .kc,html[data-mode=light] .highlight .kc{color:#0550ae}html:not([data-mode]) .highlight .l,html:not([data-mode]) .highlight .ld,html:not([data-mode]) .highlight .m,html:not([data-mode]) .highlight .mb,html:not([data-mode]) .highlight .mf,html:not([data-mode]) .highlight .mh,html:not([data-mode]) .highlight .mi,html:not([data-mode]) .highlight .il,html:not([data-mode]) .highlight .mo,html:not([data-mode]) .highlight .mx,html[data-mode=light] .highlight .l,html[data-mode=light] .highlight .ld,html[data-mode=light] .highlight .m,html[data-mode=light] .highlight .mb,html[data-mode=light] .highlight .mf,html[data-mode=light] .highlight .mh,html[data-mode=light] .highlight .mi,html[data-mode=light] .highlight .il,html[data-mode=light] .highlight .mo,html[data-mode=light] .highlight .mx{color:#0550ae}html:not([data-mode]) .highlight .sb,html[data-mode=light] .highlight .sb{color:#0550ae}html:not([data-mode]) .highlight .bp,html[data-mode=light] .highlight .bp{color:#0550ae}html:not([data-mode]) .highlight .ne,html[data-mode=light] .highlight .ne{color:#0550ae}html:not([data-mode]) .highlight .nl,html[data-mode=light] .highlight .nl{color:#0550ae}html:not([data-mode]) .highlight .py,html[data-mode=light] .highlight .py{color:#0550ae}html:not([data-mode]) .highlight .nv,html:not([data-mode]) .highlight .vc,html:not([data-mode]) .highlight .vg,html:not([data-mode]) .highlight .vi,html:not([data-mode]) .highlight .vm,html[data-mode=light] .highlight .nv,html[data-mode=light] .highlight .vc,html[data-mode=light] .highlight .vg,html[data-mode=light] .highlight .vi,html[data-mode=light] .highlight .vm{color:#0550ae}html:not([data-mode]) .highlight .o,html:not([data-mode]) .highlight .ow,html[data-mode=light] .highlight .o,html[data-mode=light] .highlight .ow{color:#0550ae}html:not([data-mode]) .highlight .gh,html[data-mode=light] .highlight .gh{color:#0550ae;font-weight:bold}html:not([data-mode]) .highlight .gu,html[data-mode=light] .highlight .gu{color:#0550ae;font-weight:bold}html:not([data-mode]) .highlight .s,html:not([data-mode]) .highlight .sa,html:not([data-mode]) .highlight .sc,html:not([data-mode]) .highlight .dl,html:not([data-mode]) .highlight .sd,html:not([data-mode]) .highlight .s2,html:not([data-mode]) .highlight .se,html:not([data-mode]) .highlight .sh,html:not([data-mode]) .highlight .sx,html:not([data-mode]) .highlight .s1,html:not([data-mode]) .highlight .ss,html[data-mode=light] .highlight .s,html[data-mode=light] .highlight .sa,html[data-mode=light] .highlight .sc,html[data-mode=light] .highlight .dl,html[data-mode=light] .highlight .sd,html[data-mode=light] .highlight .s2,html[data-mode=light] .highlight .se,html[data-mode=light] .highlight .sh,html[data-mode=light] .highlight .sx,html[data-mode=light] .highlight .s1,html[data-mode=light] .highlight .ss{color:#0a3069}html:not([data-mode]) .highlight .nd,html[data-mode=light] .highlight .nd{color:#8250df}html:not([data-mode]) .highlight .nf,html:not([data-mode]) .highlight .fm,html[data-mode=light] .highlight .nf,html[data-mode=light] .highlight .fm{color:#8250df}html:not([data-mode]) .highlight .err,html[data-mode=light] .highlight .err{color:#f6f8fa;background-color:#82071e}html:not([data-mode]) .highlight .c,html:not([data-mode]) .highlight .ch,html:not([data-mode]) .highlight .cd,html:not([data-mode]) .highlight .cm,html:not([data-mode]) .highlight .cp,html:not([data-mode]) .highlight .cpf,html:not([data-mode]) .highlight .c1,html:not([data-mode]) .highlight .cs,html[data-mode=light] .highlight .c,html[data-mode=light] .highlight .ch,html[data-mode=light] .highlight .cd,html[data-mode=light] .highlight .cm,html[data-mode=light] .highlight .cp,html[data-mode=light] .highlight .cpf,html[data-mode=light] .highlight .c1,html[data-mode=light] .highlight .cs{color:#68717a}html:not([data-mode]) .highlight .gl,html[data-mode=light] .highlight .gl{color:#68717a}html:not([data-mode]) .highlight .gt,html[data-mode=light] .highlight .gt{color:#68717a}html:not([data-mode]) .highlight .ni,html[data-mode=light] .highlight .ni{color:#24292f}html:not([data-mode]) .highlight .si,html[data-mode=light] .highlight .si{color:#24292f}html:not([data-mode]) .highlight .ge,html[data-mode=light] .highlight .ge{color:#24292f;font-style:italic}html:not([data-mode]) .highlight .gs,html[data-mode=light] .highlight .gs{color:#24292f;font-weight:bold}html[data-mode=dark]{--language-border-color: #2d2d2d;--highlight-bg-color: #151515;--highlighter-rouge-color: #c9def1;--highlight-lineno-color: #808080;--inline-code-bg: #323238;--code-color: #b0b0b0;--code-header-text-color: #6a6a6a;--code-header-muted-color: #353535;--code-header-icon-color: #565656;--clipboard-checked-color: #2bcc2b;--filepath-text-color: #cacaca}html[data-mode=dark] .highlight .gp{color:#87939d}html[data-mode=dark] .highlight table td{padding:5px}html[data-mode=dark] .highlight table pre{margin:0}html[data-mode=dark] .highlight,html[data-mode=dark] .highlight .w{color:#d0d0d0;background-color:#151515}html[data-mode=dark] .highlight .err{color:#151515;background-color:#ac4142}html[data-mode=dark] .highlight .c,html[data-mode=dark] .highlight .ch,html[data-mode=dark] .highlight .cd,html[data-mode=dark] .highlight .cm,html[data-mode=dark] .highlight .cpf,html[data-mode=dark] .highlight .c1,html[data-mode=dark] .highlight .cs{color:#848484}html[data-mode=dark] .highlight .cp{color:#f4bf75}html[data-mode=dark] .highlight .nt{color:#f4bf75}html[data-mode=dark] .highlight .o,html[data-mode=dark] .highlight .ow{color:#d0d0d0}html[data-mode=dark] .highlight .p,html[data-mode=dark] .highlight .pi{color:#d0d0d0}html[data-mode=dark] .highlight .gi{color:#90a959}html[data-mode=dark] .highlight .gd{color:#f08a8b;background-color:#320000}html[data-mode=dark] .highlight .gh{color:#6a9fb5;background-color:#151515;font-weight:bold}html[data-mode=dark] .highlight .k,html[data-mode=dark] .highlight .kn,html[data-mode=dark] .highlight .kp,html[data-mode=dark] .highlight .kr,html[data-mode=dark] .highlight .kv{color:#aa759f}html[data-mode=dark] .highlight .kc{color:#d28445}html[data-mode=dark] .highlight .kt{color:#d28445}html[data-mode=dark] .highlight .kd{color:#d28445}html[data-mode=dark] .highlight .s,html[data-mode=dark] .highlight .sb,html[data-mode=dark] .highlight .sc,html[data-mode=dark] .highlight .dl,html[data-mode=dark] .highlight .sd,html[data-mode=dark] .highlight .s2,html[data-mode=dark] .highlight .sh,html[data-mode=dark] .highlight .sx,html[data-mode=dark] .highlight .s1{color:#90a959}html[data-mode=dark] .highlight .sa{color:#aa759f}html[data-mode=dark] .highlight .sr{color:#75b5aa}html[data-mode=dark] .highlight .si{color:#b76d45}html[data-mode=dark] .highlight .se{color:#b76d45}html[data-mode=dark] .highlight .nn{color:#f4bf75}html[data-mode=dark] .highlight .nc{color:#f4bf75}html[data-mode=dark] .highlight .no{color:#f4bf75}html[data-mode=dark] .highlight .na{color:#6a9fb5}html[data-mode=dark] .highlight .m,html[data-mode=dark] .highlight .mb,html[data-mode=dark] .highlight .mf,html[data-mode=dark] .highlight .mh,html[data-mode=dark] .highlight .mi,html[data-mode=dark] .highlight .il,html[data-mode=dark] .highlight .mo,html[data-mode=dark] .highlight .mx{color:#90a959}html[data-mode=dark] .highlight .ss{color:#90a959}}@media(prefers-color-scheme: dark){html:not([data-mode]),html[data-mode=dark]{--language-border-color: #2d2d2d;--highlight-bg-color: #151515;--highlighter-rouge-color: #c9def1;--highlight-lineno-color: #808080;--inline-code-bg: #323238;--code-color: #b0b0b0;--code-header-text-color: #6a6a6a;--code-header-muted-color: #353535;--code-header-icon-color: #565656;--clipboard-checked-color: #2bcc2b;--filepath-text-color: #cacaca}html:not([data-mode]) .highlight .gp,html[data-mode=dark] .highlight .gp{color:#87939d}html:not([data-mode]) .highlight table td,html[data-mode=dark] .highlight table td{padding:5px}html:not([data-mode]) .highlight table pre,html[data-mode=dark] .highlight table pre{margin:0}html:not([data-mode]) .highlight,html:not([data-mode]) .highlight .w,html[data-mode=dark] .highlight,html[data-mode=dark] .highlight .w{color:#d0d0d0;background-color:#151515}html:not([data-mode]) .highlight .err,html[data-mode=dark] .highlight .err{color:#151515;background-color:#ac4142}html:not([data-mode]) .highlight .c,html:not([data-mode]) .highlight .ch,html:not([data-mode]) .highlight .cd,html:not([data-mode]) .highlight .cm,html:not([data-mode]) .highlight .cpf,html:not([data-mode]) .highlight .c1,html:not([data-mode]) .highlight .cs,html[data-mode=dark] .highlight .c,html[data-mode=dark] .highlight .ch,html[data-mode=dark] .highlight .cd,html[data-mode=dark] .highlight .cm,html[data-mode=dark] .highlight .cpf,html[data-mode=dark] .highlight .c1,html[data-mode=dark] .highlight .cs{color:#848484}html:not([data-mode]) .highlight .cp,html[data-mode=dark] .highlight .cp{color:#f4bf75}html:not([data-mode]) .highlight .nt,html[data-mode=dark] .highlight .nt{color:#f4bf75}html:not([data-mode]) .highlight .o,html:not([data-mode]) .highlight .ow,html[data-mode=dark] .highlight .o,html[data-mode=dark] .highlight .ow{color:#d0d0d0}html:not([data-mode]) .highlight .p,html:not([data-mode]) .highlight .pi,html[data-mode=dark] .highlight .p,html[data-mode=dark] .highlight .pi{color:#d0d0d0}html:not([data-mode]) .highlight .gi,html[data-mode=dark] .highlight .gi{color:#90a959}html:not([data-mode]) .highlight .gd,html[data-mode=dark] .highlight .gd{color:#f08a8b;background-color:#320000}html:not([data-mode]) .highlight .gh,html[data-mode=dark] .highlight .gh{color:#6a9fb5;background-color:#151515;font-weight:bold}html:not([data-mode]) .highlight .k,html:not([data-mode]) .highlight .kn,html:not([data-mode]) .highlight .kp,html:not([data-mode]) .highlight .kr,html:not([data-mode]) .highlight .kv,html[data-mode=dark] .highlight .k,html[data-mode=dark] .highlight .kn,html[data-mode=dark] .highlight .kp,html[data-mode=dark] .highlight .kr,html[data-mode=dark] .highlight .kv{color:#aa759f}html:not([data-mode]) .highlight .kc,html[data-mode=dark] .highlight .kc{color:#d28445}html:not([data-mode]) .highlight .kt,html[data-mode=dark] .highlight .kt{color:#d28445}html:not([data-mode]) .highlight .kd,html[data-mode=dark] .highlight .kd{color:#d28445}html:not([data-mode]) .highlight .s,html:not([data-mode]) .highlight .sb,html:not([data-mode]) .highlight .sc,html:not([data-mode]) .highlight .dl,html:not([data-mode]) .highlight .sd,html:not([data-mode]) .highlight .s2,html:not([data-mode]) .highlight .sh,html:not([data-mode]) .highlight .sx,html:not([data-mode]) .highlight .s1,html[data-mode=dark] .highlight .s,html[data-mode=dark] .highlight .sb,html[data-mode=dark] .highlight .sc,html[data-mode=dark] .highlight .dl,html[data-mode=dark] .highlight .sd,html[data-mode=dark] .highlight .s2,html[data-mode=dark] .highlight .sh,html[data-mode=dark] .highlight .sx,html[data-mode=dark] .highlight .s1{color:#90a959}html:not([data-mode]) .highlight .sa,html[data-mode=dark] .highlight .sa{color:#aa759f}html:not([data-mode]) .highlight .sr,html[data-mode=dark] .highlight .sr{color:#75b5aa}html:not([data-mode]) .highlight .si,html[data-mode=dark] .highlight .si{color:#b76d45}html:not([data-mode]) .highlight .se,html[data-mode=dark] .highlight .se{color:#b76d45}html:not([data-mode]) .highlight .nn,html[data-mode=dark] .highlight .nn{color:#f4bf75}html:not([data-mode]) .highlight .nc,html[data-mode=dark] .highlight .nc{color:#f4bf75}html:not([data-mode]) .highlight .no,html[data-mode=dark] .highlight .no{color:#f4bf75}html:not([data-mode]) .highlight .na,html[data-mode=dark] .highlight .na{color:#6a9fb5}html:not([data-mode]) .highlight .m,html:not([data-mode]) .highlight .mb,html:not([data-mode]) .highlight .mf,html:not([data-mode]) .highlight .mh,html:not([data-mode]) .highlight .mi,html:not([data-mode]) .highlight .il,html:not([data-mode]) .highlight .mo,html:not([data-mode]) .highlight .mx,html[data-mode=dark] .highlight .m,html[data-mode=dark] .highlight .mb,html[data-mode=dark] .highlight .mf,html[data-mode=dark] .highlight .mh,html[data-mode=dark] .highlight .mi,html[data-mode=dark] .highlight .il,html[data-mode=dark] .highlight .mo,html[data-mode=dark] .highlight .mx{color:#90a959}html:not([data-mode]) .highlight .ss,html[data-mode=dark] .highlight .ss{color:#90a959}html[data-mode=light]{--language-border-color: #ececec;--highlight-bg-color: #f6f8fa;--highlighter-rouge-color: #3f596f;--highlight-lineno-color: #9e9e9e;--inline-code-bg: #f6f6f7;--code-color: #3a3a3a;--code-header-text-color: #a3a3a3;--code-header-muted-color: #e5e5e5;--code-header-icon-color: #c9c8c8;--clipboard-checked-color: #43c743}html[data-mode=light] [class^=prompt-]{--inline-code-bg: #fbfafa}html[data-mode=light] .highlight table td{padding:5px}html[data-mode=light] .highlight table pre{margin:0}html[data-mode=light] .highlight,html[data-mode=light] .highlight .w{color:#24292f;background-color:#f6f8fa}html[data-mode=light] .highlight .k,html[data-mode=light] .highlight .kd,html[data-mode=light] .highlight .kn,html[data-mode=light] .highlight .kp,html[data-mode=light] .highlight .kr,html[data-mode=light] .highlight .kt,html[data-mode=light] .highlight .kv{color:#cf222e}html[data-mode=light] .highlight .gr{color:#f6f8fa}html[data-mode=light] .highlight .gd{color:#82071e;background-color:#ffebe9}html[data-mode=light] .highlight .nb{color:#953800}html[data-mode=light] .highlight .nc{color:#953800}html[data-mode=light] .highlight .no{color:#953800}html[data-mode=light] .highlight .nn{color:#953800}html[data-mode=light] .highlight .sr{color:#116329}html[data-mode=light] .highlight .na{color:#116329}html[data-mode=light] .highlight .nt{color:#116329}html[data-mode=light] .highlight .gi{color:#116329;background-color:#dafbe1}html[data-mode=light] .highlight .kc{color:#0550ae}html[data-mode=light] .highlight .l,html[data-mode=light] .highlight .ld,html[data-mode=light] .highlight .m,html[data-mode=light] .highlight .mb,html[data-mode=light] .highlight .mf,html[data-mode=light] .highlight .mh,html[data-mode=light] .highlight .mi,html[data-mode=light] .highlight .il,html[data-mode=light] .highlight .mo,html[data-mode=light] .highlight .mx{color:#0550ae}html[data-mode=light] .highlight .sb{color:#0550ae}html[data-mode=light] .highlight .bp{color:#0550ae}html[data-mode=light] .highlight .ne{color:#0550ae}html[data-mode=light] .highlight .nl{color:#0550ae}html[data-mode=light] .highlight .py{color:#0550ae}html[data-mode=light] .highlight .nv,html[data-mode=light] .highlight .vc,html[data-mode=light] .highlight .vg,html[data-mode=light] .highlight .vi,html[data-mode=light] .highlight .vm{color:#0550ae}html[data-mode=light] .highlight .o,html[data-mode=light] .highlight .ow{color:#0550ae}html[data-mode=light] .highlight .gh{color:#0550ae;font-weight:bold}html[data-mode=light] .highlight .gu{color:#0550ae;font-weight:bold}html[data-mode=light] .highlight .s,html[data-mode=light] .highlight .sa,html[data-mode=light] .highlight .sc,html[data-mode=light] .highlight .dl,html[data-mode=light] .highlight .sd,html[data-mode=light] .highlight .s2,html[data-mode=light] .highlight .se,html[data-mode=light] .highlight .sh,html[data-mode=light] .highlight .sx,html[data-mode=light] .highlight .s1,html[data-mode=light] .highlight .ss{color:#0a3069}html[data-mode=light] .highlight .nd{color:#8250df}html[data-mode=light] .highlight .nf,html[data-mode=light] .highlight .fm{color:#8250df}html[data-mode=light] .highlight .err{color:#f6f8fa;background-color:#82071e}html[data-mode=light] .highlight .c,html[data-mode=light] .highlight .ch,html[data-mode=light] .highlight .cd,html[data-mode=light] .highlight .cm,html[data-mode=light] .highlight .cp,html[data-mode=light] .highlight .cpf,html[data-mode=light] .highlight .c1,html[data-mode=light] .highlight .cs{color:#68717a}html[data-mode=light] .highlight .gl{color:#68717a}html[data-mode=light] .highlight .gt{color:#68717a}html[data-mode=light] .highlight .ni{color:#24292f}html[data-mode=light] .highlight .si{color:#24292f}html[data-mode=light] .highlight .ge{color:#24292f;font-style:italic}html[data-mode=light] .highlight .gs{color:#24292f;font-weight:bold}}div[class^=language-],figure.highlight,.highlight{background-color:var(--highlight-bg-color)}td.rouge-code{padding-left:1rem;padding-right:1.5rem}.highlighter-rouge{color:var(--highlighter-rouge-color);margin-top:.5rem;margin-bottom:1.2em}.highlight{overflow:auto;padding-bottom:.75rem}.highlight pre{margin-bottom:0;font-size:.85rem;line-height:1.4rem;word-wrap:normal}.highlight table td:first-child{display:inline-block;margin-left:1rem;margin-right:.75rem}.highlight table td:last-child{padding-right:2rem !important}.highlight table td pre{overflow:visible;word-break:normal}.highlight .lineno{text-align:right;color:var(--highlight-lineno-color);-webkit-user-select:none;-moz-user-select:none;-o-user-select:none;-ms-user-select:none;user-select:none}code{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;color:var(--code-color)}code.highlighter-rouge{font-size:.85rem;padding:3px 5px;word-break:break-word;border-radius:4px;background-color:var(--inline-code-bg)}code.filepath{background-color:inherit;color:var(--filepath-text-color);font-weight:600;padding:0}a>code.highlighter-rouge{padding-bottom:0;color:inherit}a:hover>code.highlighter-rouge{border-bottom:none}blockquote code{color:inherit}td.rouge-code a{color:inherit !important;border-bottom:none !important;pointer-events:none}div[class^=language-]{box-shadow:var(--language-border-color) 0 0 0 1px}.content>div[class^=language-]{margin-left:-1rem;margin-right:-1rem;border-radius:0}div[class^=language-] .highlight{border-top-left-radius:0;border-top-right-radius:0}div.nolineno td:first-child,div.language-plaintext td:first-child,div.language-console td:first-child,div.language-terminal td:first-child{padding:0 !important;margin-right:0}div.nolineno td:first-child .lineno,div.language-plaintext td:first-child .lineno,div.language-console td:first-child .lineno,div.language-terminal td:first-child .lineno{display:none}.code-header{display:flex;justify-content:space-between;align-items:center;height:2.25rem;margin-left:.75rem;margin-right:.25rem}.code-header span{line-height:2.25rem}.code-header span i{font-size:1rem;width:1.75rem;color:var(--code-header-icon-color)}.code-header span i.small{font-size:70%}[file] .code-header span>i{position:relative;top:1px}.code-header span::after{content:attr(data-label-text);font-size:.85rem;font-weight:600;color:var(--code-header-text-color)}.code-header button{border:1px solid rgba(0,0,0,0);height:2.25rem;width:2.25rem;padding:0;background-color:inherit}.code-header button i{color:var(--code-header-icon-color)}.code-header button[timeout]:hover{border-color:var(--clipboard-checked-color)}.code-header button[timeout] i{color:var(--clipboard-checked-color)}.code-header button:focus{outline:none}.code-header button:not([timeout]):hover{background-color:rgba(128,128,128,.37)}.code-header button:not([timeout]):hover i{color:#fff}@media all and (min-width: 576px){.content>div[class^=language-]{margin-left:0;margin-right:0;border-radius:.625rem}div[class^=language-] .code-header{margin-left:0;margin-right:0}div[class^=language-] .code-header::before{content:"";display:inline-block;margin-left:1rem;width:.75rem;height:.75rem;border-radius:50%;background-color:var(--code-header-muted-color);box-shadow:1.25rem 0 0 var(--code-header-muted-color),2.5rem 0 0 var(--code-header-muted-color)}div[class^=language-] .code-header span{margin-left:-0.875rem}}html{font-size:16px}@media(prefers-color-scheme: light){html:not([data-mode]),html[data-mode=light]{--main-bg: white;--mask-bg: #c1c3c5;--main-border-color: #f3f3f3;--text-color: #34343c;--text-muted-color: #757575;--text-muted-hightlight-color: inherit;--heading-color: #2a2a2a;--label-color: #585858;--blockquote-border-color: #eeeeee;--blockquote-text-color: #757575;--link-color: #0056b2;--link-underline-color: #dee2e6;--button-bg: #ffffff;--btn-border-color: #e9ecef;--btn-backtotop-color: #686868;--btn-backtotop-border-color: #f1f1f1;--btn-box-shadow: #eaeaea;--checkbox-color: #c5c5c5;--checkbox-checked-color: #07a8f7;--img-bg: radial-gradient( circle, rgb(255, 255, 255) 0%, rgb(239, 239, 239) 100% );--shimmer-bg: linear-gradient( 90deg, rgba(250, 250, 250, 0) 0%, rgba(232, 230, 230, 1) 50%, rgba(250, 250, 250, 0) 100% );--site-title-color: rgb(113, 113, 113);--site-subtitle-color: #717171;--sidebar-bg: #f6f8fa;--sidebar-border-color: #efefef;--sidebar-muted-color: #545454;--sidebar-active-color: #1d1d1d;--sidebar-hover-bg: rgb(223, 233, 241, 0.64);--sidebar-btn-bg: white;--sidebar-btn-color: #8e8e8e;--avatar-border-color: white;--topbar-bg: rgb(255, 255, 255, 0.7);--topbar-text-color: rgb(78, 78, 78);--search-border-color: rgb(240, 240, 240);--search-icon-color: #c2c6cc;--input-focus-border-color: #b8b8b8;--post-list-text-color: dimgray;--btn-patinator-text-color: #555555;--btn-paginator-hover-color: var(--sidebar-bg);--toc-highlight: #0550ae;--btn-share-color: gray;--btn-share-hover-color: #0d6efd;--card-bg: white;--card-hovor-bg: #e2e2e2;--card-shadow: rgb(104, 104, 104, 0.05) 0 2px 6px 0, rgba(211, 209, 209, 0.15) 0 0 0 1px;--footnote-target-bg: lightcyan;--tb-odd-bg: #fbfcfd;--tb-border-color: #eaeaea;--dash-color: silver;--kbd-wrap-color: #bdbdbd;--kbd-text-color: var(--text-color);--kbd-bg-color: white;--prompt-text-color: rgb(46, 46, 46, 0.77);--prompt-tip-bg: rgb(123, 247, 144, 0.2);--prompt-tip-icon-color: #03b303;--prompt-info-bg: #e1f5fe;--prompt-info-icon-color: #0070cb;--prompt-warning-bg: rgb(255, 243, 205);--prompt-warning-icon-color: #ef9c03;--prompt-danger-bg: rgb(248, 215, 218, 0.56);--prompt-danger-icon-color: #df3c30;--tag-border: #dee2e6;--tag-shadow: var(--btn-border-color);--tag-hover: rgb(222, 226, 230);--search-tag-bg: #f8f9fa;--categories-border: rgba(0, 0, 0, 0.125);--categories-hover-bg: var(--btn-border-color);--categories-icon-hover-color: darkslategray;--timeline-color: rgba(0, 0, 0, 0.075);--timeline-node-bg: #c2c6cc;--timeline-year-dot-color: #ffffff}html:not([data-mode]) [class^=prompt-],html[data-mode=light] [class^=prompt-]{--link-underline-color: rgb(219, 216, 216)}html:not([data-mode]) .dark,html[data-mode=light] .dark{display:none}html[data-mode=dark]{--main-bg: rgb(27, 27, 30);--mask-bg: rgb(68, 69, 70);--main-border-color: rgb(44, 45, 45);--text-color: rgb(175, 176, 177);--text-muted-color: #868686;--text-muted-hightlight-color: #aeaeae;--heading-color: #cccccc;--label-color: #a7a7a7;--blockquote-border-color: rgb(66, 66, 66);--blockquote-text-color: #868686;--link-color: rgb(138, 180, 248);--link-underline-color: rgb(82, 108, 150);--button-bg: #1e1e1e;--btn-border-color: #2e2f31;--btn-backtotop-color: var(--text-color);--btn-backtotop-border-color: #212122;--btn-box-shadow: var(--main-bg);--card-header-bg: #292929;--checkbox-color: rgb(118, 120, 121);--checkbox-checked-color: var(--link-color);--img-bg: radial-gradient(circle, rgb(22, 22, 24) 0%, rgb(32, 32, 32) 100%);--shimmer-bg: linear-gradient( 90deg, rgba(255, 255, 255, 0) 0%, rgba(58, 55, 55, 0.4) 50%, rgba(255, 255, 255, 0) 100% );--site-title-color: #717070;--site-subtitle-color: #868686;--sidebar-bg: #1e1e1e;--sidebar-border-color: #292929;--sidebar-muted-color: #868686;--sidebar-active-color: rgb(255, 255, 255, 0.95);--sidebar-hover-bg: #262626;--sidebar-btn-bg: #232328;--sidebar-btn-color: #787878;--avatar-border-color: rgb(206, 206, 206, 0.9);--topbar-bg: rgb(27, 27, 30, 0.64);--topbar-text-color: var(--text-color);--search-border-color: rgb(55, 55, 55);--search-icon-color: rgb(100, 102, 105);--input-focus-border-color: rgb(112, 114, 115);--post-list-text-color: rgb(175, 176, 177);--btn-patinator-text-color: var(--text-color);--btn-paginator-hover-color: #2e2e2e;--toc-highlight: rgb(116, 178, 243);--tag-hover: rgb(43, 56, 62);--tb-odd-bg: #252526;--tb-even-bg: rgb(31, 31, 34);--tb-border-color: var(--tb-odd-bg);--footnote-target-bg: rgb(63, 81, 181);--btn-share-color: #6c757d;--btn-share-hover-color: #bfc1ca;--card-bg: #1e1e1e;--card-hovor-bg: #464d51;--card-shadow: rgb(21, 21, 21, 0.72) 0 6px 18px 0, rgb(137, 135, 135, 0.24) 0 0 0 1px;--kbd-wrap-color: #6a6a6a;--kbd-text-color: #d3d3d3;--kbd-bg-color: #242424;--prompt-text-color: rgb(216, 212, 212, 0.75);--prompt-tip-bg: rgb(22, 60, 36, 0.64);--prompt-tip-icon-color: rgb(15, 164, 15, 0.81);--prompt-info-bg: rgb(7, 59, 104, 0.8);--prompt-info-icon-color: #0075d1;--prompt-warning-bg: rgb(90, 69, 3, 0.88);--prompt-warning-icon-color: rgb(255, 165, 0, 0.8);--prompt-danger-bg: rgb(86, 28, 8, 0.8);--prompt-danger-icon-color: #cd0202;--tag-border: rgb(59, 79, 88);--tag-shadow: rgb(32, 33, 33);--dash-color: rgb(63, 65, 68);--search-tag-bg: #292828;--categories-border: rgb(64, 66, 69, 0.5);--categories-hover-bg: rgb(73, 75, 76);--categories-icon-hover-color: white;--timeline-node-bg: rgb(150, 152, 156);--timeline-color: rgb(63, 65, 68);--timeline-year-dot-color: var(--timeline-color);color-scheme:dark}html[data-mode=dark] .light{display:none}html[data-mode=dark] hr{border-color:var(--main-border-color)}html[data-mode=dark] .categories.card,html[data-mode=dark] .list-group-item{background-color:var(--card-bg)}html[data-mode=dark] .categories .card-header{background-color:var(--card-header-bg)}html[data-mode=dark] .categories .list-group-item{border-left:none;border-right:none;padding-left:2rem;border-color:var(--categories-border)}html[data-mode=dark] .categories .list-group-item:last-child{border-bottom-color:var(--card-bg)}html[data-mode=dark] #archives li:nth-child(odd){background-image:linear-gradient(to left, rgb(26, 26, 30), rgb(39, 39, 45), rgb(39, 39, 45), rgb(39, 39, 45), rgb(26, 26, 30))}html[data-mode=dark] #disqus_thread{color-scheme:none}}@media(prefers-color-scheme: dark){html:not([data-mode]),html[data-mode=dark]{--main-bg: rgb(27, 27, 30);--mask-bg: rgb(68, 69, 70);--main-border-color: rgb(44, 45, 45);--text-color: rgb(175, 176, 177);--text-muted-color: #868686;--text-muted-hightlight-color: #aeaeae;--heading-color: #cccccc;--label-color: #a7a7a7;--blockquote-border-color: rgb(66, 66, 66);--blockquote-text-color: #868686;--link-color: rgb(138, 180, 248);--link-underline-color: rgb(82, 108, 150);--button-bg: #1e1e1e;--btn-border-color: #2e2f31;--btn-backtotop-color: var(--text-color);--btn-backtotop-border-color: #212122;--btn-box-shadow: var(--main-bg);--card-header-bg: #292929;--checkbox-color: rgb(118, 120, 121);--checkbox-checked-color: var(--link-color);--img-bg: radial-gradient(circle, rgb(22, 22, 24) 0%, rgb(32, 32, 32) 100%);--shimmer-bg: linear-gradient( 90deg, rgba(255, 255, 255, 0) 0%, rgba(58, 55, 55, 0.4) 50%, rgba(255, 255, 255, 0) 100% );--site-title-color: #717070;--site-subtitle-color: #868686;--sidebar-bg: #1e1e1e;--sidebar-border-color: #292929;--sidebar-muted-color: #868686;--sidebar-active-color: rgb(255, 255, 255, 0.95);--sidebar-hover-bg: #262626;--sidebar-btn-bg: #232328;--sidebar-btn-color: #787878;--avatar-border-color: rgb(206, 206, 206, 0.9);--topbar-bg: rgb(27, 27, 30, 0.64);--topbar-text-color: var(--text-color);--search-border-color: rgb(55, 55, 55);--search-icon-color: rgb(100, 102, 105);--input-focus-border-color: rgb(112, 114, 115);--post-list-text-color: rgb(175, 176, 177);--btn-patinator-text-color: var(--text-color);--btn-paginator-hover-color: #2e2e2e;--toc-highlight: rgb(116, 178, 243);--tag-hover: rgb(43, 56, 62);--tb-odd-bg: #252526;--tb-even-bg: rgb(31, 31, 34);--tb-border-color: var(--tb-odd-bg);--footnote-target-bg: rgb(63, 81, 181);--btn-share-color: #6c757d;--btn-share-hover-color: #bfc1ca;--card-bg: #1e1e1e;--card-hovor-bg: #464d51;--card-shadow: rgb(21, 21, 21, 0.72) 0 6px 18px 0, rgb(137, 135, 135, 0.24) 0 0 0 1px;--kbd-wrap-color: #6a6a6a;--kbd-text-color: #d3d3d3;--kbd-bg-color: #242424;--prompt-text-color: rgb(216, 212, 212, 0.75);--prompt-tip-bg: rgb(22, 60, 36, 0.64);--prompt-tip-icon-color: rgb(15, 164, 15, 0.81);--prompt-info-bg: rgb(7, 59, 104, 0.8);--prompt-info-icon-color: #0075d1;--prompt-warning-bg: rgb(90, 69, 3, 0.88);--prompt-warning-icon-color: rgb(255, 165, 0, 0.8);--prompt-danger-bg: rgb(86, 28, 8, 0.8);--prompt-danger-icon-color: #cd0202;--tag-border: rgb(59, 79, 88);--tag-shadow: rgb(32, 33, 33);--dash-color: rgb(63, 65, 68);--search-tag-bg: #292828;--categories-border: rgb(64, 66, 69, 0.5);--categories-hover-bg: rgb(73, 75, 76);--categories-icon-hover-color: white;--timeline-node-bg: rgb(150, 152, 156);--timeline-color: rgb(63, 65, 68);--timeline-year-dot-color: var(--timeline-color);color-scheme:dark}html:not([data-mode]) .light,html[data-mode=dark] .light{display:none}html:not([data-mode]) hr,html[data-mode=dark] hr{border-color:var(--main-border-color)}html:not([data-mode]) .categories.card,html:not([data-mode]) .list-group-item,html[data-mode=dark] .categories.card,html[data-mode=dark] .list-group-item{background-color:var(--card-bg)}html:not([data-mode]) .categories .card-header,html[data-mode=dark] .categories .card-header{background-color:var(--card-header-bg)}html:not([data-mode]) .categories .list-group-item,html[data-mode=dark] .categories .list-group-item{border-left:none;border-right:none;padding-left:2rem;border-color:var(--categories-border)}html:not([data-mode]) .categories .list-group-item:last-child,html[data-mode=dark] .categories .list-group-item:last-child{border-bottom-color:var(--card-bg)}html:not([data-mode]) #archives li:nth-child(odd),html[data-mode=dark] #archives li:nth-child(odd){background-image:linear-gradient(to left, rgb(26, 26, 30), rgb(39, 39, 45), rgb(39, 39, 45), rgb(39, 39, 45), rgb(26, 26, 30))}html:not([data-mode]) #disqus_thread,html[data-mode=dark] #disqus_thread{color-scheme:none}html[data-mode=light]{--main-bg: white;--mask-bg: #c1c3c5;--main-border-color: #f3f3f3;--text-color: #34343c;--text-muted-color: #757575;--text-muted-hightlight-color: inherit;--heading-color: #2a2a2a;--label-color: #585858;--blockquote-border-color: #eeeeee;--blockquote-text-color: #757575;--link-color: #0056b2;--link-underline-color: #dee2e6;--button-bg: #ffffff;--btn-border-color: #e9ecef;--btn-backtotop-color: #686868;--btn-backtotop-border-color: #f1f1f1;--btn-box-shadow: #eaeaea;--checkbox-color: #c5c5c5;--checkbox-checked-color: #07a8f7;--img-bg: radial-gradient( circle, rgb(255, 255, 255) 0%, rgb(239, 239, 239) 100% );--shimmer-bg: linear-gradient( 90deg, rgba(250, 250, 250, 0) 0%, rgba(232, 230, 230, 1) 50%, rgba(250, 250, 250, 0) 100% );--site-title-color: rgb(113, 113, 113);--site-subtitle-color: #717171;--sidebar-bg: #f6f8fa;--sidebar-border-color: #efefef;--sidebar-muted-color: #545454;--sidebar-active-color: #1d1d1d;--sidebar-hover-bg: rgb(223, 233, 241, 0.64);--sidebar-btn-bg: white;--sidebar-btn-color: #8e8e8e;--avatar-border-color: white;--topbar-bg: rgb(255, 255, 255, 0.7);--topbar-text-color: rgb(78, 78, 78);--search-border-color: rgb(240, 240, 240);--search-icon-color: #c2c6cc;--input-focus-border-color: #b8b8b8;--post-list-text-color: dimgray;--btn-patinator-text-color: #555555;--btn-paginator-hover-color: var(--sidebar-bg);--toc-highlight: #0550ae;--btn-share-color: gray;--btn-share-hover-color: #0d6efd;--card-bg: white;--card-hovor-bg: #e2e2e2;--card-shadow: rgb(104, 104, 104, 0.05) 0 2px 6px 0, rgba(211, 209, 209, 0.15) 0 0 0 1px;--footnote-target-bg: lightcyan;--tb-odd-bg: #fbfcfd;--tb-border-color: #eaeaea;--dash-color: silver;--kbd-wrap-color: #bdbdbd;--kbd-text-color: var(--text-color);--kbd-bg-color: white;--prompt-text-color: rgb(46, 46, 46, 0.77);--prompt-tip-bg: rgb(123, 247, 144, 0.2);--prompt-tip-icon-color: #03b303;--prompt-info-bg: #e1f5fe;--prompt-info-icon-color: #0070cb;--prompt-warning-bg: rgb(255, 243, 205);--prompt-warning-icon-color: #ef9c03;--prompt-danger-bg: rgb(248, 215, 218, 0.56);--prompt-danger-icon-color: #df3c30;--tag-border: #dee2e6;--tag-shadow: var(--btn-border-color);--tag-hover: rgb(222, 226, 230);--search-tag-bg: #f8f9fa;--categories-border: rgba(0, 0, 0, 0.125);--categories-hover-bg: var(--btn-border-color);--categories-icon-hover-color: darkslategray;--timeline-color: rgba(0, 0, 0, 0.075);--timeline-node-bg: #c2c6cc;--timeline-year-dot-color: #ffffff}html[data-mode=light] [class^=prompt-]{--link-underline-color: rgb(219, 216, 216)}html[data-mode=light] .dark{display:none}}body{background:var(--main-bg);padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);color:var(--text-color);-webkit-font-smoothing:antialiased;font-family:"Source Sans Pro","Microsoft Yahei",sans-serif}h1{font-size:1.92rem}h2{font-size:1.54rem}h3{font-size:1.36rem}h4{font-size:1.18rem}h5{font-size:1rem}a{text-decoration:none}img{max-width:100%;height:auto;transition:all .35s ease-in-out}.blur img{-webkit-filter:blur(20px);filter:blur(20px)}blockquote{border-left:5px solid var(--blockquote-border-color);padding-left:1rem;color:var(--blockquote-text-color)}blockquote>p:last-child{margin-bottom:0}blockquote[class^=prompt-]{border-left:0;position:relative;padding:1rem 1rem 1rem 3rem;color:var(--prompt-text-color)}blockquote[class^=prompt-]::before{text-align:center;width:3rem;position:absolute;left:.25rem;margin-top:.4rem;text-rendering:auto;-webkit-font-smoothing:antialiased}blockquote.prompt-tip{background-color:var(--prompt-tip-bg)}blockquote.prompt-tip::before{content:"";color:var(--prompt-tip-icon-color);font:var(--fa-font-regular)}blockquote.prompt-info{background-color:var(--prompt-info-bg)}blockquote.prompt-info::before{content:"";color:var(--prompt-info-icon-color);font:var(--fa-font-solid)}blockquote.prompt-warning{background-color:var(--prompt-warning-bg)}blockquote.prompt-warning::before{content:"";color:var(--prompt-warning-icon-color);font:var(--fa-font-solid)}blockquote.prompt-danger{background-color:var(--prompt-danger-bg)}blockquote.prompt-danger::before{content:"";color:var(--prompt-danger-icon-color);font:var(--fa-font-solid)}kbd{font-family:inherit;display:inline-block;vertical-align:middle;line-height:1.3rem;min-width:1.75rem;text-align:center;margin:0 .3rem;padding-top:.1rem;color:var(--kbd-text-color);background-color:var(--kbd-bg-color);border-radius:.25rem;border:solid 1px var(--kbd-wrap-color);box-shadow:inset 0 -2px 0 var(--kbd-wrap-color)}footer{background-color:var(--main-bg);height:5rem;border-top:1px solid var(--main-border-color)}footer p{text-align:center;margin-bottom:0}.access{top:2rem;transition:top .2s ease-in-out;margin-top:3rem;margin-bottom:4rem}.access:only-child{position:-webkit-sticky;position:sticky}.access>section{padding-left:1rem;border-left:1px solid var(--main-border-color)}.access>section:not(:last-child){margin-bottom:4rem}.access .content{font-size:.9rem}#panel-wrapper .panel-heading{font-family:inherit;line-height:inherit;color:var(--label-color);font-size:inherit;font-weight:600}#panel-wrapper .post-tag{line-height:1.05rem;font-size:.85rem;border-radius:.8rem;padding:.3rem .5rem;margin:0 .35rem .5rem 0}#panel-wrapper .post-tag:hover{transition:all .3s ease-in}#access-lastmod a{color:inherit}.footnotes>ol{padding-left:2rem;margin-top:.5rem}.footnotes>ol>li:not(:last-child){margin-bottom:.3rem}.footnotes>ol>li>p{margin-left:.25em;margin-top:0;margin-bottom:0}a.footnote{margin-left:1px;margin-right:1px;padding-left:2px;padding-right:2px;border-bottom-style:none !important}a.reversefootnote{font-size:.6rem;line-height:1;position:relative;bottom:.25em;margin-left:.25em;border-bottom-style:none !important}.table-wrapper{overflow-x:auto;margin-bottom:1.5rem}.table-wrapper>table{min-width:100%;overflow-x:auto;border-spacing:0}.table-wrapper>table thead{border-bottom:solid 2px rgba(210,215,217,.75)}.table-wrapper>table tbody tr{border-bottom:1px solid var(--tb-border-color)}.table-wrapper>table tbody tr:nth-child(2n){background-color:var(--tb-even-bg)}.table-wrapper>table tbody tr:nth-child(2n+1){background-color:var(--tb-odd-bg)}.preview-img{aspect-ratio:40/21;width:100%;height:100%;overflow:hidden}.preview-img:not(.no-bg){background:var(--img-bg)}.preview-img img{height:100%;-o-object-fit:cover;object-fit:cover}#post-list .preview-img img{width:100%}.post-preview{border:0;background:var(--card-bg);box-shadow:var(--card-shadow)}.post-preview::before{content:"";width:100%;height:100%;position:absolute;background-color:var(--card-hovor-bg);opacity:0;transition:opacity .35s ease-in-out}.post-preview:hover::before{opacity:.3}main{line-height:1.75}main h1{margin-top:2rem;margin-bottom:1.5rem}main p>a.popup:not(.normal):not(.left):not(.right){position:relative;left:50%;transform:translateX(-50%)}.content{font-size:1.08rem;margin-top:2rem;overflow-wrap:break-word}.content a.popup{margin-top:.5rem;margin-bottom:.5rem;cursor:zoom-in}.content ol:not([class]),.content ol.task-list,.content ul:not([class]),.content ul.task-list{-webkit-padding-start:1.75rem;padding-inline-start:1.75rem}.content ol:not([class]) li,.content ol.task-list li,.content ul:not([class]) li,.content ul.task-list li{margin:.25rem 0;padding-left:.25rem}.content ol:not([class]) ol,.content ol:not([class]) ul,.content ol.task-list ol,.content ol.task-list ul,.content ul:not([class]) ol,.content ul:not([class]) ul,.content ul.task-list ol,.content ul.task-list ul{-webkit-padding-start:1.25rem;padding-inline-start:1.25rem;margin:.5rem 0}.content ul.task-list{-webkit-padding-start:1.25rem;padding-inline-start:1.25rem}.content ul.task-list li{list-style-type:none;padding-left:0}.content ul.task-list li>i{width:2rem;margin-left:-1.25rem;color:var(--checkbox-color)}.content ul.task-list li>i.checked{color:var(--checkbox-checked-color)}.content ul.task-list li ul{-webkit-padding-start:1.75rem;padding-inline-start:1.75rem}.content ul.task-list input[type=checkbox]{margin:0 .5rem .2rem -1.3rem;vertical-align:middle}.content dl>dd{margin-left:1rem}.content ::marker{color:var(--text-muted-color)}.post-tag{display:inline-block;min-width:2rem;text-align:center;border-radius:.5rem;border:1px solid var(--btn-border-color);padding:0 .4rem;color:var(--text-muted-color);line-height:1.3rem}.post-tag:not(:last-child){margin-right:.2rem}.rounded-10{border-radius:10px !important}.img-link{color:rgba(0,0,0,0);display:inline-flex}.shimmer{overflow:hidden;position:relative;background:var(--img-bg)}.shimmer::before{content:"";position:absolute;background:var(--shimmer-bg);height:100%;width:100%;-webkit-animation:shimmer 1.3s infinite;animation:shimmer 1.3s infinite}@-webkit-keyframes shimmer{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}}@keyframes shimmer{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}}.embed-video{width:100%;height:100%;margin-bottom:1rem}.embed-video.youtube,.embed-video.bilibili{aspect-ratio:16/9}.embed-video.twitch{aspect-ratio:310/189}.btn-lang{border:1px solid !important;padding:1px 3px;border-radius:3px;color:var(--link-color)}.btn-lang:focus{box-shadow:none}.loaded{display:block !important}.d-flex.loaded{display:flex !important}.unloaded{display:none !important}.visible{visibility:visible !important}.hidden{visibility:hidden !important}.flex-grow-1{flex-grow:1 !important}.btn-box-shadow{box-shadow:var(--card-shadow)}.text-muted{color:var(--text-muted-color) !important}.tooltip-inner{font-size:.7rem;max-width:220px;text-align:left}.btn.btn-outline-primary:not(.disabled):hover{border-color:#007bff !important}.disabled{color:#cec4c4;pointer-events:auto;cursor:not-allowed}.hide-border-bottom{border-bottom:none !important}.input-focus{box-shadow:none;border-color:var(--input-focus-border-color) !important;background:center !important;transition:background-color .15s ease-in-out,border-color .15s ease-in-out}.left{float:left;margin:.75rem 1rem 1rem 0}.right{float:right;margin:.75rem 0 1rem 1rem}figure .mfp-title{text-align:center;padding-right:0;margin-top:.5rem}.mfp-img{transition:none}.mermaid{text-align:center}mjx-container{overflow-y:hidden;min-width:auto !important}#sidebar{padding-left:0;padding-right:0;position:fixed;top:0;left:0;height:100%;overflow-y:auto;width:260px;z-index:99;background:var(--sidebar-bg);border-right:1px solid var(--sidebar-border-color);-ms-overflow-style:none;scrollbar-width:none}#sidebar::-webkit-scrollbar{display:none}#sidebar .sidebar-bottom .mode-toggle:hover,#sidebar .sidebar-bottom a:hover,#sidebar .site-title a:hover{color:var(--sidebar-active-color)}#sidebar #avatar{display:block;width:7rem;height:7rem;overflow:hidden;box-shadow:var(--avatar-border-color) 0 0 0 2px;transform:translateZ(0)}#sidebar #avatar img{transition:transform .5s}#sidebar #avatar img:hover{transform:scale(1.2)}#sidebar .profile-wrapper{margin-top:2.5rem;margin-bottom:2.5rem;padding-left:2.5rem;padding-right:1.25rem;width:100%}#sidebar .site-title{font-family:inherit;font-weight:900;font-size:1.75rem;line-height:1.2;letter-spacing:.25px;margin-top:1.25rem;margin-bottom:.5rem}#sidebar .site-title a{color:var(--site-title-color)}#sidebar .site-subtitle{font-size:95%;color:var(--site-subtitle-color);margin-top:.25rem;word-spacing:1px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#sidebar ul{margin-bottom:2rem}#sidebar ul li.nav-item{opacity:.9;width:100%;padding-left:1.5rem;padding-right:1.5rem}#sidebar ul li.nav-item a.nav-link{padding-top:.6rem;padding-bottom:.6rem;display:flex;align-items:center;border-radius:.75rem;font-weight:600}#sidebar ul li.nav-item a.nav-link:hover{background-color:var(--sidebar-hover-bg)}#sidebar ul li.nav-item a.nav-link i{font-size:95%;opacity:.8;margin-right:1.5rem}#sidebar ul li.nav-item a.nav-link span{font-size:90%;letter-spacing:.2px}#sidebar ul li.nav-item.active .nav-link{color:var(--sidebar-active-color);background-color:var(--sidebar-hover-bg)}#sidebar ul li.nav-item.active .nav-link span{opacity:1}#sidebar ul li.nav-item:not(:first-child){margin-top:.25rem}#sidebar .sidebar-bottom{padding-left:2rem;padding-right:1rem;margin-bottom:1.5rem}#sidebar .sidebar-bottom .mode-toggle,#sidebar .sidebar-bottom a{width:1.75rem;height:1.75rem;margin-bottom:.5rem;border-radius:50%;color:var(--sidebar-btn-color);background-color:var(--sidebar-btn-bg);text-align:center;display:flex;align-items:center;justify-content:center;box-shadow:var(--sidebar-border-color) 0 0 0 1px}#sidebar .sidebar-bottom .mode-toggle:hover,#sidebar .sidebar-bottom a:hover{background-color:var(--sidebar-hover-bg)}#sidebar .sidebar-bottom a:not(:last-child){margin-right:.8rem}#sidebar .sidebar-bottom i{line-height:1.75rem}#sidebar .sidebar-bottom .mode-toggle{padding:0;border:0}#sidebar .sidebar-bottom .icon-border{margin-left:calc((.8rem - 3px)/2);margin-right:calc((.8rem - 3px)/2);background-color:var(--sidebar-btn-color);content:"";width:3px;height:3px;border-radius:50%;margin-bottom:.5rem}@media(hover: hover){#sidebar ul>li:last-child::after{transition:top .5s ease}.nav-link{transition:background-color .3s ease-in-out}.post-preview{transition:background-color .35s ease-in-out}}#search-result-wrapper{display:none;height:100%;width:100%;overflow:auto}#search-result-wrapper .content{margin-top:2rem}#topbar-wrapper{height:3rem;background-color:var(--topbar-bg)}#topbar button i{color:#999}#topbar #breadcrumb{font-size:1rem;color:var(--text-muted-color);padding-left:.5rem}#topbar #breadcrumb span:not(:last-child)::after{content:"›";padding:0 .3rem}::-webkit-input-placeholder{color:var(--text-muted-color) !important}::-moz-placeholder{color:var(--text-muted-color) !important}:-ms-input-placeholder{color:var(--text-muted-color) !important}::-ms-input-placeholder{color:var(--text-muted-color) !important}::placeholder{color:var(--text-muted-color) !important}:focus::-webkit-input-placeholder{opacity:.6}:focus::-moz-placeholder{opacity:.6}:focus:-ms-input-placeholder{opacity:.6}:focus::-ms-input-placeholder{opacity:.6}:focus::placeholder{opacity:.6}search{display:flex;width:100%;border-radius:1rem;border:1px solid var(--search-border-color);background:var(--main-bg);padding:0 .5rem}search i{z-index:2;font-size:.9rem;color:var(--search-icon-color)}#sidebar-trigger,#search-trigger{display:none}#search-cancel{color:var(--link-color);display:none;white-space:nowrap}#search-input{background:center;border:0;border-radius:0;padding:.18rem .3rem;color:var(--text-color);height:auto}#search-input:focus{box-shadow:none}#search-hints{padding:0 1rem}#search-hints h4{margin-bottom:1.5rem}#search-hints .post-tag{display:inline-block;line-height:1rem;font-size:1rem;background:var(--search-tag-bg);border:none;padding:.5rem;margin:0 1.25rem 1rem 0}#search-hints .post-tag::before{content:"#";color:var(--text-muted-color);padding-right:.2rem}#search-results{padding-bottom:3rem}#search-results a{font-size:1.4rem;line-height:2.5rem}#search-results>article{width:100%}#search-results>article:not(:last-child){margin-bottom:1rem}#search-results>article i{color:#818182;margin-right:.15rem;font-size:80%}#search-results>article>p{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}#topbar-title{display:none;font-size:1.1rem;font-weight:600;font-family:sans-serif;color:var(--topbar-text-color);text-align:center;width:70%;overflow:hidden;text-overflow:ellipsis;word-break:keep-all;white-space:nowrap}#mask{display:none;position:fixed;inset:0 0 0 0;height:100%;width:100%;z-index:1}[sidebar-display] #mask{display:block !important}#main-wrapper{position:relative;padding-left:0;padding-right:0}#main-wrapper>.container{min-height:100vh}#topbar-wrapper.row,#main-wrapper>.container>.row,#search-result-wrapper>.row{margin-left:0;margin-right:0}#tail-wrapper>:not(script){margin-top:3rem}#back-to-top{display:none;z-index:1;cursor:pointer;position:fixed;right:1rem;bottom:4.625rem;background:var(--button-bg);color:var(--btn-backtotop-color);padding:0;width:2.75rem;height:2.75rem;border-radius:50%;border:1px solid var(--btn-backtotop-border-color);transition:transform .2s ease-out;-webkit-transition:transform .2s ease-out}#back-to-top:hover{transform:translate3d(0, -5px, 0);-webkit-transform:translate3d(0, -5px, 0)}#back-to-top i{line-height:2.75rem;position:relative;bottom:2px}@-webkit-keyframes popup{from{opacity:0;bottom:0}}@keyframes popup{from{opacity:0;bottom:0}}#notification .toast-header{background:none;border-bottom:none;color:inherit}#notification .toast-body{font-family:Lato,sans-serif;line-height:1.25rem}#notification .toast-body button{font-size:90%;min-width:4rem}#notification.toast.show{display:block;min-width:20rem;border-radius:.5rem;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background-color:rgba(255,255,255,.5);color:rgba(27,27,30,.7294117647);position:fixed;left:50%;bottom:20%;transform:translateX(-50%);-webkit-animation:popup .8s;animation:popup .8s}@media all and (max-width: 576px){main .content>blockquote[class^=prompt-]{margin-left:-1rem;margin-right:-1rem;border-radius:0;max-width:none}#avatar{width:5rem;height:5rem}}@media all and (max-width: 768px){#main-wrapper>.container,#topbar{max-width:100%}#main-wrapper>.container{padding-left:0;padding-right:0}}@media all and (max-width: 849px){footer{transition:transform .4s ease;height:6rem;padding:1.5rem 0}[sidebar-display] #sidebar{transform:translateX(0)}[sidebar-display] #main-wrapper{transform:translateX(260px)}[sidebar-display] #back-to-top{visibility:hidden}#sidebar{transition:transform .4s ease;transform:translateX(-260px);-webkit-transform:translateX(-260px)}#main-wrapper{transition:transform .4s ease}#topbar,#main-wrapper>.container{max-width:100%}#search-result-wrapper{width:100%}#breadcrumb,search{display:none}#topbar-wrapper{transition:transform .4s ease,top .2s ease;left:0}main,#panel-wrapper{margin-top:0}#topbar-title,#sidebar-trigger,#search-trigger{display:block}#search-result-wrapper .content{letter-spacing:0}#tags{justify-content:center !important}h1.dynamic-title{display:none}h1.dynamic-title~.content{margin-top:2.5rem}}@media all and (min-width: 850px){html{overflow-y:scroll}#main-wrapper{margin-left:260px}#sidebar .profile-wrapper{margin-top:3rem}#search-hints{display:none}search{max-width:200px}#search-result-wrapper{max-width:1250px;justify-content:start !important}main h1{margin-top:3rem}div.content .table-wrapper>table{min-width:70%}#back-to-top{right:5%;bottom:3.625rem}#topbar-title{text-align:left}}@media all and (min-width: 992px)and (max-width: 1199px){#main-wrapper>.container .col-lg-11{flex:0 0 96%;max-width:96%}}@media all and (min-width: 850px)and (max-width: 1199px){#search-results>div{max-width:700px}#breadcrumb{width:65%;overflow:hidden;text-overflow:ellipsis;word-break:keep-all;white-space:nowrap}}@media all and (max-width: 1199px){#panel-wrapper{display:none}#main-wrapper>.container>div.row{justify-content:center !important}}@media all and (min-width: 1200px){search{margin-right:4rem}#search-input{transition:all .3s ease-in-out}#search-results>article{width:45%}#search-results>article:nth-child(odd){margin-right:1.5rem}#search-results>article:nth-child(even){margin-left:1.5rem}#search-results>article:last-child:nth-child(odd){position:relative;right:24.3%}.content{font-size:1.03rem}}@media all and (min-width: 1400px){#back-to-top{right:calc((100vw - 260px - 1140px)/2 + 3rem)}}@media all and (min-width: 1650px){#main-wrapper{margin-left:300px}#topbar-wrapper{left:300px}search{margin-right:calc(112.5px - .75rem)}#main-wrapper>.container{max-width:1250px;padding-left:1.75rem !important;padding-right:1.75rem !important}main.col-12,#tail-wrapper{padding-right:4.5rem !important}#back-to-top{right:calc((100vw - 300px - 1250px)/2 + 2rem)}#sidebar{width:300px}#sidebar .profile-wrapper{margin-top:3.5rem;margin-bottom:2.5rem;padding-left:3.5rem}#sidebar ul li.nav-item{padding-left:2.75rem;padding-right:2.75rem}#sidebar .sidebar-bottom{padding-left:2.75rem;margin-bottom:1.75rem}#sidebar .sidebar-bottom a:not(:last-child){margin-right:1rem}#sidebar .sidebar-bottom .icon-border{margin-left:calc((1rem - 3px)/2);margin-right:calc((1rem - 3px)/2)}}#post-list{margin-top:2rem}#post-list .card-wrapper:hover{text-decoration:none}#post-list .card-wrapper:not(:last-child){margin-bottom:1.25rem}#post-list .card{border:0;background:none}#post-list .card .preview-img img,#post-list .card .preview-img{border-radius:.625rem .625rem 0 0}#post-list .card .card-body{height:100%;padding:1rem}#post-list .card .card-body .card-title{color:var(--heading-color) !important;font-size:1.25rem}#post-list .card .card-body .post-meta,#post-list .card .card-body .card-text.content{color:var(--text-muted-color) !important}#post-list .card .card-body .card-text.content p{line-height:1.5;margin:0}#post-list .card .card-body .post-meta i:not(:first-child){margin-left:1.5rem}#post-list .card .card-body .post-meta em{color:inherit}#post-list .card .card-body .post-meta>div:first-child{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pagination{color:var(--text-color);font-family:Lato,sans-serif;justify-content:space-evenly}.pagination a:hover{text-decoration:none}.pagination .page-item .page-link{color:var(--btn-patinator-text-color);padding:0 .6rem;display:-webkit-box;-webkit-box-pack:center;-webkit-box-align:center;border-radius:.5rem;border:0;background-color:inherit}.pagination .page-item.active .page-link{background-color:var(--btn-paginator-hover-color)}.pagination .page-item:not(.active) .page-link:hover{box-shadow:inset var(--btn-border-color) 0 0 0 1px}.pagination .page-item.disabled{cursor:not-allowed}.pagination .page-item.disabled .page-link{color:rgba(108,117,125,.57)}@media all and (min-width: 768px){#post-list .card .preview-img,#post-list .card .preview-img img{border-radius:0 .625rem .625rem 0}#post-list .card .card-body{padding:1.75rem 1.75rem 1.25rem 1.75rem}#post-list .card .card-body .card-text{display:inherit !important}#post-list .card .card-body .post-meta i:not(:first-child){margin-left:1.75rem}}@media all and (max-width: 830px){.pagination .page-item:not(:first-child):not(:last-child){display:none}}@media all and (min-width: 831px){#post-list{margin-top:2.5rem}.pagination{font-size:.85rem;justify-content:center}.pagination .page-item:not(:last-child){margin-right:.7rem}.pagination .page-index{display:none}}.post-navigation .btn.disabled,.post-navigation .btn{width:50%;position:relative;border-color:var(--btn-border-color)}h1+.post-meta>span+span::before{content:"•";padding-left:.25rem;padding-right:.25rem}h1+.post-meta em a{color:inherit}.post-tail-wrapper{margin-top:6rem;border-bottom:1px double var(--main-border-color)}.post-tail-wrapper .license-wrapper{line-height:1.2rem}.post-tail-wrapper .share-wrapper{vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.post-tail-wrapper .share-wrapper .share-icons>*,.post-tail-wrapper .share-wrapper .share-icons i{font-size:1.125rem}.post-tail-wrapper .share-wrapper .share-icons{display:flex}.post-tail-wrapper .share-wrapper .share-icons i{color:var(--btn-share-color)}.post-tail-wrapper .share-wrapper .share-icons>*{margin-left:.5rem}.post-tail-wrapper .share-wrapper .share-icons button{padding:0;border:none;line-height:inherit}.share-mastodon{--wc-stm-font-family: $font-family-base;--wc-stm-dialog-background-color: var(--card-bg);--wc-stm-form-button-border: 1px solid var(--btn-border-color);--wc-stm-form-submit-background-color: var(--sidebar-btn-bg);--wc-stm-form-cancel-background-color: var(--sidebar-btn-bg);--wc-stm-form-button-background-color-hover: #007bff;--wc-stm-form-button-color-hover: white;font-size:1rem}.post-tags{line-height:2rem}.post-navigation .btn:not(:hover){color:var(--link-color)}.post-navigation .btn:hover:not(.disabled)::before{color:#f5f5f5}.post-navigation .btn.disabled{pointer-events:auto;cursor:not-allowed;background:none;color:gray}.post-navigation .btn.btn-outline-primary.disabled:focus{box-shadow:none}.post-navigation .btn::before{color:var(--text-muted-color);font-size:.65rem;text-transform:uppercase;content:attr(aria-label)}.post-navigation .btn:first-child{border-radius:.625rem 0 0 .625rem;left:.5px}.post-navigation .btn:last-child{border-radius:0 .625rem .625rem 0;right:.5px}.post-navigation p{font-size:1.1rem;line-height:1.5rem;margin-top:.3rem;white-space:normal}@media(hover: hover){.post-navigation .btn,.post-navigation .btn::before{transition:all .35s ease-in-out}}@-webkit-keyframes fade-up{from{opacity:0;position:relative;top:2rem}to{opacity:1;position:relative;top:0}}@keyframes fade-up{from{opacity:0;position:relative;top:2rem}to{opacity:1;position:relative;top:0}}#toc-wrapper{border-left:1px solid rgba(158,158,158,.17);position:-webkit-sticky;position:sticky;top:4rem;transition:top .2s ease-in-out;-webkit-animation:fade-up .8s;animation:fade-up .8s}#toc-wrapper ul{list-style:none;font-size:.85rem;line-height:1.25;padding-left:0}#toc-wrapper ul li:not(:last-child){margin:.4rem 0}#toc-wrapper ul li a{padding:.2rem 0 .2rem 1.25rem}#toc-wrapper ul .toc-link{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#toc-wrapper ul .toc-link:hover{color:var(--toc-highlight);text-decoration:none}#toc-wrapper ul .toc-link::before{display:none}#toc-wrapper ul .is-active-link{color:var(--toc-highlight) !important;font-weight:600}#toc-wrapper ul .is-active-link::before{display:inline-block;width:1px;left:-1px;height:1.25rem;background-color:var(--toc-highlight) !important}#toc-wrapper ul ul{padding-left:.75rem}#related-posts>h3{color:var(--label-color);font-size:1.1rem;font-weight:600}#related-posts time{color:var(--text-muted-color)}#related-posts p{font-size:.9rem;margin-bottom:.5rem;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}#disqus_thread{min-height:8.5rem}.utterances{max-width:100%}.post-tail-wrapper .share-wrapper .share-icons>*:hover i{color:var(--btn-share-hover-color) !important}.share-label{color:inherit;font-size:inherit;font-weight:400}.share-label::after{content:":"}@media all and (max-width: 576px){.post-tail-bottom{flex-wrap:wrap-reverse !important}.post-tail-bottom>div:first-child{width:100%;margin-top:1rem}}@media all and (max-width: 768px){.content>p>img{max-width:calc(100% + 1rem)}}@media all and (max-width: 849px){.post-navigation{padding-left:0;padding-right:0;margin-left:-0.5rem;margin-right:-0.5rem}}.tag{border-radius:.7em;padding:6px 8px 7px;margin-right:.8rem;line-height:3rem;letter-spacing:0;border:1px solid var(--tag-border) !important;box-shadow:0 0 3px 0 var(--tag-shadow)}.tag span{margin-left:.6em;font-size:.7em;font-family:Oswald,sans-serif}#archives{letter-spacing:.03rem}#archives ul li::before,#archives .year:first-child::before,#archives .year::before{content:"";width:4px;position:relative;float:left;background-color:var(--timeline-color)}#archives .year{height:3.5rem;font-size:1.5rem;position:relative;left:2px;margin-left:-4px}#archives .year::before{height:72px;left:79px;bottom:16px}#archives .year:first-child::before{height:32px;top:24px}#archives .year::after{content:"";display:inline-block;position:relative;border-radius:50%;width:12px;height:12px;left:21.5px;border:3px solid;background-color:var(--timeline-year-dot-color);border-color:var(--timeline-node-bg);box-shadow:0 0 2px 0 #c2c6cc;z-index:1}#archives ul li{font-size:1.1rem;line-height:3rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#archives ul li:nth-child(odd){background-color:var(--main-bg, #ffffff);background-image:linear-gradient(to left, #ffffff, #fbfbfb, #fbfbfb, #fbfbfb, #ffffff)}#archives ul li::before{top:0;left:77px;height:3.1rem}#archives ul:last-child li:last-child::before{height:1.5rem}#archives .date{white-space:nowrap;display:inline-block;position:relative;right:.5rem}#archives .date.month{width:1.4rem;text-align:center}#archives .date.day{font-size:85%;font-family:Lato,sans-serif}#archives a{margin-left:2.5rem;position:relative;top:.1rem}#archives a:hover{border-bottom:none}#archives a::before{content:"";display:inline-block;position:relative;border-radius:50%;width:8px;height:8px;float:left;top:1.35rem;left:71px;background-color:var(--timeline-node-bg);box-shadow:0 0 3px 0 #c2c6cc;z-index:1}@media all and (max-width: 576px){#archives{margin-top:-1rem}#archives ul{letter-spacing:0}}.categories i{color:gray}.categories{margin-bottom:2rem;border-color:var(--categories-border)}.categories .card-header{padding:.75rem;border-radius:calc(.625rem - 1px);border-bottom:0}.categories .card-header.hide-border-bottom{border-bottom-left-radius:0;border-bottom-right-radius:0}.categories i{font-size:86%}.categories .list-group-item{border-left:none;border-right:none;padding-left:2rem}.categories .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.categories .list-group-item:last-child{border-bottom:0}.category-trigger{width:1.7rem;height:1.7rem;border-radius:50%;text-align:center;color:#6c757d !important}.category-trigger i{position:relative;height:.7rem;width:1rem;transition:transform 300ms ease}.category-trigger:hover i{color:var(--categories-icon-hover-color)}@media(hover: hover){.category-trigger:hover{background-color:var(--categories-hover-bg)}}.rotate{transform:rotate(-90deg)}.dash{margin:0 .5rem .6rem .5rem;border-bottom:2px dotted var(--dash-color)}#page-category ul>li,#page-tag ul>li{line-height:1.5rem;padding:.6rem 0}#page-category ul>li::before,#page-tag ul>li::before{background:#999;width:5px;height:5px;border-radius:50%;display:block;content:"";position:relative;top:.6rem;margin-right:.5rem}#page-category ul>li>a,#page-tag ul>li>a{font-size:1.1rem}#page-tag h1>i{font-size:1.2rem}#page-category h1>i{font-size:1.25rem}#page-category a:hover,#page-tag a:hover,#access-lastmod a:hover{margin-bottom:-1px}@media all and (max-width: 576px){#page-category ul>li::before,#page-tag ul>li::before{margin:0 .5rem}#page-category ul>li>a,#page-tag ul>li>a{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/assets/css/style.css.map b/assets/css/style.css.map new file mode 100644 index 0000000..5b68a71 --- /dev/null +++ b/assets/css/style.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/addon/module.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/addon/variables.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/addon/syntax.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/colors/syntax-light.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/colors/syntax-dark.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/addon/commons.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/colors/typography-light.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/colors/typography-dark.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/layout/home.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/layout/post.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/layout/tags.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/layout/archives.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/layout/categories.scss","../../vendor/bundle/ruby/3.2.0/gems/jekyll-theme-chirpy-6.4.2/_sass/layout/category-tag.scss"],"names":[],"mappings":"CAMA,iCACE,2BACA,gBACA,YCuBoB,kCDnBpB,gCACE,kBACA,sBAEA,wDACE,aAMJ,4CACE,cAGF,qBACE,4CACE,kBACA,UACA,2DAIA,oEACE,mBACA,UACA,0DAMR,sCACE,4BACA,uCAGF,+DACE,mBACA,cACA,mBAGF,2QACE,yBACA,gCACA,qBAGF,4CACE,wBAGF,sEACE,oDAGF,4EACE,+BAGF,gFACE,yBACA,sBACA,qBACA,iBAGF,6LACE,mBAGF,yFACE,eAGF,4EACE,kBAGF,wMACE,cC5EY,QDgFZ,oBACE,cACA,kBACA,kBACA,cACA,UACA,cAIJ,iDACE,iCACA,yBACA,sBACA,qBACA,iBAGF,iHACE,oBACA,gBACA,uBACA,qBACA,4BAGF,mFACE,yCACA,gBAGF,kFACE,iBAGF,2BACE,gBAIA,mCACE,2CACA,uBACA,0BACA,kBACA,8CEvIF,oCACE,4CCHF,iCACA,8BACA,mCACA,kCACA,0BACA,sBACA,kCACA,mCACA,kCACA,mCAEA,8EACE,0BAKF,oFACE,YAGF,sFACE,SAGF,0IAEE,cACA,yBAGF,ogBAOE,cAGF,0EACE,cAGF,0EACE,cACA,yBAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cACA,yBAGF,0EACE,cAGF,guBAUE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,kXAKE,cAGF,kJAEE,cAGF,0EACE,cACA,iBAGF,0EACE,cACA,iBAGF,4yBAWE,cAGF,0EACE,cAGF,oJAEE,cAGF,4EACE,cACA,yBAGF,glBAQE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cAGF,0EACE,cACA,kBAGF,0EACE,cACA,iBDrMA,qBETF,iCACA,8BACA,mCACA,kCACA,0BACA,sBACA,kCACA,mCACA,kCACA,mCACA,+BAEA,oCACE,cAKF,yCACE,YAGF,0CACE,SAGF,mEAEE,cACA,yBAGF,qCACE,cACA,yBAGF,4PAOE,cAGF,oCACE,cAGF,oCACE,cAGF,uEAEE,cAGF,uEAEE,cAGF,oCACE,cAGF,oCACE,cACA,yBAGF,oCACE,cACA,yBACA,iBAGF,mLAKE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,mUASE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,oCACE,cAGF,+RAQE,cAGF,oCACE,eF9IF,mCACE,2CEfF,iCACA,8BACA,mCACA,kCACA,0BACA,sBACA,kCACA,mCACA,kCACA,mCACA,+BAEA,yEACE,cAKF,mFACE,YAGF,qFACE,SAGF,wIAEE,cACA,yBAGF,2EACE,cACA,yBAGF,+fAOE,cAGF,yEACE,cAGF,yEACE,cAGF,gJAEE,cAGF,gJAEE,cAGF,yEACE,cAGF,yEACE,cACA,yBAGF,yEACE,cACA,yBACA,iBAGF,2WAKE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,+oBASE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,yEACE,cAGF,skBAQE,cAGF,yEACE,cFxIA,sBCnBF,iCACA,8BACA,mCACA,kCACA,0BACA,sBACA,kCACA,mCACA,kCACA,mCAEA,uCACE,0BAKF,0CACE,YAGF,2CACE,SAGF,qEAEE,cACA,yBAGF,kQAOE,cAGF,qCACE,cAGF,qCACE,cACA,yBAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cACA,yBAGF,qCACE,cAGF,gXAUE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,yLAKE,cAGF,yEAEE,cAGF,qCACE,cACA,iBAGF,qCACE,cACA,iBAGF,sZAWE,cAGF,qCACE,cAGF,0EAEE,cAGF,sCACE,cACA,yBAGF,wSAQE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cAGF,qCACE,cACA,kBAGF,qCACE,cACA,kBDlLJ,kDACE,2CAGF,cACE,kBACA,qBAGF,mBACE,qCACA,iBACA,oBAGF,WAQE,cACA,sBAEA,eACE,gBACA,UDtCa,OCuCb,mBACA,iBAKE,gCACE,qBACA,iBACA,oBAGF,+BACE,8BAGF,wBACE,iBACA,kBAKN,mBACE,iBACA,oCACA,yBACA,sBACA,oBACA,qBACA,iBAIJ,KACE,qBACA,iBACA,aACA,wBAEA,uBACE,UDhFa,OCiFb,gBACA,sBACA,kBACA,uCAGF,cACE,yBACA,iCACA,gBACA,UAGF,yBACE,iBACA,cAGF,+BACE,mBAGF,gBACE,cAWF,gBACE,yBACA,8BACA,oBAIJ,sBAIE,kDAEA,+BFIA,YEHiB,MFIjB,aEJiB,MAEf,gBAGF,iCACE,yBACA,0BAUA,2IACE,qBACA,eAEA,2KACE,aAMR,aAGE,aACA,8BACA,mBACA,ODlKmB,QCmKnB,mBACA,oBAGA,kBACE,YDxKiB,QC2KjB,oBACE,eACA,MD1KY,QC2KZ,oCAEA,0BACE,cAIK,2BACP,kBACA,QAIF,yBACE,8BACA,iBACA,gBACA,oCAKJ,oBAIE,+BACA,ODzMiB,QC0MjB,MD1MiB,QC2MjB,UACA,yBAEA,sBACE,oCAIA,mCACE,4CAGF,+BACE,qCAIJ,0BACE,aAGF,yCACE,uCAEA,2CACE,WAMR,kCAEI,+BFzGF,YE0GmB,EFzGnB,aEyGmB,EAEf,cDrPQ,QCwPV,mCF/GF,YEgHmB,EF/GnB,aE+GmB,EAIf,2CACE,WACA,qBACA,YALW,KAMX,MD1PQ,OC2PR,OD3PQ,OC4PR,kBACA,gDACA,gGAMF,wCAEE,uBG7RR,KAuBE,eAtBA,oCACE,4CCEF,iBACA,mBACA,6BAGA,sBACA,4BACA,uCACA,yBACA,uBACA,mCACA,iCACA,sBACA,gCACA,qBACA,4BACA,+BACA,sCACA,0BACA,0BACA,kCACA,oFAKA,2HAQA,uCACA,+BACA,sBACA,gCACA,+BACA,gCACA,6CACA,wBACA,6BACA,6BAGA,qCACA,qCACA,0CACA,6BACA,oCAGA,gCACA,oCACA,+CAGA,yBACA,wBACA,iCACA,iBACA,yBACA,yFAEA,gCACA,qBACA,2BACA,qBACA,0BACA,oCACA,sBACA,2CACA,yCACA,iCACA,0BACA,kCACA,wCACA,qCACA,6CACA,oCAGA,sBACA,sCACA,gCACA,yBAWA,0CACA,+CACA,6CAGA,uCACA,4BACA,mCAhBA,8EACE,2CAGF,wDACE,aD1FA,qBEHF,2BACA,2BACA,qCAGA,iCACA,4BACA,uCACA,yBACA,uBACA,2CACA,iCACA,iCACA,0CACA,qBACA,4BACA,yCACA,sCACA,iCACA,0BACA,qCACA,4CACA,4EACA,0HAQA,4BACA,+BACA,sBACA,gCACA,+BACA,iDACA,4BACA,0BACA,6BACA,+CAGA,mCACA,uCACA,uCACA,wCACA,+CAGA,2CACA,8CACA,qCAGA,oCACA,6BACA,qBACA,8BACA,oCACA,uCACA,2BACA,iCACA,mBACA,yBACA,sFAEA,0BACA,0BACA,wBACA,8CACA,uCACA,gDACA,uCACA,kCACA,0CACA,mDACA,wCACA,oCAGA,8BACA,8BACA,8BACA,yBAGA,0CACA,uCACA,qCAGA,uCACA,kCACA,iDA4CA,kBA1CA,4BACE,aAGF,wBACE,sCAIF,4EAEE,gCAIA,8CACE,uCAGF,kDACE,iBACA,kBACA,kBACA,sCAEA,6DACE,mCAKN,iDACE,+HAaF,oCACE,mBFtIF,mCACE,2CETF,2BACA,2BACA,qCAGA,iCACA,4BACA,uCACA,yBACA,uBACA,2CACA,iCACA,iCACA,0CACA,qBACA,4BACA,yCACA,sCACA,iCACA,0BACA,qCACA,4CACA,4EACA,0HAQA,4BACA,+BACA,sBACA,gCACA,+BACA,iDACA,4BACA,0BACA,6BACA,+CAGA,mCACA,uCACA,uCACA,wCACA,+CAGA,2CACA,8CACA,qCAGA,oCACA,6BACA,qBACA,8BACA,oCACA,uCACA,2BACA,iCACA,mBACA,yBACA,sFAEA,0BACA,0BACA,wBACA,8CACA,uCACA,gDACA,uCACA,kCACA,0CACA,mDACA,wCACA,oCAGA,8BACA,8BACA,8BACA,yBAGA,0CACA,uCACA,qCAGA,uCACA,kCACA,iDA4CA,kBA1CA,yDACE,aAGF,iDACE,sCAIF,0JAEE,gCAIA,6FACE,uCAGF,qGACE,iBACA,kBACA,kBACA,sCAEA,2HACE,mCAKN,mGACE,+HAaF,yEACE,kBFhIA,sBCdF,iBACA,mBACA,6BAGA,sBACA,4BACA,uCACA,yBACA,uBACA,mCACA,iCACA,sBACA,gCACA,qBACA,4BACA,+BACA,sCACA,0BACA,0BACA,kCACA,oFAKA,2HAQA,uCACA,+BACA,sBACA,gCACA,+BACA,gCACA,6CACA,wBACA,6BACA,6BAGA,qCACA,qCACA,0CACA,6BACA,oCAGA,gCACA,oCACA,+CAGA,yBACA,wBACA,iCACA,iBACA,yBACA,yFAEA,gCACA,qBACA,2BACA,qBACA,0BACA,oCACA,sBACA,2CACA,yCACA,iCACA,0BACA,kCACA,wCACA,qCACA,6CACA,oCAGA,sBACA,sCACA,gCACA,yBAWA,0CACA,+CACA,6CAGA,uCACA,4BACA,mCAhBA,uCACE,2CAGF,4BACE,cDvEJ,KACE,0BACA,kHAEA,wBACA,mCACA,YJHiB,+CISjB,GAeI,kBAfJ,GAeI,kBAfJ,GAeI,kBAfJ,GAeI,kBAfJ,GAiBI,eAKN,EAGE,qBAGF,IACE,eACA,YACA,gCAEA,UAGE,0BACA,kBAIJ,WACE,qDACA,kBACA,mCAEA,wBACE,gBAGF,2BACE,cACA,kBACA,4BACA,+BAIA,mCACE,kBACA,WACA,kBACA,YACA,iBACA,oBACA,mCLqFJ,sBACE,sCAEA,8BACE,QKrFmB,ILsFnB,mCACA,4BANJ,uBACE,uCAEA,+BACE,QKpFoB,ILqFpB,oCACA,0BANJ,0BACE,0CAEA,kCACE,QKnFuB,ILoFvB,uCACA,0BANJ,yBACE,yCAEA,iCACE,QKlFsB,ILmFtB,sCACA,0BKjFN,IACE,oBACA,qBACA,sBACA,mBACA,kBACA,kBACA,eACA,kBACA,4BACA,qCACA,qBACA,uCACA,gDAGF,OACE,gCACA,OJtHc,KIuHd,8CAYA,SACE,kBACA,gBAcJ,QACE,SACA,+BACA,gBACA,mBAEA,mBACE,wBACA,gBAGF,gBACE,kBACA,+CAEA,iCACE,mBAIJ,iBACE,gBAMF,8BACE,oBACA,oBLbF,MADwD,mBAExD,UKciB,QLbjB,YAH2C,IKmB3C,yBACE,oBACA,iBACA,oBACA,oBACA,wBAEA,+BACE,2BAMJ,kBAOE,cAIJ,cACE,kBACA,iBAGE,kCACE,oBAKF,mBACE,kBACA,aACA,gBAMK,WLtFT,YKuFiB,ILtFjB,aKsFiB,IL7EjB,aK8EiB,IL7EjB,cK6EiB,IAEf,oCASO,kBACP,gBACA,cACA,kBACA,aACA,kBACA,oCAOJ,eACE,gBACA,qBAEA,qBACE,eACA,gBACA,iBAEA,2BACE,8CAQA,8BACE,+CAEA,4CACE,mCAGF,8CACE,kCAaV,aACE,mBACA,WACA,YACA,gBAIA,yBACE,yBAGF,iBACE,YACA,oBACA,iBAIS,4BACP,WAKN,cAGE,SACA,0BACA,8BAEA,sBAGE,WACA,WACA,YACA,kBACA,sCACA,UACA,oCAIA,4BACE,WAKN,KACE,iBAEA,QACE,gBACA,qBAKE,mDLvLJ,kBACA,SACA,2BKkNF,SACE,kBACA,gBACA,yBAGE,iBL5PF,WK+PmB,ML9PnB,cK8PmB,MAEf,eAcF,8FAEE,8BACA,6BAEA,0GACE,gBACA,oBAGF,oNAEE,8BACA,6BACA,eAKN,sBACE,8BACA,6BAEA,yBACE,qBACA,eAGA,2BACE,WACA,qBACA,4BAEA,mCACE,oCAIJ,4BACE,8BACA,6BAIJ,2CACE,6BACA,sBAIJ,eACE,iBAGF,kBACE,8BAQJ,UACE,qBACA,eACA,kBACA,oBACA,yCACA,gBACA,8BACA,mBAEA,2BACE,mBAIJ,YACE,8BAGF,UACE,oBACA,oBAGF,SACE,gBACA,kBACA,yBAEA,iBACE,WACA,kBACA,6BACA,YACA,WACA,wCACA,gCAGF,2BACE,GACE,4BAGF,KACE,4BAIJ,mBACE,GACE,4BAGF,KACE,4BAKN,aACE,WACA,YACA,mBAIA,2CAEE,kBAGF,oBACE,qBAKJ,UACE,4BACA,gBACA,kBACA,wBAEA,gBACE,gBAMJ,QACE,yBAES,eACP,wBAIJ,UACE,wBAGF,SACE,8BAGF,QACE,6BAGF,aACE,uBAGF,gBACE,8BAIF,YACE,yCAIF,eACE,gBACA,gBACA,gBAKA,8CACE,gCAIJ,UACE,cACA,oBACA,mBAGF,oBACE,8BAGF,aACE,gBACA,wDACA,6BACA,2EAGF,MACE,WACA,0BAGF,OACE,YACA,0BAOF,kBACE,kBACA,gBACA,iBAGF,SACE,gBAIF,SACE,kBAIF,cACE,kBACA,0BASF,SLngBE,aKogBe,ELngBf,cKmgBe,EAEf,eACA,MACA,OACA,YACA,gBACA,MJ1qBc,MI2qBd,WACA,6BACA,mDAQA,wBACA,qBANA,4BACE,aAQA,0GACE,kCAQJ,iBACE,cACA,WACA,YACA,gBACA,gDACA,wBAEA,qBACE,yBAEA,2BACE,qBAKN,0BLnkBA,WKokBiB,OLnkBjB,cKmkBiB,OAGf,oBACA,sBACA,WAGF,qBACE,oBACA,gBACA,kBACA,gBACA,qBACA,mBACA,oBAEA,uBAIE,8BAIJ,wBACE,cACA,iCACA,kBACA,iBACA,yBACA,sBACA,qBACA,iBAGF,YACE,mBAEA,wBACE,WACA,WACA,oBACA,qBAEA,mCLvmBJ,YKwmBqB,MLvmBrB,eKumBqB,MAEf,aACA,mBACA,qBACA,gBAEA,yCACE,yCAGF,qCACE,cACA,WACA,oBAGF,wCACE,cACA,oBAKF,yCACE,kCACA,yCAEA,8CACE,UAKN,0CACE,kBAKN,yBACE,kBACA,mBACA,qBAIA,iEACE,MAHS,QAIT,OAJS,QAKT,cApJG,MAqJH,kBACA,+BACA,uCACA,kBACA,aACA,mBACA,uBACA,iDAEA,6EACE,yCASF,4CACE,aJv0BK,MI20BT,2BACE,YA/BS,QAkCX,sCACE,UACA,SAOF,sCLrsBF,YKusBmB,sBLtsBnB,aKssBmB,sBAEf,0CACA,WACA,MAjMa,IAkMb,OAlMa,IAmMb,kBACA,cAnMG,MAwMT,qBACE,iCACE,wBAGF,UACE,4CAGF,cACE,8CAIJ,uBACE,aACA,YACA,WACA,cAEA,gCACE,gBAMJ,gBACE,OJ73Bc,KI83Bd,kCAIA,iBACE,WAGF,oBACE,eACA,8BACA,mBAQI,iDACE,YACA,gBAOV,4BL7vBE,yCKiwBF,mBLjwBE,yCKqwBF,uBLrwBE,yCKywBF,wBLzwBE,yCK6wBF,cL7wBE,yCKixBF,kCL7wBE,WKixBF,yBLjxBE,WKqxBF,6BLrxBE,WKyxBF,8BLzxBE,WK6xBF,oBL7xBE,WKiyBF,OACE,aACA,WACA,mBACA,4CACA,0BACA,gBAEA,SACE,UACA,gBACA,+BAIJ,iCAEE,aAIF,eACE,wBACA,aACA,mBAKF,cACE,kBACA,SACA,gBACA,qBACA,wBACA,YAEA,oBACE,gBAIJ,cACE,eAEA,iBACE,qBAGF,wBACE,qBACA,iBACA,eACA,gCACA,YACA,cACA,wBAEA,gCACE,YACA,8BACA,oBAON,gBACE,oBAEA,kBASE,iBACA,mBAGF,wBACE,WAEA,yCACE,mBAIF,0BACE,cACA,oBACA,cAGF,0BACE,gBACA,uBACA,oBACA,qBACA,4BAKN,cACE,aACA,iBACA,gBACA,uBACA,+BACA,kBACA,UACA,gBACA,uBACA,oBACA,mBAGF,MACE,aACA,eACA,cACA,YACA,WACA,UAES,wBACP,yBAMJ,cACE,kBLp7BA,aKs7Be,ELr7Bf,cKq7Be,EAEf,yBACE,iBAIJ,8ELv8BE,YK08Be,ELz8Bf,aKy8Be,EAIf,2BACE,gBAMJ,aACE,aACA,UACA,eACA,eACA,WACA,gBACA,4BACA,iCACA,UACA,MJvmCc,QIwmCd,OJxmCc,QIymCd,kBACA,mDACA,kCACA,0CAEA,mBACE,kCACA,0CAGF,eACE,YJpnCY,QIqnCZ,kBACA,WAKF,yBACE,KACE,UACA,UAIJ,iBACE,KACE,UACA,UAIJ,4BACE,gBACA,mBACA,cAGF,0BACE,4BACA,oBAEA,iCACE,cACA,eAKF,yBACE,cACA,gBACA,oBACA,mCACA,2BACA,sCACA,iCACA,eACA,SACA,WACA,2BACA,4BACA,oBAcN,kCAGM,yCLhjCJ,YKijCqB,MLhjCrB,aKgjCqB,MAEf,gBACA,eAKN,QACE,WACA,aAIJ,kCACE,iCACE,eAOF,yBL9jCA,aKgkCiB,EL/jCjB,cK+jCiB,GAKnB,kCAWE,OAJI,WALM,mBAYR,OJxuCkB,KIyuClB,iBAIA,2BACE,wBAGF,gCACE,4BAGF,+BACE,kBAIJ,SAzBI,WALM,mBAiCR,6BACA,qCAGF,cAhCI,WALM,mBAyCV,iCAEE,eAGF,uBACE,WAGF,mBAEE,aAGF,gBApDI,2CAuDF,OAGF,oBAEE,aAGF,+CAGE,cAGF,gCACE,iBAGF,MACE,kCAGF,iBACE,aAEA,0BACE,mBAMN,kCAEE,KACE,kBAGF,cACE,YJv0CY,MI20CZ,0BACE,gBAIJ,cACE,aAGF,OACE,UJ70Ce,MIg1CjB,uBACE,UJ90CqB,OI+0CrB,iCAIA,QACE,gBAIJ,iCACE,cAIF,aACE,SACA,gBAGF,cACE,iBAKJ,yDACE,oCACE,aACA,eAKJ,yDACE,oBACE,gBAGF,YACE,UACA,gBACA,uBACA,oBACA,oBAKJ,mCACE,eACE,aAGF,iCACE,mCAMJ,mCACE,OACE,kBAGF,cACE,+BAGF,wBACE,UAEA,uCACE,oBAGF,wCACE,mBAGF,kDACE,kBACA,YAIJ,SACE,mBAIJ,mCACE,aACE,+CAIJ,mCAGE,cACE,YJ97CkB,MIi8CpB,gBACE,KJl8CkB,MIq8CpB,OACE,oCAKF,yBACE,UJl8CqB,OIm8CrB,gCACA,iCAGF,0BAEE,gCAGF,aACE,8CAKF,SACE,MJ79CkB,MI+9ClB,0BACE,kBACA,qBACA,oBAIA,wBLx0CJ,aKy0CqB,QLx0CrB,cKw0CqB,QAInB,yBACE,qBACA,sBAEA,4CACE,aJ9+CQ,KIi/CV,sCL/1CJ,YKg2CqB,qBL/1CrB,aK+1CqB,sBGv/CvB,WACE,gBAGE,+BACE,qBAGF,0CACE,sBAIJ,iBACE,SACA,gBAEA,gEACE,kCAWF,4BACE,YACA,aAEA,wCAGE,sCACA,kBAGF,sFACE,yCAMA,iDAGE,gBACA,SAQA,2DACE,mBAIJ,0CAGE,cAGF,uDACE,cACA,mBACA,gBACA,uBAOV,YACE,wBACA,4BACA,6BAEA,oBACE,qBAIA,kCACE,sCACA,gBACA,oBACA,wBACA,yBACA,oBACA,SACA,yBAIA,yCACE,kDAMA,qDACE,mDAKN,gCACE,mBAEA,2CACE,4BAOR,kCACE,gEACE,kCAKE,4BACE,wCAEA,uCACE,2BAKE,2DACE,qBAUd,kCAGM,0DACE,cAOR,kCACE,WACE,kBAGF,YACE,iBACA,uBAGE,wCACE,mBAIJ,wBACE,cCrLN,qDACE,UACA,kBACA,qCAUA,gCANA,YACA,aAFc,OAGd,cAH4B,OAiB1B,mBACE,cAKN,mBAGE,gBACA,kDAEA,oCACE,mBAmBF,kCACE,sBACA,yBACA,sBACA,qBACA,iBAEA,kGACE,mBAGF,+CACE,aAEA,iDACE,6BAKF,iDAGE,kBASF,sDACE,UACA,YACA,oBAQR,gBAEE,wCACA,iDACA,+DACA,6DACA,6DACA,qDACA,wCAEA,eAGF,WACE,iBAeE,kCACE,wBAIA,mDACE,cAIJ,+BAGE,oBACA,mBACA,gBACA,WAGF,yDACE,gBAGF,8BACE,8BACA,iBACA,yBACA,yBAGF,kCACE,kCACA,UAGF,iCACE,kCACA,WAIJ,mBACE,iBACA,mBACA,iBACA,mBAIJ,qBAEI,oDAEE,iCAKN,2BACE,KACE,UACA,kBACA,SAGF,GACE,UACA,kBACA,OAIJ,mBACE,KACE,UACA,kBACA,SAGF,GACE,UACA,kBACA,OAIJ,aACE,4CACA,wBACA,gBACA,SACA,+BACA,8BACA,sBAEA,gBACE,gBACA,iBACA,iBACA,eAGE,oCACE,eAGF,qBACE,8BAMJ,0BACE,cACA,mBACA,gBACA,uBAEA,gCACE,2BACA,qBAGF,kCACE,aAIJ,gCACE,sCACA,gBAEA,wCACE,qBACA,UACA,UACA,eACA,iDAIJ,mBACE,oBAQJ,kBTxGA,MADwD,mBAExD,USwGiB,OTvGjB,YSuGyB,IAGzB,oBAIE,8BAGF,iBACE,gBACA,oBACA,gBACA,uBACA,oBACA,qBACA,4BAWJ,eACE,kBAGF,YACE,eAGF,yDACE,8CAGF,aTjJE,MSkJ6B,QTjJ7B,USiJe,QThJf,YSgJwB,IAExB,oBACE,YAIJ,kCACE,kBACE,kCAEA,kCACE,WACA,iBAKN,kCACE,eACE,6BAKJ,kCACE,iBACE,eACA,gBACA,oBACA,sBC9VJ,KACE,mBACA,oBACA,mBACA,iBACA,iBACA,8CACA,uCAEA,UACE,iBACA,eACA,8BCZJ,UACE,sBAIA,oFACE,WACA,MAJe,IAKf,kBACA,WACA,uCAGF,gBACE,cACA,iBACA,kBACA,SACA,iBAEA,wBAGE,YACA,UACA,YAGF,oCAGE,YACA,SAIF,uBACE,WACA,qBACA,kBACA,kBACA,WACA,YACA,YACA,iBACA,gDACA,qCACA,6BACA,UAKF,gBACE,iBACA,iBACA,mBACA,gBACA,uBAEA,+BACE,yCACA,uFAUF,wBAGE,MACA,UACA,cAIJ,8CACE,cAIJ,gBACE,mBACA,qBACA,kBACA,YAEA,sBACE,aACA,kBAGF,oBACE,cACA,4BAIJ,YAEE,mBACA,kBACA,UAEA,kBACE,mBAGF,oBAEE,WACA,qBACA,kBACA,kBACA,UACA,WACA,WACA,YACA,UACA,yCACA,6BACA,UAKN,kCACE,UACE,iBAEA,aACE,kBCxIN,cACE,WAGF,YACE,mBACA,sCAOA,yBAGE,eACA,cAHS,oBAIT,gBAEA,4CACE,4BACA,6BAIJ,cAGE,cAGF,6BACE,iBACA,kBACA,kBAEA,yCACE,yBACA,0BAGF,wCACE,gBAKN,kBACE,aACA,cACA,kBACA,kBACA,yBAEA,oBACE,kBACA,aACA,WACA,gCAIA,0BACE,yCAMN,qBACE,wBACE,6CAIJ,QACE,yBC7EF,MACE,2BACA,2CAKA,qCACE,mBACA,gBAGA,qDACE,gBACA,UACA,WACA,kBACA,cACA,WACA,kBACA,UACA,mBAIF,yCAGE,iBAMN,eACE,iBAGF,oBACE,kBAMA,iEAGE,mBAIJ,kCAIM,qDACE,eAGF,yCACE,mBACA,gBACA","sourcesContent":["/*\n* Mainly scss modules, only imported to `assets/css/main.scss`\n*/\n\n/* ---------- scss placeholder --------- */\n\n%heading {\n color: var(--heading-color);\n font-weight: 400;\n font-family: $font-family-heading;\n}\n\n%section {\n main & {\n margin-top: 2.5rem;\n margin-bottom: 1.25rem;\n\n &:focus {\n outline: none; /* avoid outline in Safari */\n }\n }\n}\n\n%anchor {\n .anchor {\n font-size: 80%;\n }\n\n @media (hover: hover) {\n .anchor {\n visibility: hidden;\n opacity: 0;\n transition: opacity 0.25s ease-in, visibility 0s ease-in 0.25s;\n }\n\n &:hover {\n .anchor {\n visibility: visible;\n opacity: 1;\n transition: opacity 0.25s ease-in, visibility 0s ease-in 0s;\n }\n }\n }\n}\n\n%tag-hover {\n background: var(--tag-hover);\n transition: background 0.35s ease-in-out;\n}\n\n%table-cell {\n padding: 0.4rem 1rem;\n font-size: 95%;\n white-space: nowrap;\n}\n\n%link-hover {\n color: #d2603a !important;\n border-bottom: 1px solid #d2603a;\n text-decoration: none;\n}\n\n%link-color {\n color: var(--link-color);\n}\n\n%link-underline {\n border-bottom: 1px solid var(--link-underline-color);\n}\n\n%clickable-transition {\n transition: all 0.3s ease-in-out;\n}\n\n%no-cursor {\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n%no-bottom-border {\n border-bottom: none;\n}\n\n%cursor-pointer {\n cursor: pointer;\n}\n\n%normal-font-style {\n font-style: normal;\n}\n\n%rounded {\n border-radius: $base-radius;\n}\n\n%img-caption {\n + em {\n display: block;\n text-align: center;\n font-style: normal;\n font-size: 80%;\n padding: 0;\n color: #6d6c6c;\n }\n}\n\n%sidebar-links {\n color: var(--sidebar-muted-color);\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n%text-clip {\n display: -webkit-box;\n overflow: hidden;\n text-overflow: ellipsis;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n%text-highlight {\n color: var(--text-muted-hightlight-color);\n font-weight: 600;\n}\n\n%text-sm {\n font-size: 0.85rem;\n}\n\n%text-xs {\n font-size: 0.8rem;\n}\n\n%sup-fn-target {\n &:target {\n background-color: var(--footnote-target-bg);\n width: -moz-fit-content;\n width: -webkit-fit-content;\n width: fit-content;\n transition: background-color 1.75s ease-in-out;\n }\n}\n\n/* ---------- scss mixin --------- */\n\n@mixin mt-mb($value) {\n margin-top: $value;\n margin-bottom: $value;\n}\n\n@mixin ml-mr($value) {\n margin-left: $value;\n margin-right: $value;\n}\n\n@mixin pt-pb($val) {\n padding-top: $val;\n padding-bottom: $val;\n}\n\n@mixin pl-pr($val) {\n padding-left: $val;\n padding-right: $val;\n}\n\n@mixin placeholder {\n color: var(--text-muted-color) !important;\n}\n\n@mixin placeholder-focus {\n opacity: 0.6;\n}\n\n@mixin label($font-size: 1rem, $font-weight: 600, $color: var(--label-color)) {\n color: $color;\n font-size: $font-size;\n font-weight: $font-weight;\n}\n\n@mixin align-center {\n position: relative;\n left: 50%;\n transform: translateX(-50%);\n}\n\n@mixin prompt($type, $fa-content, $fa-style: 'solid') {\n &.prompt-#{$type} {\n background-color: var(--prompt-#{$type}-bg);\n\n &::before {\n content: $fa-content;\n color: var(--prompt-#{$type}-icon-color);\n font: var(--fa-font-#{$fa-style});\n }\n }\n}\n","/*\n * The SCSS variables\n */\n\n/* sidebar */\n\n$sidebar-width: 260px !default; /* the basic width */\n$sidebar-width-large: 300px !default; /* screen width: >= 1650px */\n$sb-btn-gap: 0.8rem !default;\n$sb-btn-gap-lg: 1rem !default;\n\n/* other framework sizes */\n\n$topbar-height: 3rem !default;\n$search-max-width: 200px !default;\n$footer-height: 5rem !default;\n$footer-height-large: 6rem !default; /* screen width: < 850px */\n$main-content-max-width: 1250px !default;\n$base-radius: 0.625rem !default;\n$back2top-size: 2.75rem !default;\n\n/* syntax highlight */\n\n$code-font-size: 0.85rem !default;\n$code-header-height: 2.25rem !default;\n$code-dot-size: 0.75rem !default;\n$code-dot-gap: 0.5rem !default;\n$code-icon-width: 1.75rem !default;\n\n/* fonts */\n\n$font-family-base: 'Source Sans Pro', 'Microsoft Yahei', sans-serif !default;\n$font-family-heading: Lato, 'Microsoft Yahei', sans-serif !default;\n","/*\n* The syntax highlight.\n*/\n\n@import 'colors/syntax-light';\n@import 'colors/syntax-dark';\n\nhtml {\n @media (prefers-color-scheme: light) {\n &:not([data-mode]),\n &[data-mode='light'] {\n @include light-syntax;\n }\n\n &[data-mode='dark'] {\n @include dark-syntax;\n }\n }\n\n @media (prefers-color-scheme: dark) {\n &:not([data-mode]),\n &[data-mode='dark'] {\n @include dark-syntax;\n }\n\n &[data-mode='light'] {\n @include light-syntax;\n }\n }\n}\n\n/* -- code snippets -- */\n\n%code-snippet-bg {\n background-color: var(--highlight-bg-color);\n}\n\n%code-snippet-padding {\n padding-left: 1rem;\n padding-right: 1.5rem;\n}\n\n.highlighter-rouge {\n color: var(--highlighter-rouge-color);\n margin-top: 0.5rem;\n margin-bottom: 1.2em; /* Override BS Inline-code style */\n}\n\n.highlight {\n @extend %rounded;\n @extend %code-snippet-bg;\n\n @at-root figure#{&} {\n @extend %code-snippet-bg;\n }\n\n overflow: auto;\n padding-bottom: 0.75rem;\n\n pre {\n margin-bottom: 0;\n font-size: $code-font-size;\n line-height: 1.4rem;\n word-wrap: normal; /* Fixed Safari overflow-x */\n }\n\n table {\n td {\n &:first-child {\n display: inline-block;\n margin-left: 1rem;\n margin-right: 0.75rem;\n }\n\n &:last-child {\n padding-right: 2rem !important;\n }\n\n pre {\n overflow: visible; /* Fixed iOS safari overflow-x */\n word-break: normal; /* Fixed iOS safari linenos code break */\n }\n }\n }\n\n .lineno {\n text-align: right;\n color: var(--highlight-lineno-color);\n -webkit-user-select: none;\n -moz-user-select: none;\n -o-user-select: none;\n -ms-user-select: none;\n user-select: none;\n }\n} /* .highlight */\n\ncode {\n -webkit-hyphens: none;\n -ms-hyphens: none;\n hyphens: none;\n color: var(--code-color);\n\n &.highlighter-rouge {\n font-size: $code-font-size;\n padding: 3px 5px;\n word-break: break-word;\n border-radius: 4px;\n background-color: var(--inline-code-bg);\n }\n\n &.filepath {\n background-color: inherit;\n color: var(--filepath-text-color);\n font-weight: 600;\n padding: 0;\n }\n\n a > &.highlighter-rouge {\n padding-bottom: 0; /* show link's underlinke */\n color: inherit;\n }\n\n a:hover > &.highlighter-rouge {\n border-bottom: none;\n }\n\n blockquote & {\n color: inherit;\n }\n}\n\ntd.rouge-code {\n @extend %code-snippet-padding;\n\n /*\n Prevent some browser extends from\n changing the URL string of code block.\n */\n a {\n color: inherit !important;\n border-bottom: none !important;\n pointer-events: none;\n }\n}\n\ndiv[class^='language-'] {\n @extend %rounded;\n @extend %code-snippet-bg;\n\n box-shadow: var(--language-border-color) 0 0 0 1px;\n\n .content > & {\n @include ml-mr(-1rem);\n\n border-radius: 0;\n }\n\n .highlight {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n }\n}\n\n/* Hide line numbers for default, console, and terminal code snippets */\ndiv {\n &.nolineno,\n &.language-plaintext,\n &.language-console,\n &.language-terminal {\n td:first-child {\n padding: 0 !important;\n margin-right: 0;\n\n .lineno {\n display: none;\n }\n }\n }\n}\n\n.code-header {\n @extend %no-cursor;\n\n display: flex;\n justify-content: space-between;\n align-items: center;\n height: $code-header-height;\n margin-left: 0.75rem;\n margin-right: 0.25rem;\n\n /* the label block */\n span {\n line-height: $code-header-height;\n\n /* label icon */\n i {\n font-size: 1rem;\n width: $code-icon-width;\n color: var(--code-header-icon-color);\n\n &.small {\n font-size: 70%;\n }\n }\n\n @at-root [file] #{&} > i {\n position: relative;\n top: 1px; /* center the file icon */\n }\n\n /* label text */\n &::after {\n content: attr(data-label-text);\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--code-header-text-color);\n }\n }\n\n /* clipboard */\n button {\n @extend %cursor-pointer;\n @extend %rounded;\n\n border: 1px solid transparent;\n height: $code-header-height;\n width: $code-header-height;\n padding: 0;\n background-color: inherit;\n\n i {\n color: var(--code-header-icon-color);\n }\n\n &[timeout] {\n &:hover {\n border-color: var(--clipboard-checked-color);\n }\n\n i {\n color: var(--clipboard-checked-color);\n }\n }\n\n &:focus {\n outline: none;\n }\n\n &:not([timeout]):hover {\n background-color: rgba(128, 128, 128, 0.37);\n\n i {\n color: white;\n }\n }\n }\n}\n\n@media all and (min-width: 576px) {\n div[class^='language-'] {\n .content > & {\n @include ml-mr(0);\n\n border-radius: $base-radius;\n }\n\n .code-header {\n @include ml-mr(0);\n\n $dot-margin: 1rem;\n\n &::before {\n content: '';\n display: inline-block;\n margin-left: $dot-margin;\n width: $code-dot-size;\n height: $code-dot-size;\n border-radius: 50%;\n background-color: var(--code-header-muted-color);\n box-shadow: ($code-dot-size + $code-dot-gap) 0 0\n var(--code-header-muted-color),\n ($code-dot-size + $code-dot-gap) * 2 0 0\n var(--code-header-muted-color);\n }\n\n span {\n // center the text of label\n margin-left: calc(($dot-margin + $code-dot-size) / 2 * -1);\n }\n }\n }\n}\n","/*\n * The syntax light mode code snippet colors.\n */\n\n@mixin light-syntax {\n /* --- custom light colors --- */\n --language-border-color: #ececec;\n --highlight-bg-color: #f6f8fa;\n --highlighter-rouge-color: #3f596f;\n --highlight-lineno-color: #9e9e9e;\n --inline-code-bg: #f6f6f7;\n --code-color: #3a3a3a;\n --code-header-text-color: #a3a3a3;\n --code-header-muted-color: #e5e5e5;\n --code-header-icon-color: #c9c8c8;\n --clipboard-checked-color: #43c743;\n\n [class^='prompt-'] {\n --inline-code-bg: #fbfafa;\n }\n\n /* --- Syntax highlight theme from `rougify style github` --- */\n\n .highlight table td {\n padding: 5px;\n }\n\n .highlight table pre {\n margin: 0;\n }\n\n .highlight,\n .highlight .w {\n color: #24292f;\n background-color: #f6f8fa;\n }\n\n .highlight .k,\n .highlight .kd,\n .highlight .kn,\n .highlight .kp,\n .highlight .kr,\n .highlight .kt,\n .highlight .kv {\n color: #cf222e;\n }\n\n .highlight .gr {\n color: #f6f8fa;\n }\n\n .highlight .gd {\n color: #82071e;\n background-color: #ffebe9;\n }\n\n .highlight .nb {\n color: #953800;\n }\n\n .highlight .nc {\n color: #953800;\n }\n\n .highlight .no {\n color: #953800;\n }\n\n .highlight .nn {\n color: #953800;\n }\n\n .highlight .sr {\n color: #116329;\n }\n\n .highlight .na {\n color: #116329;\n }\n\n .highlight .nt {\n color: #116329;\n }\n\n .highlight .gi {\n color: #116329;\n background-color: #dafbe1;\n }\n\n .highlight .kc {\n color: #0550ae;\n }\n\n .highlight .l,\n .highlight .ld,\n .highlight .m,\n .highlight .mb,\n .highlight .mf,\n .highlight .mh,\n .highlight .mi,\n .highlight .il,\n .highlight .mo,\n .highlight .mx {\n color: #0550ae;\n }\n\n .highlight .sb {\n color: #0550ae;\n }\n\n .highlight .bp {\n color: #0550ae;\n }\n\n .highlight .ne {\n color: #0550ae;\n }\n\n .highlight .nl {\n color: #0550ae;\n }\n\n .highlight .py {\n color: #0550ae;\n }\n\n .highlight .nv,\n .highlight .vc,\n .highlight .vg,\n .highlight .vi,\n .highlight .vm {\n color: #0550ae;\n }\n\n .highlight .o,\n .highlight .ow {\n color: #0550ae;\n }\n\n .highlight .gh {\n color: #0550ae;\n font-weight: bold;\n }\n\n .highlight .gu {\n color: #0550ae;\n font-weight: bold;\n }\n\n .highlight .s,\n .highlight .sa,\n .highlight .sc,\n .highlight .dl,\n .highlight .sd,\n .highlight .s2,\n .highlight .se,\n .highlight .sh,\n .highlight .sx,\n .highlight .s1,\n .highlight .ss {\n color: #0a3069;\n }\n\n .highlight .nd {\n color: #8250df;\n }\n\n .highlight .nf,\n .highlight .fm {\n color: #8250df;\n }\n\n .highlight .err {\n color: #f6f8fa;\n background-color: #82071e;\n }\n\n .highlight .c,\n .highlight .ch,\n .highlight .cd,\n .highlight .cm,\n .highlight .cp,\n .highlight .cpf,\n .highlight .c1,\n .highlight .cs {\n color: #68717a;\n }\n\n .highlight .gl {\n color: #68717a;\n }\n\n .highlight .gt {\n color: #68717a;\n }\n\n .highlight .ni {\n color: #24292f;\n }\n\n .highlight .si {\n color: #24292f;\n }\n\n .highlight .ge {\n color: #24292f;\n font-style: italic;\n }\n\n .highlight .gs {\n color: #24292f;\n font-weight: bold;\n }\n} /* light-syntax */\n","/*\n * The syntax dark mode styles.\n */\n\n@mixin dark-syntax {\n --language-border-color: #2d2d2d;\n --highlight-bg-color: #151515;\n --highlighter-rouge-color: #c9def1;\n --highlight-lineno-color: #808080;\n --inline-code-bg: #323238;\n --code-color: #b0b0b0;\n --code-header-text-color: #6a6a6a;\n --code-header-muted-color: #353535;\n --code-header-icon-color: #565656;\n --clipboard-checked-color: #2bcc2b;\n --filepath-text-color: #cacaca;\n\n .highlight .gp {\n color: #87939d;\n }\n\n /* --- Syntax highlight theme from `rougify style base16.dark` --- */\n\n .highlight table td {\n padding: 5px;\n }\n\n .highlight table pre {\n margin: 0;\n }\n\n .highlight,\n .highlight .w {\n color: #d0d0d0;\n background-color: #151515;\n }\n\n .highlight .err {\n color: #151515;\n background-color: #ac4142;\n }\n\n .highlight .c,\n .highlight .ch,\n .highlight .cd,\n .highlight .cm,\n .highlight .cpf,\n .highlight .c1,\n .highlight .cs {\n color: #848484;\n }\n\n .highlight .cp {\n color: #f4bf75;\n }\n\n .highlight .nt {\n color: #f4bf75;\n }\n\n .highlight .o,\n .highlight .ow {\n color: #d0d0d0;\n }\n\n .highlight .p,\n .highlight .pi {\n color: #d0d0d0;\n }\n\n .highlight .gi {\n color: #90a959;\n }\n\n .highlight .gd {\n color: #f08a8b;\n background-color: #320000;\n }\n\n .highlight .gh {\n color: #6a9fb5;\n background-color: #151515;\n font-weight: bold;\n }\n\n .highlight .k,\n .highlight .kn,\n .highlight .kp,\n .highlight .kr,\n .highlight .kv {\n color: #aa759f;\n }\n\n .highlight .kc {\n color: #d28445;\n }\n\n .highlight .kt {\n color: #d28445;\n }\n\n .highlight .kd {\n color: #d28445;\n }\n\n .highlight .s,\n .highlight .sb,\n .highlight .sc,\n .highlight .dl,\n .highlight .sd,\n .highlight .s2,\n .highlight .sh,\n .highlight .sx,\n .highlight .s1 {\n color: #90a959;\n }\n\n .highlight .sa {\n color: #aa759f;\n }\n\n .highlight .sr {\n color: #75b5aa;\n }\n\n .highlight .si {\n color: #b76d45;\n }\n\n .highlight .se {\n color: #b76d45;\n }\n\n .highlight .nn {\n color: #f4bf75;\n }\n\n .highlight .nc {\n color: #f4bf75;\n }\n\n .highlight .no {\n color: #f4bf75;\n }\n\n .highlight .na {\n color: #6a9fb5;\n }\n\n .highlight .m,\n .highlight .mb,\n .highlight .mf,\n .highlight .mh,\n .highlight .mi,\n .highlight .il,\n .highlight .mo,\n .highlight .mx {\n color: #90a959;\n }\n\n .highlight .ss {\n color: #90a959;\n }\n}\n","/* The common styles */\n\nhtml {\n @media (prefers-color-scheme: light) {\n &:not([data-mode]),\n &[data-mode='light'] {\n @include light-scheme;\n }\n\n &[data-mode='dark'] {\n @include dark-scheme;\n }\n }\n\n @media (prefers-color-scheme: dark) {\n &:not([data-mode]),\n &[data-mode='dark'] {\n @include dark-scheme;\n }\n\n &[data-mode='light'] {\n @include light-scheme;\n }\n }\n\n font-size: 16px;\n}\n\nbody {\n background: var(--main-bg);\n padding: env(safe-area-inset-top) env(safe-area-inset-right)\n env(safe-area-inset-bottom) env(safe-area-inset-left);\n color: var(--text-color);\n -webkit-font-smoothing: antialiased;\n font-family: $font-family-base;\n}\n\n/* --- Typography --- */\n\n@for $i from 1 through 5 {\n h#{$i} {\n @extend %heading;\n\n @if $i > 1 {\n @extend %section;\n @extend %anchor;\n }\n\n @if $i < 5 {\n $factor: 0.18rem;\n\n @if $i == 1 {\n $factor: 0.23rem;\n }\n\n font-size: 1rem + (5 - $i) * $factor;\n } @else {\n font-size: 1rem;\n }\n }\n}\n\na {\n @extend %link-color;\n\n text-decoration: none;\n}\n\nimg {\n max-width: 100%;\n height: auto;\n transition: all 0.35s ease-in-out;\n\n .blur & {\n $blur: 20px;\n\n -webkit-filter: blur($blur);\n filter: blur($blur);\n }\n}\n\nblockquote {\n border-left: 5px solid var(--blockquote-border-color);\n padding-left: 1rem;\n color: var(--blockquote-text-color);\n\n > p:last-child {\n margin-bottom: 0;\n }\n\n &[class^='prompt-'] {\n border-left: 0;\n position: relative;\n padding: 1rem 1rem 1rem 3rem;\n color: var(--prompt-text-color);\n\n @extend %rounded;\n\n &::before {\n text-align: center;\n width: 3rem;\n position: absolute;\n left: 0.25rem;\n margin-top: 0.4rem;\n text-rendering: auto;\n -webkit-font-smoothing: antialiased;\n }\n }\n\n @include prompt('tip', '\\f0eb', 'regular');\n @include prompt('info', '\\f06a');\n @include prompt('warning', '\\f06a');\n @include prompt('danger', '\\f071');\n}\n\nkbd {\n font-family: inherit;\n display: inline-block;\n vertical-align: middle;\n line-height: 1.3rem;\n min-width: 1.75rem;\n text-align: center;\n margin: 0 0.3rem;\n padding-top: 0.1rem;\n color: var(--kbd-text-color);\n background-color: var(--kbd-bg-color);\n border-radius: 0.25rem;\n border: solid 1px var(--kbd-wrap-color);\n box-shadow: inset 0 -2px 0 var(--kbd-wrap-color);\n}\n\nfooter {\n background-color: var(--main-bg);\n height: $footer-height;\n border-top: 1px solid var(--main-border-color);\n\n @extend %text-xs;\n\n a {\n @extend %text-highlight;\n\n &:hover {\n @extend %link-hover;\n }\n }\n\n p {\n text-align: center;\n margin-bottom: 0;\n }\n}\n\n/* fontawesome icons */\ni {\n &.far,\n &.fas {\n @extend %no-cursor;\n }\n}\n\n/* --- Panels --- */\n\n.access {\n top: 2rem;\n transition: top 0.2s ease-in-out;\n margin-top: 3rem;\n margin-bottom: 4rem;\n\n &:only-child {\n position: -webkit-sticky;\n position: sticky;\n }\n\n > section {\n padding-left: 1rem;\n border-left: 1px solid var(--main-border-color);\n\n &:not(:last-child) {\n margin-bottom: 4rem;\n }\n }\n\n .content {\n font-size: 0.9rem;\n }\n}\n\n#panel-wrapper {\n /* the headings */\n .panel-heading {\n font-family: inherit;\n line-height: inherit;\n\n @include label(inherit);\n }\n\n .post-tag {\n line-height: 1.05rem;\n font-size: 0.85rem;\n border-radius: 0.8rem;\n padding: 0.3rem 0.5rem;\n margin: 0 0.35rem 0.5rem 0;\n\n &:hover {\n transition: all 0.3s ease-in;\n }\n }\n}\n\n#access-lastmod {\n a {\n &:hover {\n @extend %link-hover;\n }\n\n @extend %no-bottom-border;\n\n color: inherit;\n }\n}\n\n.footnotes > ol {\n padding-left: 2rem;\n margin-top: 0.5rem;\n\n > li {\n &:not(:last-child) {\n margin-bottom: 0.3rem;\n }\n\n @extend %sup-fn-target;\n\n > p {\n margin-left: 0.25em;\n margin-top: 0;\n margin-bottom: 0;\n }\n }\n}\n\n.footnote {\n @at-root a#{&} {\n @include ml-mr(1px);\n @include pl-pr(2px);\n\n border-bottom-style: none !important;\n }\n}\n\nsup {\n @extend %sup-fn-target;\n}\n\n.reversefootnote {\n @at-root a#{&} {\n font-size: 0.6rem;\n line-height: 1;\n position: relative;\n bottom: 0.25em;\n margin-left: 0.25em;\n border-bottom-style: none !important;\n }\n}\n\n/* --- Begin of Markdown table style --- */\n\n/* it will be created by Liquid */\n.table-wrapper {\n overflow-x: auto;\n margin-bottom: 1.5rem;\n\n > table {\n min-width: 100%;\n overflow-x: auto;\n border-spacing: 0;\n\n thead {\n border-bottom: solid 2px rgba(210, 215, 217, 0.75);\n\n th {\n @extend %table-cell;\n }\n }\n\n tbody {\n tr {\n border-bottom: 1px solid var(--tb-border-color);\n\n &:nth-child(2n) {\n background-color: var(--tb-even-bg);\n }\n\n &:nth-child(2n + 1) {\n background-color: var(--tb-odd-bg);\n }\n\n td {\n @extend %table-cell;\n }\n }\n } /* tbody */\n } /* table */\n}\n\n/* --- post --- */\n\n.preview-img {\n aspect-ratio: 40 / 21;\n width: 100%;\n height: 100%;\n overflow: hidden;\n\n @extend %rounded;\n\n &:not(.no-bg) {\n background: var(--img-bg);\n }\n\n img {\n height: 100%;\n -o-object-fit: cover;\n object-fit: cover;\n\n @extend %rounded;\n\n @at-root #post-list & {\n width: 100%;\n }\n }\n}\n\n.post-preview {\n @extend %rounded;\n\n border: 0;\n background: var(--card-bg);\n box-shadow: var(--card-shadow);\n\n &::before {\n @extend %rounded;\n\n content: '';\n width: 100%;\n height: 100%;\n position: absolute;\n background-color: var(--card-hovor-bg);\n opacity: 0;\n transition: opacity 0.35s ease-in-out;\n }\n\n &:hover {\n &::before {\n opacity: 0.3;\n }\n }\n}\n\nmain {\n line-height: 1.75;\n\n h1 {\n margin-top: 2rem;\n margin-bottom: 1.5rem;\n }\n\n p {\n > a.popup {\n &:not(.normal):not(.left):not(.right) {\n @include align-center;\n }\n }\n }\n\n .categories,\n #tags,\n #archives {\n a:not(:hover) {\n @extend %no-bottom-border;\n }\n }\n}\n\n.post-meta {\n @extend %text-sm;\n\n a {\n &:not([class]):hover {\n @extend %link-hover;\n }\n }\n\n em {\n @extend %normal-font-style;\n }\n}\n\n.content {\n font-size: 1.08rem;\n margin-top: 2rem;\n overflow-wrap: break-word;\n\n a {\n &.popup {\n @extend %no-cursor;\n @extend %img-caption;\n @include mt-mb(0.5rem);\n\n cursor: zoom-in;\n }\n\n &:not(.img-link) {\n @extend %link-underline;\n\n &:hover {\n @extend %link-hover;\n }\n }\n }\n\n ol,\n ul {\n &:not([class]),\n &.task-list {\n -webkit-padding-start: 1.75rem;\n padding-inline-start: 1.75rem;\n\n li {\n margin: 0.25rem 0;\n padding-left: 0.25rem;\n }\n\n ol,\n ul {\n -webkit-padding-start: 1.25rem;\n padding-inline-start: 1.25rem;\n margin: 0.5rem 0;\n }\n }\n }\n\n ul.task-list {\n -webkit-padding-start: 1.25rem;\n padding-inline-start: 1.25rem;\n\n li {\n list-style-type: none;\n padding-left: 0;\n\n /* checkbox icon */\n > i {\n width: 2rem;\n margin-left: -1.25rem;\n color: var(--checkbox-color);\n\n &.checked {\n color: var(--checkbox-checked-color);\n }\n }\n\n ul {\n -webkit-padding-start: 1.75rem;\n padding-inline-start: 1.75rem;\n }\n }\n\n input[type='checkbox'] {\n margin: 0 0.5rem 0.2rem -1.3rem;\n vertical-align: middle;\n }\n } /* ul */\n\n dl > dd {\n margin-left: 1rem;\n }\n\n ::marker {\n color: var(--text-muted-color);\n }\n} /* .content */\n\n.tag:hover {\n @extend %tag-hover;\n}\n\n.post-tag {\n display: inline-block;\n min-width: 2rem;\n text-align: center;\n border-radius: 0.5rem;\n border: 1px solid var(--btn-border-color);\n padding: 0 0.4rem;\n color: var(--text-muted-color);\n line-height: 1.3rem;\n\n &:not(:last-child) {\n margin-right: 0.2rem;\n }\n}\n\n.rounded-10 {\n border-radius: 10px !important;\n}\n\n.img-link {\n color: transparent;\n display: inline-flex;\n}\n\n.shimmer {\n overflow: hidden;\n position: relative;\n background: var(--img-bg);\n\n &::before {\n content: '';\n position: absolute;\n background: var(--shimmer-bg);\n height: 100%;\n width: 100%;\n -webkit-animation: shimmer 1.3s infinite;\n animation: shimmer 1.3s infinite;\n }\n\n @-webkit-keyframes shimmer {\n 0% {\n transform: translateX(-100%);\n }\n\n 100% {\n transform: translateX(100%);\n }\n }\n\n @keyframes shimmer {\n 0% {\n transform: translateX(-100%);\n }\n\n 100% {\n transform: translateX(100%);\n }\n }\n}\n\n.embed-video {\n width: 100%;\n height: 100%;\n margin-bottom: 1rem;\n\n @extend %rounded;\n\n &.youtube,\n &.bilibili {\n aspect-ratio: 16 / 9;\n }\n\n &.twitch {\n aspect-ratio: 310 / 189;\n }\n}\n\n/* --- buttons --- */\n.btn-lang {\n border: 1px solid !important;\n padding: 1px 3px;\n border-radius: 3px;\n color: var(--link-color);\n\n &:focus {\n box-shadow: none;\n }\n}\n\n/* --- Effects classes --- */\n\n.loaded {\n display: block !important;\n\n @at-root .d-flex#{&} {\n display: flex !important;\n }\n}\n\n.unloaded {\n display: none !important;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.hidden {\n visibility: hidden !important;\n}\n\n.flex-grow-1 {\n flex-grow: 1 !important;\n}\n\n.btn-box-shadow {\n box-shadow: var(--card-shadow);\n}\n\n/* overwrite bootstrap muted */\n.text-muted {\n color: var(--text-muted-color) !important;\n}\n\n/* Overwrite bootstrap tooltip */\n.tooltip-inner {\n font-size: 0.7rem;\n max-width: 220px;\n text-align: left;\n}\n\n/* Overwrite bootstrap outline button */\n.btn.btn-outline-primary {\n &:not(.disabled):hover {\n border-color: #007bff !important;\n }\n}\n\n.disabled {\n color: rgb(206, 196, 196);\n pointer-events: auto;\n cursor: not-allowed;\n}\n\n.hide-border-bottom {\n border-bottom: none !important;\n}\n\n.input-focus {\n box-shadow: none;\n border-color: var(--input-focus-border-color) !important;\n background: center !important;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;\n}\n\n.left {\n float: left;\n margin: 0.75rem 1rem 1rem 0;\n}\n\n.right {\n float: right;\n margin: 0.75rem 0 1rem 1rem;\n}\n\n/* --- Overriding --- */\n\n/* magnific-popup */\n\nfigure .mfp-title {\n text-align: center;\n padding-right: 0;\n margin-top: 0.5rem;\n}\n\n.mfp-img {\n transition: none;\n}\n\n/* mermaid */\n.mermaid {\n text-align: center;\n}\n\n/* MathJax */\nmjx-container {\n overflow-y: hidden;\n min-width: auto !important;\n}\n\n/* --- sidebar layout --- */\n\n$sidebar-display: 'sidebar-display';\n$btn-border-width: 3px;\n$btn-mb: 0.5rem;\n\n#sidebar {\n @include pl-pr(0);\n\n position: fixed;\n top: 0;\n left: 0;\n height: 100%;\n overflow-y: auto;\n width: $sidebar-width;\n z-index: 99;\n background: var(--sidebar-bg);\n border-right: 1px solid var(--sidebar-border-color);\n\n /* Hide scrollbar for Chrome, Safari and Opera */\n &::-webkit-scrollbar {\n display: none;\n }\n\n /* Hide scrollbar for IE, Edge and Firefox */\n -ms-overflow-style: none; /* IE and Edge */\n scrollbar-width: none; /* Firefox */\n\n %sidebar-link-hover {\n &:hover {\n color: var(--sidebar-active-color);\n }\n }\n\n a {\n @extend %sidebar-links;\n }\n\n #avatar {\n display: block;\n width: 7rem;\n height: 7rem;\n overflow: hidden;\n box-shadow: var(--avatar-border-color) 0 0 0 2px;\n transform: translateZ(0); /* fixed the zoom in Safari */\n\n img {\n transition: transform 0.5s;\n\n &:hover {\n transform: scale(1.2);\n }\n }\n }\n\n .profile-wrapper {\n @include mt-mb(2.5rem);\n @extend %clickable-transition;\n\n padding-left: 2.5rem;\n padding-right: 1.25rem;\n width: 100%;\n }\n\n .site-title {\n font-family: inherit;\n font-weight: 900;\n font-size: 1.75rem;\n line-height: 1.2;\n letter-spacing: 0.25px;\n margin-top: 1.25rem;\n margin-bottom: 0.5rem;\n\n a {\n @extend %clickable-transition;\n @extend %sidebar-link-hover;\n\n color: var(--site-title-color);\n }\n }\n\n .site-subtitle {\n font-size: 95%;\n color: var(--site-subtitle-color);\n margin-top: 0.25rem;\n word-spacing: 1px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n }\n\n ul {\n margin-bottom: 2rem;\n\n li.nav-item {\n opacity: 0.9;\n width: 100%;\n padding-left: 1.5rem;\n padding-right: 1.5rem;\n\n a.nav-link {\n @include pt-pb(0.6rem);\n\n display: flex;\n align-items: center;\n border-radius: 0.75rem;\n font-weight: 600;\n\n &:hover {\n background-color: var(--sidebar-hover-bg);\n }\n\n i {\n font-size: 95%;\n opacity: 0.8;\n margin-right: 1.5rem;\n }\n\n span {\n font-size: 90%;\n letter-spacing: 0.2px;\n }\n }\n\n &.active {\n .nav-link {\n color: var(--sidebar-active-color);\n background-color: var(--sidebar-hover-bg);\n\n span {\n opacity: 1;\n }\n }\n }\n\n &:not(:first-child) {\n margin-top: 0.25rem;\n }\n }\n }\n\n .sidebar-bottom {\n padding-left: 2rem;\n padding-right: 1rem;\n margin-bottom: 1.5rem;\n\n $btn-size: 1.75rem;\n\n %button {\n width: $btn-size;\n height: $btn-size;\n margin-bottom: $btn-mb; // multi line gap\n border-radius: 50%;\n color: var(--sidebar-btn-color);\n background-color: var(--sidebar-btn-bg);\n text-align: center;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: var(--sidebar-border-color) 0 0 0 1px;\n\n &:hover {\n background-color: var(--sidebar-hover-bg);\n }\n }\n\n a {\n @extend %button;\n @extend %sidebar-link-hover;\n @extend %clickable-transition;\n\n &:not(:last-child) {\n margin-right: $sb-btn-gap;\n }\n }\n\n i {\n line-height: $btn-size;\n }\n\n .mode-toggle {\n padding: 0;\n border: 0;\n\n @extend %button;\n @extend %sidebar-links;\n @extend %sidebar-link-hover;\n }\n\n .icon-border {\n @extend %no-cursor;\n @include ml-mr(calc(($sb-btn-gap - $btn-border-width) / 2));\n\n background-color: var(--sidebar-btn-color);\n content: '';\n width: $btn-border-width;\n height: $btn-border-width;\n border-radius: 50%;\n margin-bottom: $btn-mb;\n }\n } /* .sidebar-bottom */\n} /* #sidebar */\n\n@media (hover: hover) {\n #sidebar ul > li:last-child::after {\n transition: top 0.5s ease;\n }\n\n .nav-link {\n transition: background-color 0.3s ease-in-out;\n }\n\n .post-preview {\n transition: background-color 0.35s ease-in-out;\n }\n}\n\n#search-result-wrapper {\n display: none;\n height: 100%;\n width: 100%;\n overflow: auto;\n\n .content {\n margin-top: 2rem;\n }\n}\n\n/* --- top-bar --- */\n\n#topbar-wrapper {\n height: $topbar-height;\n background-color: var(--topbar-bg);\n}\n\n#topbar {\n button i {\n color: #999999;\n }\n\n #breadcrumb {\n font-size: 1rem;\n color: var(--text-muted-color);\n padding-left: 0.5rem;\n\n a:hover {\n @extend %link-hover;\n }\n\n span {\n &:not(:last-child) {\n &::after {\n content: '›';\n padding: 0 0.3rem;\n }\n }\n }\n }\n} /* #topbar */\n\n::-webkit-input-placeholder {\n @include placeholder;\n}\n\n::-moz-placeholder {\n @include placeholder;\n}\n\n:-ms-input-placeholder {\n @include placeholder;\n}\n\n::-ms-input-placeholder {\n @include placeholder;\n}\n\n::placeholder {\n @include placeholder;\n}\n\n:focus::-webkit-input-placeholder {\n @include placeholder-focus;\n}\n\n:focus::-moz-placeholder {\n @include placeholder-focus;\n}\n\n:focus:-ms-input-placeholder {\n @include placeholder-focus;\n}\n\n:focus::-ms-input-placeholder {\n @include placeholder-focus;\n}\n\n:focus::placeholder {\n @include placeholder-focus;\n}\n\nsearch {\n display: flex;\n width: 100%;\n border-radius: 1rem;\n border: 1px solid var(--search-border-color);\n background: var(--main-bg);\n padding: 0 0.5rem;\n\n i {\n z-index: 2;\n font-size: 0.9rem;\n color: var(--search-icon-color);\n }\n}\n\n#sidebar-trigger,\n#search-trigger {\n display: none;\n}\n\n/* 'Cancel' link */\n#search-cancel {\n color: var(--link-color);\n display: none;\n white-space: nowrap;\n\n @extend %cursor-pointer;\n}\n\n#search-input {\n background: center;\n border: 0;\n border-radius: 0;\n padding: 0.18rem 0.3rem;\n color: var(--text-color);\n height: auto;\n\n &:focus {\n box-shadow: none;\n }\n}\n\n#search-hints {\n padding: 0 1rem;\n\n h4 {\n margin-bottom: 1.5rem;\n }\n\n .post-tag {\n display: inline-block;\n line-height: 1rem;\n font-size: 1rem;\n background: var(--search-tag-bg);\n border: none;\n padding: 0.5rem;\n margin: 0 1.25rem 1rem 0;\n\n &::before {\n content: '#';\n color: var(--text-muted-color);\n padding-right: 0.2rem;\n }\n\n @extend %link-color;\n }\n}\n\n#search-results {\n padding-bottom: 3rem;\n\n a {\n &:hover {\n @extend %link-hover;\n }\n\n @extend %link-color;\n @extend %no-bottom-border;\n @extend %heading;\n\n font-size: 1.4rem;\n line-height: 2.5rem;\n }\n\n > article {\n width: 100%;\n\n &:not(:last-child) {\n margin-bottom: 1rem;\n }\n\n /* icons */\n i {\n color: #818182;\n margin-right: 0.15rem;\n font-size: 80%;\n }\n\n > p {\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n }\n }\n} /* #search-results */\n\n#topbar-title {\n display: none;\n font-size: 1.1rem;\n font-weight: 600;\n font-family: sans-serif;\n color: var(--topbar-text-color);\n text-align: center;\n width: 70%;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: keep-all;\n white-space: nowrap;\n}\n\n#mask {\n display: none;\n position: fixed;\n inset: 0 0 0 0;\n height: 100%;\n width: 100%;\n z-index: 1;\n\n @at-root [#{$sidebar-display}] & {\n display: block !important;\n }\n}\n\n/* --- basic wrappers --- */\n\n#main-wrapper {\n position: relative;\n\n @include pl-pr(0);\n\n > .container {\n min-height: 100vh;\n }\n}\n\n#topbar-wrapper.row,\n#main-wrapper > .container > .row,\n#search-result-wrapper > .row {\n @include ml-mr(0);\n}\n\n#tail-wrapper {\n > :not(script) {\n margin-top: 3rem;\n }\n}\n\n/* --- button back-to-top --- */\n\n#back-to-top {\n display: none;\n z-index: 1;\n cursor: pointer;\n position: fixed;\n right: 1rem;\n bottom: calc($footer-height-large - $back2top-size / 2);\n background: var(--button-bg);\n color: var(--btn-backtotop-color);\n padding: 0;\n width: $back2top-size;\n height: $back2top-size;\n border-radius: 50%;\n border: 1px solid var(--btn-backtotop-border-color);\n transition: transform 0.2s ease-out;\n -webkit-transition: transform 0.2s ease-out;\n\n &:hover {\n transform: translate3d(0, -5px, 0);\n -webkit-transform: translate3d(0, -5px, 0);\n }\n\n i {\n line-height: $back2top-size;\n position: relative;\n bottom: 2px;\n }\n}\n\n#notification {\n @-webkit-keyframes popup {\n from {\n opacity: 0;\n bottom: 0;\n }\n }\n\n @keyframes popup {\n from {\n opacity: 0;\n bottom: 0;\n }\n }\n\n .toast-header {\n background: none;\n border-bottom: none;\n color: inherit;\n }\n\n .toast-body {\n font-family: Lato, sans-serif;\n line-height: 1.25rem;\n\n button {\n font-size: 90%;\n min-width: 4rem;\n }\n }\n\n &.toast {\n &.show {\n display: block;\n min-width: 20rem;\n border-radius: 0.5rem;\n -webkit-backdrop-filter: blur(10px);\n backdrop-filter: blur(10px);\n background-color: rgba(255, 255, 255, 0.5);\n color: #1b1b1eba;\n position: fixed;\n left: 50%;\n bottom: 20%;\n transform: translateX(-50%);\n -webkit-animation: popup 0.8s;\n animation: popup 0.8s;\n }\n }\n}\n\n/*\n Responsive Design:\n\n {sidebar, content, panel} >= 1200px screen width\n {sidebar, content} >= 850px screen width\n {content} <= 849px screen width\n\n*/\n\n@media all and (max-width: 576px) {\n main {\n .content {\n > blockquote[class^='prompt-'] {\n @include ml-mr(-1rem);\n\n border-radius: 0;\n max-width: none;\n }\n }\n }\n\n #avatar {\n width: 5rem;\n height: 5rem;\n }\n}\n\n@media all and (max-width: 768px) {\n %full-width {\n max-width: 100%;\n }\n\n #topbar {\n @extend %full-width;\n }\n\n #main-wrapper > .container {\n @extend %full-width;\n @include pl-pr(0);\n }\n}\n\n/* hide sidebar and panel */\n@media all and (max-width: 849px) {\n @mixin slide($append: null) {\n $basic: transform 0.4s ease;\n\n @if $append {\n transition: $basic, $append;\n } @else {\n transition: $basic;\n }\n }\n\n footer {\n @include slide;\n\n height: $footer-height-large;\n padding: 1.5rem 0;\n }\n\n [#{$sidebar-display}] {\n #sidebar {\n transform: translateX(0);\n }\n\n #main-wrapper {\n transform: translateX($sidebar-width);\n }\n\n #back-to-top {\n visibility: hidden;\n }\n }\n\n #sidebar {\n @include slide;\n\n transform: translateX(-$sidebar-width); /* hide */\n -webkit-transform: translateX(-$sidebar-width);\n }\n\n #main-wrapper {\n @include slide;\n }\n\n #topbar,\n #main-wrapper > .container {\n max-width: 100%;\n }\n\n #search-result-wrapper {\n width: 100%;\n }\n\n #breadcrumb,\n search {\n display: none;\n }\n\n #topbar-wrapper {\n @include slide(top 0.2s ease);\n\n left: 0;\n }\n\n main,\n #panel-wrapper {\n margin-top: 0;\n }\n\n #topbar-title,\n #sidebar-trigger,\n #search-trigger {\n display: block;\n }\n\n #search-result-wrapper .content {\n letter-spacing: 0;\n }\n\n #tags {\n justify-content: center !important;\n }\n\n h1.dynamic-title {\n display: none;\n\n ~ .content {\n margin-top: 2.5rem;\n }\n }\n} /* max-width: 849px */\n\n/* Sidebar is visible */\n@media all and (min-width: 850px) {\n /* Solved jumping scrollbar */\n html {\n overflow-y: scroll;\n }\n\n #main-wrapper {\n margin-left: $sidebar-width;\n }\n\n #sidebar {\n .profile-wrapper {\n margin-top: 3rem;\n }\n }\n\n #search-hints {\n display: none;\n }\n\n search {\n max-width: $search-max-width;\n }\n\n #search-result-wrapper {\n max-width: $main-content-max-width;\n justify-content: start !important;\n }\n\n main {\n h1 {\n margin-top: 3rem;\n }\n }\n\n div.content .table-wrapper > table {\n min-width: 70%;\n }\n\n /* button 'back-to-Top' position */\n #back-to-top {\n right: 5%;\n bottom: calc($footer-height - $back2top-size / 2);\n }\n\n #topbar-title {\n text-align: left;\n }\n}\n\n/* Pad horizontal */\n@media all and (min-width: 992px) and (max-width: 1199px) {\n #main-wrapper > .container .col-lg-11 {\n flex: 0 0 96%;\n max-width: 96%;\n }\n}\n\n/* Compact icons in sidebar & panel hidden */\n@media all and (min-width: 850px) and (max-width: 1199px) {\n #search-results > div {\n max-width: 700px;\n }\n\n #breadcrumb {\n width: 65%;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: keep-all;\n white-space: nowrap;\n }\n}\n\n/* panel hidden */\n@media all and (max-width: 1199px) {\n #panel-wrapper {\n display: none;\n }\n\n #main-wrapper > .container > div.row {\n justify-content: center !important;\n }\n}\n\n/* --- desktop mode, both sidebar and panel are visible --- */\n\n@media all and (min-width: 1200px) {\n search {\n margin-right: 4rem;\n }\n\n #search-input {\n transition: all 0.3s ease-in-out;\n }\n\n #search-results > article {\n width: 45%;\n\n &:nth-child(odd) {\n margin-right: 1.5rem;\n }\n\n &:nth-child(even) {\n margin-left: 1.5rem;\n }\n\n &:last-child:nth-child(odd) {\n position: relative;\n right: 24.3%;\n }\n }\n\n .content {\n font-size: 1.03rem;\n }\n}\n\n@media all and (min-width: 1400px) {\n #back-to-top {\n right: calc((100vw - $sidebar-width - 1140px) / 2 + 3rem);\n }\n}\n\n@media all and (min-width: 1650px) {\n $icon-gap: 1rem;\n\n #main-wrapper {\n margin-left: $sidebar-width-large;\n }\n\n #topbar-wrapper {\n left: $sidebar-width-large;\n }\n\n search {\n margin-right: calc(\n $main-content-max-width / 4 - $search-max-width - 0.75rem\n );\n }\n\n #main-wrapper > .container {\n max-width: $main-content-max-width;\n padding-left: 1.75rem !important;\n padding-right: 1.75rem !important;\n }\n\n main.col-12,\n #tail-wrapper {\n padding-right: 4.5rem !important;\n }\n\n #back-to-top {\n right: calc(\n (100vw - $sidebar-width-large - $main-content-max-width) / 2 + 2rem\n );\n }\n\n #sidebar {\n width: $sidebar-width-large;\n\n .profile-wrapper {\n margin-top: 3.5rem;\n margin-bottom: 2.5rem;\n padding-left: 3.5rem;\n }\n\n ul {\n li.nav-item {\n @include pl-pr(2.75rem);\n }\n }\n\n .sidebar-bottom {\n padding-left: 2.75rem;\n margin-bottom: 1.75rem;\n\n a:not(:last-child) {\n margin-right: $sb-btn-gap-lg;\n }\n\n .icon-border {\n @include ml-mr(calc(($sb-btn-gap-lg - $btn-border-width) / 2));\n }\n }\n }\n} /* min-width: 1650px */\n","/*\n * The syntax light mode typography colors\n */\n\n@mixin light-scheme {\n /* Framework color */\n --main-bg: white;\n --mask-bg: #c1c3c5;\n --main-border-color: #f3f3f3;\n\n /* Common color */\n --text-color: #34343c;\n --text-muted-color: #757575;\n --text-muted-hightlight-color: inherit;\n --heading-color: #2a2a2a;\n --label-color: #585858;\n --blockquote-border-color: #eeeeee;\n --blockquote-text-color: #757575;\n --link-color: #0056b2;\n --link-underline-color: #dee2e6;\n --button-bg: #ffffff;\n --btn-border-color: #e9ecef;\n --btn-backtotop-color: #686868;\n --btn-backtotop-border-color: #f1f1f1;\n --btn-box-shadow: #eaeaea;\n --checkbox-color: #c5c5c5;\n --checkbox-checked-color: #07a8f7;\n --img-bg: radial-gradient(\n circle,\n rgb(255, 255, 255) 0%,\n rgb(239, 239, 239) 100%\n );\n --shimmer-bg: linear-gradient(\n 90deg,\n rgba(250, 250, 250, 0) 0%,\n rgba(232, 230, 230, 1) 50%,\n rgba(250, 250, 250, 0) 100%\n );\n\n /* Sidebar */\n --site-title-color: rgb(113, 113, 113);\n --site-subtitle-color: #717171;\n --sidebar-bg: #f6f8fa;\n --sidebar-border-color: #efefef;\n --sidebar-muted-color: #545454;\n --sidebar-active-color: #1d1d1d;\n --sidebar-hover-bg: rgb(223, 233, 241, 0.64);\n --sidebar-btn-bg: white;\n --sidebar-btn-color: #8e8e8e;\n --avatar-border-color: white;\n\n /* Topbar */\n --topbar-bg: rgb(255, 255, 255, 0.7);\n --topbar-text-color: rgb(78, 78, 78);\n --search-border-color: rgb(240, 240, 240);\n --search-icon-color: #c2c6cc;\n --input-focus-border-color: #b8b8b8;\n\n /* Home page */\n --post-list-text-color: dimgray;\n --btn-patinator-text-color: #555555;\n --btn-paginator-hover-color: var(--sidebar-bg);\n\n /* Posts */\n --toc-highlight: #0550ae;\n --btn-share-color: gray;\n --btn-share-hover-color: #0d6efd;\n --card-bg: white;\n --card-hovor-bg: #e2e2e2;\n --card-shadow: rgb(104, 104, 104, 0.05) 0 2px 6px 0,\n rgba(211, 209, 209, 0.15) 0 0 0 1px;\n --footnote-target-bg: lightcyan;\n --tb-odd-bg: #fbfcfd;\n --tb-border-color: #eaeaea;\n --dash-color: silver;\n --kbd-wrap-color: #bdbdbd;\n --kbd-text-color: var(--text-color);\n --kbd-bg-color: white;\n --prompt-text-color: rgb(46, 46, 46, 0.77);\n --prompt-tip-bg: rgb(123, 247, 144, 0.2);\n --prompt-tip-icon-color: #03b303;\n --prompt-info-bg: #e1f5fe;\n --prompt-info-icon-color: #0070cb;\n --prompt-warning-bg: rgb(255, 243, 205);\n --prompt-warning-icon-color: #ef9c03;\n --prompt-danger-bg: rgb(248, 215, 218, 0.56);\n --prompt-danger-icon-color: #df3c30;\n\n /* Tags */\n --tag-border: #dee2e6;\n --tag-shadow: var(--btn-border-color);\n --tag-hover: rgb(222, 226, 230);\n --search-tag-bg: #f8f9fa;\n\n [class^='prompt-'] {\n --link-underline-color: rgb(219, 216, 216);\n }\n\n .dark {\n display: none;\n }\n\n /* Categories */\n --categories-border: rgba(0, 0, 0, 0.125);\n --categories-hover-bg: var(--btn-border-color);\n --categories-icon-hover-color: darkslategray;\n\n /* Archive */\n --timeline-color: rgba(0, 0, 0, 0.075);\n --timeline-node-bg: #c2c6cc;\n --timeline-year-dot-color: #ffffff;\n} /* light-scheme */\n","/*\n * The main dark mode styles\n */\n\n@mixin dark-scheme {\n /* Framework color */\n --main-bg: rgb(27, 27, 30);\n --mask-bg: rgb(68, 69, 70);\n --main-border-color: rgb(44, 45, 45);\n\n /* Common color */\n --text-color: rgb(175, 176, 177);\n --text-muted-color: #868686;\n --text-muted-hightlight-color: #aeaeae;\n --heading-color: #cccccc;\n --label-color: #a7a7a7;\n --blockquote-border-color: rgb(66, 66, 66);\n --blockquote-text-color: #868686;\n --link-color: rgb(138, 180, 248);\n --link-underline-color: rgb(82, 108, 150);\n --button-bg: #1e1e1e;\n --btn-border-color: #2e2f31;\n --btn-backtotop-color: var(--text-color);\n --btn-backtotop-border-color: #212122;\n --btn-box-shadow: var(--main-bg);\n --card-header-bg: #292929;\n --checkbox-color: rgb(118, 120, 121);\n --checkbox-checked-color: var(--link-color);\n --img-bg: radial-gradient(circle, rgb(22, 22, 24) 0%, rgb(32, 32, 32) 100%);\n --shimmer-bg: linear-gradient(\n 90deg,\n rgba(255, 255, 255, 0) 0%,\n rgba(58, 55, 55, 0.4) 50%,\n rgba(255, 255, 255, 0) 100%\n );\n\n /* Sidebar */\n --site-title-color: #717070;\n --site-subtitle-color: #868686;\n --sidebar-bg: #1e1e1e;\n --sidebar-border-color: #292929;\n --sidebar-muted-color: #868686;\n --sidebar-active-color: rgb(255, 255, 255, 0.95);\n --sidebar-hover-bg: #262626;\n --sidebar-btn-bg: #232328;\n --sidebar-btn-color: #787878;\n --avatar-border-color: rgb(206, 206, 206, 0.9);\n\n /* Topbar */\n --topbar-bg: rgb(27, 27, 30, 0.64);\n --topbar-text-color: var(--text-color);\n --search-border-color: rgb(55, 55, 55);\n --search-icon-color: rgb(100, 102, 105);\n --input-focus-border-color: rgb(112, 114, 115);\n\n /* Home page */\n --post-list-text-color: rgb(175, 176, 177);\n --btn-patinator-text-color: var(--text-color);\n --btn-paginator-hover-color: #2e2e2e;\n\n /* Posts */\n --toc-highlight: rgb(116, 178, 243);\n --tag-hover: rgb(43, 56, 62);\n --tb-odd-bg: #252526; /* odd rows of the posts' table */\n --tb-even-bg: rgb(31, 31, 34); /* even rows of the posts' table */\n --tb-border-color: var(--tb-odd-bg);\n --footnote-target-bg: rgb(63, 81, 181);\n --btn-share-color: #6c757d;\n --btn-share-hover-color: #bfc1ca;\n --card-bg: #1e1e1e;\n --card-hovor-bg: #464d51;\n --card-shadow: rgb(21, 21, 21, 0.72) 0 6px 18px 0,\n rgb(137, 135, 135, 0.24) 0 0 0 1px;\n --kbd-wrap-color: #6a6a6a;\n --kbd-text-color: #d3d3d3;\n --kbd-bg-color: #242424;\n --prompt-text-color: rgb(216, 212, 212, 0.75);\n --prompt-tip-bg: rgb(22, 60, 36, 0.64);\n --prompt-tip-icon-color: rgb(15, 164, 15, 0.81);\n --prompt-info-bg: rgb(7, 59, 104, 0.8);\n --prompt-info-icon-color: #0075d1;\n --prompt-warning-bg: rgb(90, 69, 3, 0.88);\n --prompt-warning-icon-color: rgb(255, 165, 0, 0.8);\n --prompt-danger-bg: rgb(86, 28, 8, 0.8);\n --prompt-danger-icon-color: #cd0202;\n\n /* tags */\n --tag-border: rgb(59, 79, 88);\n --tag-shadow: rgb(32, 33, 33);\n --dash-color: rgb(63, 65, 68);\n --search-tag-bg: #292828;\n\n /* categories */\n --categories-border: rgb(64, 66, 69, 0.5);\n --categories-hover-bg: rgb(73, 75, 76);\n --categories-icon-hover-color: white;\n\n /* archives */\n --timeline-node-bg: rgb(150, 152, 156);\n --timeline-color: rgb(63, 65, 68);\n --timeline-year-dot-color: var(--timeline-color);\n\n .light {\n display: none;\n }\n\n hr {\n border-color: var(--main-border-color);\n }\n\n /* categories */\n .categories.card,\n .list-group-item {\n background-color: var(--card-bg);\n }\n\n .categories {\n .card-header {\n background-color: var(--card-header-bg);\n }\n\n .list-group-item {\n border-left: none;\n border-right: none;\n padding-left: 2rem;\n border-color: var(--categories-border);\n\n &:last-child {\n border-bottom-color: var(--card-bg);\n }\n }\n }\n\n #archives li:nth-child(odd) {\n background-image: linear-gradient(\n to left,\n rgb(26, 26, 30),\n rgb(39, 39, 45),\n rgb(39, 39, 45),\n rgb(39, 39, 45),\n rgb(26, 26, 30)\n );\n }\n\n color-scheme: dark;\n\n /* stylelint-disable-next-line selector-id-pattern */\n #disqus_thread {\n color-scheme: none;\n }\n} /* dark-scheme */\n","/*\n Style for Homepage\n*/\n\n#post-list {\n margin-top: 2rem;\n\n .card-wrapper {\n &:hover {\n text-decoration: none;\n }\n\n &:not(:last-child) {\n margin-bottom: 1.25rem;\n }\n }\n\n .card {\n border: 0;\n background: none;\n\n %img-radius {\n border-radius: $base-radius $base-radius 0 0;\n }\n\n .preview-img {\n @extend %img-radius;\n\n img {\n @extend %img-radius;\n }\n }\n\n .card-body {\n height: 100%;\n padding: 1rem;\n\n .card-title {\n @extend %text-clip;\n\n color: var(--heading-color) !important;\n font-size: 1.25rem;\n }\n\n %muted {\n color: var(--text-muted-color) !important;\n }\n\n .card-text.content {\n @extend %muted;\n\n p {\n @extend %text-clip;\n\n line-height: 1.5;\n margin: 0;\n }\n }\n\n .post-meta {\n @extend %muted;\n\n i {\n &:not(:first-child) {\n margin-left: 1.5rem;\n }\n }\n\n em {\n @extend %normal-font-style;\n\n color: inherit;\n }\n\n > div:first-child {\n display: block;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n }\n }\n }\n} /* #post-list */\n\n.pagination {\n color: var(--text-color);\n font-family: Lato, sans-serif;\n justify-content: space-evenly;\n\n a:hover {\n text-decoration: none;\n }\n\n .page-item {\n .page-link {\n color: var(--btn-patinator-text-color);\n padding: 0 0.6rem;\n display: -webkit-box;\n -webkit-box-pack: center;\n -webkit-box-align: center;\n border-radius: 0.5rem;\n border: 0;\n background-color: inherit;\n }\n\n &.active {\n .page-link {\n background-color: var(--btn-paginator-hover-color);\n }\n }\n\n &:not(.active) {\n .page-link {\n &:hover {\n box-shadow: inset var(--btn-border-color) 0 0 0 1px;\n }\n }\n }\n\n &.disabled {\n cursor: not-allowed;\n\n .page-link {\n color: rgba(108, 117, 125, 0.57);\n }\n }\n } /* .page-item */\n} /* .pagination */\n\n/* Tablet */\n@media all and (min-width: 768px) {\n %img-radius {\n border-radius: 0 $base-radius $base-radius 0;\n }\n\n #post-list {\n .card {\n .card-body {\n padding: 1.75rem 1.75rem 1.25rem 1.75rem;\n\n .card-text {\n display: inherit !important;\n }\n\n .post-meta {\n i {\n &:not(:first-child) {\n margin-left: 1.75rem;\n }\n }\n }\n }\n }\n }\n}\n\n/* Hide SideBar and TOC */\n@media all and (max-width: 830px) {\n .pagination {\n .page-item {\n &:not(:first-child):not(:last-child) {\n display: none;\n }\n }\n }\n}\n\n/* Sidebar is visible */\n@media all and (min-width: 831px) {\n #post-list {\n margin-top: 2.5rem;\n }\n\n .pagination {\n font-size: 0.85rem;\n justify-content: center;\n\n .page-item {\n &:not(:last-child) {\n margin-right: 0.7rem;\n }\n }\n\n .page-index {\n display: none;\n }\n } /* .pagination */\n}\n","/*\n Post-specific style\n*/\n\n%btn-post-nav {\n width: 50%;\n position: relative;\n border-color: var(--btn-border-color);\n}\n\n@mixin dot($pl: 0.25rem, $pr: 0.25rem) {\n content: '\\2022';\n padding-left: $pl;\n padding-right: $pr;\n}\n\nh1 + .post-meta {\n > span + span::before {\n @include dot;\n }\n\n em,\n time {\n @extend %text-highlight;\n }\n\n em {\n a {\n color: inherit;\n }\n }\n}\n\n.post-tail-wrapper {\n @extend %text-sm;\n\n margin-top: 6rem;\n border-bottom: 1px double var(--main-border-color);\n\n .license-wrapper {\n line-height: 1.2rem;\n\n > a {\n @extend %text-highlight;\n\n &:hover {\n @extend %link-hover;\n }\n }\n\n span:last-child {\n @extend %text-sm;\n }\n } /* .license-wrapper */\n\n .post-meta a:not(:hover) {\n @extend %link-underline;\n }\n\n .share-wrapper {\n vertical-align: middle;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n\n %icon-size {\n font-size: 1.125rem;\n }\n\n .share-icons {\n display: flex;\n\n i {\n color: var(--btn-share-color);\n\n @extend %icon-size;\n }\n\n > * {\n @extend %icon-size;\n\n margin-left: 0.5rem;\n\n &:hover {\n i {\n @extend %btn-share-hovor;\n }\n }\n }\n\n button {\n padding: 0;\n border: none;\n line-height: inherit;\n\n @extend %cursor-pointer;\n }\n } /* .share-icons */\n } /* .share-wrapper */\n}\n\n.share-mastodon {\n /* See: https://github.com/justinribeiro/share-to-mastodon#properties */\n --wc-stm-font-family: $font-family-base;\n --wc-stm-dialog-background-color: var(--card-bg);\n --wc-stm-form-button-border: 1px solid var(--btn-border-color);\n --wc-stm-form-submit-background-color: var(--sidebar-btn-bg);\n --wc-stm-form-cancel-background-color: var(--sidebar-btn-bg);\n --wc-stm-form-button-background-color-hover: #007bff;\n --wc-stm-form-button-color-hover: white;\n\n font-size: 1rem;\n}\n\n.post-tags {\n line-height: 2rem;\n\n .post-tag {\n &:hover {\n @extend %link-hover;\n @extend %tag-hover;\n @extend %no-bottom-border;\n }\n }\n}\n\n.post-navigation {\n .btn {\n @extend %btn-post-nav;\n\n &:not(:hover) {\n color: var(--link-color);\n }\n\n &:hover {\n &:not(.disabled)::before {\n color: whitesmoke;\n }\n }\n\n &.disabled {\n @extend %btn-post-nav;\n\n pointer-events: auto;\n cursor: not-allowed;\n background: none;\n color: gray;\n }\n\n &.btn-outline-primary.disabled:focus {\n box-shadow: none;\n }\n\n &::before {\n color: var(--text-muted-color);\n font-size: 0.65rem;\n text-transform: uppercase;\n content: attr(aria-label);\n }\n\n &:first-child {\n border-radius: $base-radius 0 0 $base-radius;\n left: 0.5px;\n }\n\n &:last-child {\n border-radius: 0 $base-radius $base-radius 0;\n right: 0.5px;\n }\n }\n\n p {\n font-size: 1.1rem;\n line-height: 1.5rem;\n margin-top: 0.3rem;\n white-space: normal;\n }\n} /* .post-navigation */\n\n@media (hover: hover) {\n .post-navigation {\n .btn,\n .btn::before {\n transition: all 0.35s ease-in-out;\n }\n }\n}\n\n@-webkit-keyframes fade-up {\n from {\n opacity: 0;\n position: relative;\n top: 2rem;\n }\n\n to {\n opacity: 1;\n position: relative;\n top: 0;\n }\n}\n\n@keyframes fade-up {\n from {\n opacity: 0;\n position: relative;\n top: 2rem;\n }\n\n to {\n opacity: 1;\n position: relative;\n top: 0;\n }\n}\n\n#toc-wrapper {\n border-left: 1px solid rgba(158, 158, 158, 0.17);\n position: -webkit-sticky;\n position: sticky;\n top: 4rem;\n transition: top 0.2s ease-in-out;\n -webkit-animation: fade-up 0.8s;\n animation: fade-up 0.8s;\n\n ul {\n list-style: none;\n font-size: 0.85rem;\n line-height: 1.25;\n padding-left: 0;\n\n li {\n &:not(:last-child) {\n margin: 0.4rem 0;\n }\n\n a {\n padding: 0.2rem 0 0.2rem 1.25rem;\n }\n }\n\n /* Overwrite TOC plugin style */\n\n .toc-link {\n display: block;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n\n &:hover {\n color: var(--toc-highlight);\n text-decoration: none;\n }\n\n &::before {\n display: none;\n }\n }\n\n .is-active-link {\n color: var(--toc-highlight) !important;\n font-weight: 600;\n\n &::before {\n display: inline-block;\n width: 1px;\n left: -1px;\n height: 1.25rem;\n background-color: var(--toc-highlight) !important;\n }\n }\n\n ul {\n padding-left: 0.75rem;\n }\n }\n}\n\n/* --- Related Posts --- */\n\n#related-posts {\n > h3 {\n @include label(1.1rem, 600);\n }\n\n time {\n @extend %normal-font-style;\n @extend %text-xs;\n\n color: var(--text-muted-color);\n }\n\n p {\n font-size: 0.9rem;\n margin-bottom: 0.5rem;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n\n .card {\n h4 {\n @extend %text-clip;\n }\n }\n}\n\n/* stylelint-disable-next-line selector-id-pattern */\n#disqus_thread {\n min-height: 8.5rem;\n}\n\n.utterances {\n max-width: 100%;\n}\n\n%btn-share-hovor {\n color: var(--btn-share-hover-color) !important;\n}\n\n.share-label {\n @include label(inherit, 400, inherit);\n\n &::after {\n content: ':';\n }\n}\n\n@media all and (max-width: 576px) {\n .post-tail-bottom {\n flex-wrap: wrap-reverse !important;\n\n > div:first-child {\n width: 100%;\n margin-top: 1rem;\n }\n }\n}\n\n@media all and (max-width: 768px) {\n .content > p > img {\n max-width: calc(100% + 1rem);\n }\n}\n\n/* Hide SideBar and TOC */\n@media all and (max-width: 849px) {\n .post-navigation {\n padding-left: 0;\n padding-right: 0;\n margin-left: -0.5rem;\n margin-right: -0.5rem;\n }\n}\n","/*\n Styles for Tab Tags\n*/\n\n.tag {\n border-radius: 0.7em;\n padding: 6px 8px 7px;\n margin-right: 0.8rem;\n line-height: 3rem;\n letter-spacing: 0;\n border: 1px solid var(--tag-border) !important;\n box-shadow: 0 0 3px 0 var(--tag-shadow);\n\n span {\n margin-left: 0.6em;\n font-size: 0.7em;\n font-family: Oswald, sans-serif;\n }\n}\n","/*\n Style for Archives\n*/\n\n#archives {\n letter-spacing: 0.03rem;\n\n $timeline-width: 4px;\n\n %timeline {\n content: '';\n width: $timeline-width;\n position: relative;\n float: left;\n background-color: var(--timeline-color);\n }\n\n .year {\n height: 3.5rem;\n font-size: 1.5rem;\n position: relative;\n left: 2px;\n margin-left: -$timeline-width;\n\n &::before {\n @extend %timeline;\n\n height: 72px;\n left: 79px;\n bottom: 16px;\n }\n\n &:first-child::before {\n @extend %timeline;\n\n height: 32px;\n top: 24px;\n }\n\n /* Year dot */\n &::after {\n content: '';\n display: inline-block;\n position: relative;\n border-radius: 50%;\n width: 12px;\n height: 12px;\n left: 21.5px;\n border: 3px solid;\n background-color: var(--timeline-year-dot-color);\n border-color: var(--timeline-node-bg);\n box-shadow: 0 0 2px 0 #c2c6cc;\n z-index: 1;\n }\n }\n\n ul {\n li {\n font-size: 1.1rem;\n line-height: 3rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n\n &:nth-child(odd) {\n background-color: var(--main-bg, #ffffff);\n background-image: linear-gradient(\n to left,\n #ffffff,\n #fbfbfb,\n #fbfbfb,\n #fbfbfb,\n #ffffff\n );\n }\n\n &::before {\n @extend %timeline;\n\n top: 0;\n left: 77px;\n height: 3.1rem;\n }\n }\n\n &:last-child li:last-child::before {\n height: 1.5rem;\n }\n } /* #archives ul */\n\n .date {\n white-space: nowrap;\n display: inline-block;\n position: relative;\n right: 0.5rem;\n\n &.month {\n width: 1.4rem;\n text-align: center;\n }\n\n &.day {\n font-size: 85%;\n font-family: Lato, sans-serif;\n }\n }\n\n a {\n /* post title in Archvies */\n margin-left: 2.5rem;\n position: relative;\n top: 0.1rem;\n\n &:hover {\n border-bottom: none;\n }\n\n &::before {\n /* the dot before post title */\n content: '';\n display: inline-block;\n position: relative;\n border-radius: 50%;\n width: 8px;\n height: 8px;\n float: left;\n top: 1.35rem;\n left: 71px;\n background-color: var(--timeline-node-bg);\n box-shadow: 0 0 3px 0 #c2c6cc;\n z-index: 1;\n }\n }\n} /* #archives */\n\n@media all and (max-width: 576px) {\n #archives {\n margin-top: -1rem;\n\n ul {\n letter-spacing: 0;\n }\n }\n}\n","/*\n Style for Tab Categories\n*/\n\n%category-icon-color {\n color: gray;\n}\n\n.categories {\n margin-bottom: 2rem;\n border-color: var(--categories-border);\n\n &.card,\n .list-group {\n @extend %rounded;\n }\n\n .card-header {\n $radius: calc($base-radius - 1px);\n\n padding: 0.75rem;\n border-radius: $radius;\n border-bottom: 0;\n\n &.hide-border-bottom {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n }\n }\n\n i {\n @extend %category-icon-color;\n\n font-size: 86%; /* fontawesome icons */\n }\n\n .list-group-item {\n border-left: none;\n border-right: none;\n padding-left: 2rem;\n\n &:first-child {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n }\n\n &:last-child {\n border-bottom: 0;\n }\n }\n} /* .categories */\n\n.category-trigger {\n width: 1.7rem;\n height: 1.7rem;\n border-radius: 50%;\n text-align: center;\n color: #6c757d !important;\n\n i {\n position: relative;\n height: 0.7rem;\n width: 1rem;\n transition: transform 300ms ease;\n }\n\n &:hover {\n i {\n color: var(--categories-icon-hover-color);\n }\n }\n}\n\n/* only works on desktop */\n@media (hover: hover) {\n .category-trigger:hover {\n background-color: var(--categories-hover-bg);\n }\n}\n\n.rotate {\n transform: rotate(-90deg);\n}\n","/*\n Style for page Category and Tag\n*/\n\n.dash {\n margin: 0 0.5rem 0.6rem 0.5rem;\n border-bottom: 2px dotted var(--dash-color);\n}\n\n#page-category,\n#page-tag {\n ul > li {\n line-height: 1.5rem;\n padding: 0.6rem 0;\n\n /* dot */\n &::before {\n background: #999999;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n display: block;\n content: '';\n position: relative;\n top: 0.6rem;\n margin-right: 0.5rem;\n }\n\n /* post's title */\n > a {\n @extend %no-bottom-border;\n\n font-size: 1.1rem;\n }\n }\n}\n\n/* tag icon */\n#page-tag h1 > i {\n font-size: 1.2rem;\n}\n\n#page-category h1 > i {\n font-size: 1.25rem;\n}\n\n#page-category,\n#page-tag,\n#access-lastmod {\n a:hover {\n @extend %link-hover;\n\n margin-bottom: -1px; /* Avoid jumping */\n }\n}\n\n@media all and (max-width: 576px) {\n #page-category,\n #page-tag {\n ul > li {\n &::before {\n margin: 0 0.5rem;\n }\n\n > a {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n }\n }\n}\n"],"file":"style.css"} \ No newline at end of file diff --git a/assets/img/2022-04-05-1.jpg b/assets/img/2022-04-05-1.jpg new file mode 100644 index 0000000..a9cc3be Binary files /dev/null and b/assets/img/2022-04-05-1.jpg differ diff --git a/assets/img/2022-04-09-1.jpg b/assets/img/2022-04-09-1.jpg new file mode 100644 index 0000000..ff1f166 Binary files /dev/null and b/assets/img/2022-04-09-1.jpg differ diff --git a/assets/img/2022-04-09-2.jpg b/assets/img/2022-04-09-2.jpg new file mode 100644 index 0000000..a2ea4a9 Binary files /dev/null and b/assets/img/2022-04-09-2.jpg differ diff --git a/assets/img/2022-04-09-3.jpg b/assets/img/2022-04-09-3.jpg new file mode 100644 index 0000000..2bbe010 Binary files /dev/null and b/assets/img/2022-04-09-3.jpg differ diff --git a/assets/img/2022-04-09-4.jpg b/assets/img/2022-04-09-4.jpg new file mode 100644 index 0000000..965274b Binary files /dev/null and b/assets/img/2022-04-09-4.jpg differ diff --git a/assets/img/2022-04-09-5.jpg b/assets/img/2022-04-09-5.jpg new file mode 100644 index 0000000..70e3ea2 Binary files /dev/null and b/assets/img/2022-04-09-5.jpg differ diff --git a/assets/img/2022-04-09-6.jpg b/assets/img/2022-04-09-6.jpg new file mode 100644 index 0000000..e84a928 Binary files /dev/null and b/assets/img/2022-04-09-6.jpg differ diff --git a/assets/img/2022-04-13-1.jpg b/assets/img/2022-04-13-1.jpg new file mode 100644 index 0000000..92645e6 Binary files /dev/null and b/assets/img/2022-04-13-1.jpg differ diff --git a/assets/img/2022-04-13-10.jpg b/assets/img/2022-04-13-10.jpg new file mode 100644 index 0000000..199fc14 Binary files /dev/null and b/assets/img/2022-04-13-10.jpg differ diff --git a/assets/img/2022-04-13-11.jpg b/assets/img/2022-04-13-11.jpg new file mode 100644 index 0000000..950243e Binary files /dev/null and b/assets/img/2022-04-13-11.jpg differ diff --git a/assets/img/2022-04-13-2.jpg b/assets/img/2022-04-13-2.jpg new file mode 100644 index 0000000..739995a Binary files /dev/null and b/assets/img/2022-04-13-2.jpg differ diff --git a/assets/img/2022-04-13-3.jpg b/assets/img/2022-04-13-3.jpg new file mode 100644 index 0000000..2eeedb1 Binary files /dev/null and b/assets/img/2022-04-13-3.jpg differ diff --git a/assets/img/2022-04-13-4.jpg b/assets/img/2022-04-13-4.jpg new file mode 100644 index 0000000..88f8db4 Binary files /dev/null and b/assets/img/2022-04-13-4.jpg differ diff --git a/assets/img/2022-04-13-5.jpg b/assets/img/2022-04-13-5.jpg new file mode 100644 index 0000000..b9d7196 Binary files /dev/null and b/assets/img/2022-04-13-5.jpg differ diff --git a/assets/img/2022-04-13-6.jpg b/assets/img/2022-04-13-6.jpg new file mode 100644 index 0000000..eb7fcd9 Binary files /dev/null and b/assets/img/2022-04-13-6.jpg differ diff --git a/assets/img/2022-04-13-7.jpg b/assets/img/2022-04-13-7.jpg new file mode 100644 index 0000000..d65b053 Binary files /dev/null and b/assets/img/2022-04-13-7.jpg differ diff --git a/assets/img/2022-04-13-8.jpg b/assets/img/2022-04-13-8.jpg new file mode 100644 index 0000000..74fd209 Binary files /dev/null and b/assets/img/2022-04-13-8.jpg differ diff --git a/assets/img/2022-04-13-9.jpg b/assets/img/2022-04-13-9.jpg new file mode 100644 index 0000000..115d366 Binary files /dev/null and b/assets/img/2022-04-13-9.jpg differ diff --git a/assets/img/2022-04-20-1.jpg b/assets/img/2022-04-20-1.jpg new file mode 100644 index 0000000..41c3782 Binary files /dev/null and b/assets/img/2022-04-20-1.jpg differ diff --git a/assets/img/2022-05-02-1.jpg b/assets/img/2022-05-02-1.jpg new file mode 100644 index 0000000..efa00c9 Binary files /dev/null and b/assets/img/2022-05-02-1.jpg differ diff --git a/assets/img/2022-05-02-2.jpg b/assets/img/2022-05-02-2.jpg new file mode 100644 index 0000000..82a55c4 Binary files /dev/null and b/assets/img/2022-05-02-2.jpg differ diff --git a/assets/img/2022-05-02-3.jpg b/assets/img/2022-05-02-3.jpg new file mode 100644 index 0000000..1696286 Binary files /dev/null and b/assets/img/2022-05-02-3.jpg differ diff --git a/assets/img/2022-05-13-1.jpg b/assets/img/2022-05-13-1.jpg new file mode 100644 index 0000000..ffba66d Binary files /dev/null and b/assets/img/2022-05-13-1.jpg differ diff --git a/assets/img/2022-05-13-2.jpg b/assets/img/2022-05-13-2.jpg new file mode 100644 index 0000000..bdc114e Binary files /dev/null and b/assets/img/2022-05-13-2.jpg differ diff --git a/assets/img/2023-01-20-1.jpg b/assets/img/2023-01-20-1.jpg new file mode 100644 index 0000000..97c1750 Binary files /dev/null and b/assets/img/2023-01-20-1.jpg differ diff --git a/assets/img/2023-01-20-2.jpg b/assets/img/2023-01-20-2.jpg new file mode 100644 index 0000000..38ac207 Binary files /dev/null and b/assets/img/2023-01-20-2.jpg differ diff --git a/assets/img/favicons/android-chrome-192x192.png b/assets/img/favicons/android-chrome-192x192.png new file mode 100644 index 0000000..a949d2f Binary files /dev/null and b/assets/img/favicons/android-chrome-192x192.png differ diff --git a/assets/img/favicons/android-chrome-512x512.png b/assets/img/favicons/android-chrome-512x512.png new file mode 100644 index 0000000..a0cdd95 Binary files /dev/null and b/assets/img/favicons/android-chrome-512x512.png differ diff --git a/assets/img/favicons/apple-touch-icon.png b/assets/img/favicons/apple-touch-icon.png new file mode 100644 index 0000000..648097f Binary files /dev/null and b/assets/img/favicons/apple-touch-icon.png differ diff --git a/assets/img/favicons/browserconfig.xml b/assets/img/favicons/browserconfig.xml new file mode 100644 index 0000000..54217f7 --- /dev/null +++ b/assets/img/favicons/browserconfig.xml @@ -0,0 +1 @@ + #da532c diff --git a/assets/img/favicons/favicon-16x16.png b/assets/img/favicons/favicon-16x16.png new file mode 100644 index 0000000..f44237a Binary files /dev/null and b/assets/img/favicons/favicon-16x16.png differ diff --git a/assets/img/favicons/favicon-32x32.png b/assets/img/favicons/favicon-32x32.png new file mode 100644 index 0000000..d5d021d Binary files /dev/null and b/assets/img/favicons/favicon-32x32.png differ diff --git a/assets/img/favicons/favicon.ico b/assets/img/favicons/favicon.ico new file mode 100644 index 0000000..5611568 Binary files /dev/null and b/assets/img/favicons/favicon.ico differ diff --git a/assets/img/favicons/mstile-150x150.png b/assets/img/favicons/mstile-150x150.png new file mode 100644 index 0000000..c0d045e Binary files /dev/null and b/assets/img/favicons/mstile-150x150.png differ diff --git a/assets/img/favicons/site.webmanifest b/assets/img/favicons/site.webmanifest new file mode 100644 index 0000000..9b975b4 --- /dev/null +++ b/assets/img/favicons/site.webmanifest @@ -0,0 +1 @@ +{ "name": "Phyo's Log", "short_name": "Phyo's Log", "description": "Hi, my name is Yar Khine Phyo. This is a technical blog on computer science and software engineering related concepts that I will contribute to from time to time.", "icons": [ { "src": "/assets/img/favicons/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/assets/img/favicons/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }], "start_url": "/index.html", "theme_color": "#2a1e6b", "background_color": "#ffffff", "display": "fullscreen" } diff --git a/assets/img/me_1.jpg b/assets/img/me_1.jpg new file mode 100644 index 0000000..2387ebc Binary files /dev/null and b/assets/img/me_1.jpg differ diff --git a/assets/img/me_2.jpg b/assets/img/me_2.jpg new file mode 100644 index 0000000..d37a1ae Binary files /dev/null and b/assets/img/me_2.jpg differ diff --git a/assets/index.html b/assets/index.html new file mode 100644 index 0000000..3a9e350 --- /dev/null +++ b/assets/index.html @@ -0,0 +1,11 @@ + + + + Redirecting… + + + + +

Redirecting…

+ Click here if you are not redirected. + diff --git a/assets/js/data/search.json b/assets/js/data/search.json new file mode 100644 index 0000000..226fbe1 --- /dev/null +++ b/assets/js/data/search.json @@ -0,0 +1 @@ +[ { "title": "Learning Points: Weaviate Vector Database", "url": "/posts/weaviate-vector-database/", "categories": "Tech", "tags": "Database, System-Design, Learning-Points", "date": "2024-01-24 04:20:00 +0000", "snippet": "This video is a deep dive on the vector database Weaviate as part of the weekly database seminars by CMU Database Group. The presenter is Etienne Dilocker from Weaviate.Why a Vector DatabaseInstead...", "content": "This video is a deep dive on the vector database Weaviate as part of the weekly database seminars by CMU Database Group. The presenter is Etienne Dilocker from Weaviate.Why a Vector DatabaseInstead of indexing literal keywords from paragraphs, meanings (embeddings) can be indexed for search purposes.LLMs tend to hallucinate less when some context around the question is given.Retrieval Augmented Generation (RAG) retrieves the top few relevant documents from a vector database before providing as a context to LLMs. There is no need to retrain LLMs to keep updated with the latest information.Weaviate ArchitectureCollections are logical groups by the user. Shards distribute data across multiple nodes.In each shard, the HNSW index is used most of the time. The object store can keep any binary files that are related to the embeddings. There is no need to have secondary storage for non-key-value data. The inverted index allows searching by properties and BM25 (bag-of-words retrieval) queries. +-----------------------+ | Weaviate Setup | +-----------------------+ +-- | Collection \"Articles\" | | | Collection \"Authors\" | | | ... | | +-----------------------+ | | +-----------------------+ +-> | Collection \"Articles\" | +-----------------------+ +-- | Shard A | | | Shard B | | | ... | | +-----------------------+ | | +-----------------------+ +-> | Shard A | +-----------------------+ | HNSW Index | | Object Store (LSM) | | Inverted Index (LSM) | +-----------------------+Consistent hashing on a specific key is used for sharding. On each node (physical shard), there can be multiple logical shards.If the number of shards are changed on the fly, there are measures to ensure that minimal amount of data is moved around the nodes.Hierarchical Navigable Small World (HNSW)The index approximates nearest neighbor proximity graph with multiple layers. Compared to other indexes, it is slower to build but faster to query with.Algorithm for querying a Navigable Small World (NSW) graph: Pick a random entry point. Follow all the edges and evaluate if the newly discovered points are closer. Recenter on the best known point. Don’t score any points twice. Stop when there is no more improvement.Considerations when building a NSW graph: A real life dataset with natural clusters would be more efficient as compared to randomly generated points. The number of connections between each point. During the build phase, there is a search on the graph to decide where to place nodes. The more time spent on the build phase, the more efficient the search phase is.Layers of NSW is HNSW. There are fewer connections per point on higher layers of HNSW. Few connections also means the connections “travel” longer distances. The search starts from the higher layers then move to lower layers.Rebuilding NSWAdding new data points do not degrade the graph. When one point has too many connections, pruning is done by reducing first-grade connections (direct) to second-grade connections (indirect).Deleting points degrades the query time. When a point is marked as tombstone, it can still be used for traversing the graph but not be included in the result set. When the proportion of tombstones is large, the graph is rebuilt. On the fly, there are also reassignments of the tombstone’s connections to other points to make sure clusters remain connected. This operation is expensive but works well if there are not too many deletes." }, { "title": "Learning Points: Streamlining FedRAMP Compliance with CNCF", "url": "/posts/streamlining-fedramp-compliance/", "categories": "Tech", "tags": "System-Design, Learning-Points", "date": "2024-01-21 03:55:00 +0000", "snippet": "This video is about streamlining FedRAMP compliance with CNCF technologies. The presenters are Ali Monfre and Vlad Ungureanu from Palantir Technologies.FedRAMP OverviewFedRAMP is the accreditation ...", "content": "This video is about streamlining FedRAMP compliance with CNCF technologies. The presenters are Ali Monfre and Vlad Ungureanu from Palantir Technologies.FedRAMP OverviewFedRAMP is the accreditation required for companies to sell SaaS solutions to the government instead of on-prem solutions. General steps include: Identify a sponsor to join the authorization board. Document security controls and validate them with third-party assessments. Final review with the sponsor and FedRAMP management office.Challenges Pre-Kubernetes Scanning requirements included vulnerability scans, virus scans, configuration scans. They had to be done weekly or monthly basis. Before Kubernetes adoption, there was no uniform infrastructure or standardized immutable AMIs and container images. Palantir had to scan every single piece of live infrastructure. Security patching requirements required constant host reboots. Orchestrating reboot cycles without downtime was a lot of engineering effort. FIPS encryption is the government approved standards for cypher suites and cryptographic libraries. It takes a while for newly introduced cypher suites to be FIPS validated. As Palantir relies on open source technology, maintaining FIPS validated traffic between diverse services was a very difficult problem.How Palantir SolvedFor operating systems, major vendors have STIGs published. Palantir started running immutable machine images which were scanned during the CI process. This provided a faster feedback loop for the developers. Every host was also terminated every 72 hours. One side effect was that the vulnerabilities would be patched within three days.For container images, an internal “golden image” was used by all products. The downstream images that used this were built automatically. Trivy (a scanning tool) was also embedded into CI.Regarding FIPS, there is a long processing time for NIST (government agency) to validate new kernels and cryptographic libraries. Thus, Palantir cannot used features offered by new versions of the kernel.Regarding service-to-service communication, Cilium CNI is used for k8s clusters. IPSec encryption in Cilium ensures FIPS validated encryption between pods. Cilium also has powerful network policy primitives which made it easier to adhere to FedRAMP standards.Regarding Ingress/ Egress traffic, NGINX+ provides FIPS validation but there were performance problems encountered by Palantir. The decision was made to switched to Envoy, which is an open sourced service proxy designed for cloud native applications. BoringSSL with FIPS configured was used as the TLS provider.Regarding Host Intrusion Detection System, originally OSQUERY tool was used. However, it did not integrate well with k8s so all the pods showed up as similar processes. The decision was made to switch to Isovalent Tetragon which was an eBPF tool that integrated well with k8s.Continuing ChallengesThere are more challenges not solved by CNCF technologies out of the box. To solve this, Palantir created Apollo and FedStart which helps companies to deploy software for the federal environment. Change Management: Security relevant changes must be approved by authorized US person. Combining compliance checks with other automation tools is challenging. Policy-based rollouts allow CICD while enforcing approvals from the US persons. Process Controls: A lot of FedRAMP controls are not technical, but more of procedure-related. Standardized infrastructure provided by Palantir means that the companies can just follow the provided templates for the documentation rather than creating from scratch. Vulnerability Management: Palantir manages minimized images to reduce exposures to CVEs." }, { "title": "Learning Points: Snowflake Iceberg, Streaming, Unistore", "url": "/posts/snowflake-new-streams-talk/", "categories": "Tech", "tags": "Data-Engineering, System-Design, Learning-Points", "date": "2024-01-21 02:20:00 +0000", "snippet": "This video is about Snowflake Iceberg Tables, Streaming Ingest and Unistore. The presenters are N.Single, T.Jones and A.Motivala as part of the Database Seminar Series by CMU Database Group.Problem...", "content": "This video is about Snowflake Iceberg Tables, Streaming Ingest and Unistore. The presenters are N.Single, T.Jones and A.Motivala as part of the Database Seminar Series by CMU Database Group.Problems with Traditional Data LakesTraditional data lakes use file systems as the metadata layer. For example, data for each table is organized in a directory. Partitioning is implemented through nested directories. Using directories as database tables cause problems. Not easy to provide ACID guarantees. Multiple partition inserts were not atomic. Tools may directly access the file systems without consistent metadata updates. Schema evolution was very error prone. No clear way for access control.Apache Iceberg Describes how to perform updates to the table. Specification to achieve snapshot isolation. File memberships are defined to a snapshot. Easier schema evolution with Iceberg metadata.Snowflake ArchitectureTable metadata and data are stored as Parquet files on customers’ bucket. +-------------------------------------------------+ Cloud services | Authentication and Authorization | +-------------------------------------------------+ | Infra Manager | Optimizer | Transaction Manager | +-------------------------------------------------+ | Metadata Storage (Customer's Bucket) | +-------------------------------------------------+ +-------------------+ +-------------------+ Compute | Warehouse | | Warehouse | +-------------------+ +-------------------+ +-------------------------------------------------+ Storage | Data (Customer's Bucket) | +-------------------------------------------------+Customers will have to provide Snowflake External Volumes on any the cloud providers with access credentials. Data and metadata files are written to the External Volume.Metadata GenerationSnowflake has its own files to store snapshot metadata originally. To support Iceberg format, each table commit requires generation of both Iceberg metadata and internal Snowflake metadata.The generation of additional metadata files (Iceberg) increases query latency significantly. Thus Iceberg metadata files are generated on the background at the same time.When Snowflake metadata files are generated, the transaction is considered commited. If the server crashes before Iceberg metadata is generated, the request would come to the new Snowflake server and the Iceberg metadata will be generated on the fly.How Spark Accesses IcebergThe Iceberg SDK accesses a catalog which returns the location of metadata files in customers’ buckets. Then the SDK interprets the metadata files and returns the locations of data files in an API to Spark.Spark ---> Iceberg SDK ---> 1. Catalog (Hive, Glue) | | | -----------> 2. Storage (Snapshot Metadata) | ------------> 3. Data FilesSnowpipe StreamingBefore this feature, the original Snowpipe did continuous copying from a bucket to a table behind the scenes, in batches. However, there was no low latency, high throughput, in-order processing feature. Snowpipe Streaming provides: Ingestion to tables over HTTPs. Exactly once (?) and per-channel ordering guarantees. Low latency, queryable after seconds. High throughput, GB/s supported. Low overhead for configuration.New concepts include: Channel - Logical partition that represents a connection from a single client to a destination table. Client SDK - Accepts bytes from application, writes data to cloud storage as blobs and registers them to Snowflake. Mixed table - Contains both BDEC (Chunks of Arrow format) that is written by the client SDK and Snowflake’s propriatory FDN format. In the background, the BDEC files are rewritten into FDN format. The rewriting process is transparent to the users as queries can be done on the mixture of BDEC and FDN files. However, the rewriting process implies additional compute which will be charged to the customer.The implementation details: User code uses the Snowpipe Streaming Client SDK to open a Channel and write rows in the Channel. Client SDK writes BDEC files to the Streaming Ingest’s internal storage (Blobstore). Note that FDN files exist in the same Blobstore. Client SDK registers the blob via REST API to Snowflake’s Frontend node. Frontend node fans out per-table registration requests to the Snowflake’s Commit Service and provides a progress update to the client SDK. The Commit Service validates and deduplicates chunks per-table in memory. Then it commits by changing table version references to the new Arrow chunks (BDEC). Snowpipe creates regular FDN files from BDEC files. At this point, queries would reflect the newly added data in BDEC files.UnistoreSnowflake’s product for combining transactional and analytical workload on one platform.A new table type that works with existing snowflake tables, supports transactional features such as unique keys, referential integrity constraints and cross domain transactions.CREATE HYBRID TABLE CustomerTable { customer_id int primary key, full_name varchar(256), ...}" }, { "title": "Learning Points: Scaling Monitoring from Prometheus to M3", "url": "/posts/scaling-monitoring-with-m3/", "categories": "Tech", "tags": "System-Design, Learning-Points", "date": "2024-01-20 03:30:00 +0000", "snippet": "I enjoy watching 45 minutes to 1 hour long technical talks at conferences. Unfortunately, I am not retaining the knowledge as long as I would like to. From now on, I am going to try summarizing my ...", "content": "I enjoy watching 45 minutes to 1 hour long technical talks at conferences. Unfortunately, I am not retaining the knowledge as long as I would like to. From now on, I am going to try summarizing my takeaways for each videos to improve my own retention.This video is about scaling monitoring from Prometheus to M3 at Databricks presented by YY Wan and Nick Lanham.ContextPrometheus based monitoring system is used in Databricks since 2016. Most internal services run on Kubernetes and Spark workloads run in VMs in customer environments. PromQL is widely used by engineers.In each region, there are two Prometheus servers, Prom-Normal and Prom-Proxied. Prom-Normal scrapes metrics from internal services in k8s pods. Metrics from external services are pushed by Kafka to the Metrics Proxy Service (on k8s). Prom-Proxied scrapes metrics from the Metrics Proxy Service. Having two servers also means metrics can be sharded logically (internal/external) as all the metrics would not fit on one. Disks are attached to each Prometheus server to store metrics.Globally, there is a Prometheus server that contain a subset of metrics federated from all the regions.Users interact with the monitoring system in two ways: alerting and querying. Regional Prometheus servers issue alerts to the Alert Manager Service which notifies engineers via PagerDuty. Users also query regional or global servers for insights.Problem50 regions across multiple cloud providers with 4 million VMs of Databricks services and Spark workers. The global Prometheus server was huge. Disk usage of 4TB. Big queries not completing due to frequent OOMs. Short retention period, only 15 days. Users have sharded view of metrics (Prom-Normal, Prom-Proxied). Strict metrics whitelist to keep the servers running. Metrics are lost during restarts as it takes a while to replay the large volume of logs per server.RequirementsMust: High metrics volume. 90 days retention. PromQL compatible. Global view of metrics. High availability setup.Nice to have: Good maintenance story, no metrics gap. Open source. Battle tested.Why M3?Why M3 solves the problem for Databricks: Horizontally scalable. High availability with multi-replica setup. Exposes Prometheus API query endpoint. Global querying feature. Battle-tested at Uber production environment. Exists k8s operator for automated deployments.M3 ArchitectureApplication --- M3 collector | M3 aggregator | M3DB --- M3 query --- Grafana M3DB is a distributed time-series database, optimizations include time series compression. Sharding is also in-built. M3 Query is a stateless query server that accepts M3QL or PromQL. Coordinates with servers in other regions to achieve global queries. M3 Coordinator provides APIs to read and writing to M3DB. It also acts as a bridge with Prometheus. M3 Aggregator provides streaming aggregation of time series data. It reduces cardinality and/or datapoint resolution to reduce the volume of data stored.Initial PlanProm-Normal and Prom-Proxied remote-write data in M3DB instead of local disks. For regional query, users only need to interact with regional M3DB instead of thinking about sharded view of Prom-Normal and Prom-Proxied metrics. M3 Query server would also provide global view without requiring another global Prometheus server. Least amount of work.However, remote-writes by only two Prometheus servers could not achieved at a scale that Databricks required.Small Components to Replace Prom-Normal and Prom-ProxiedMore servers would achieve higher write throughput into M3DB.To replace Prom-Normal, multiple Grafana Scrape Agents scrape metrics from internal services and write to M3DB.To replace Prom-Proxied, Metrics Proxy Service directly writes to M3DB. Note that this service is already made up of multiple servers. This reduces end-to-end latency of external metrics too.Update AlertingOriginally, the alerting rule configurations are used in Prometheus servers to issue alerts to Alert Manager Service.Databricks built its own rule engine that takes the same configurations and interacts with M3DB and Alert Manager Service.Noisy Neighbor IssuesM3 Coordinators were having noisy neighbor issues. If users submit heavy queries, the coordinators would not be able to serve the write paths from Metrics Proxy Service and Grafana Scrape Agents.To solve this, M3 Coordinators were separately deployed for read and writes. CPU-heavy machines for write-coordinators and Memory-heavy machines for read-coordinators.Monitoring the M3 Monitoring SystemVanilla Prometheus servers that scrape M3 related components. Metrics retention period is short but it is sufficient for the use case.Global Prometheus server to federate metrics for all the Prometheus server.Final Architecture" }, { "title": "Learning Points: Kubernetes Networking Deep Dive", "url": "/posts/kubernetes-networking-intro-and-deep-dive/", "categories": "Tech", "tags": "Networking, System-Design, Learning-Points", "date": "2024-01-20 03:30:00 +0000", "snippet": "This video is about how networking works in Kubernetes by Bowei Du and Tim Hockin from Google.Networking APIs Exposed by Kubernetes Service, Endpoint: Service registration and discovery. Ingress:...", "content": "This video is about how networking works in Kubernetes by Bowei Du and Tim Hockin from Google.Networking APIs Exposed by Kubernetes Service, Endpoint: Service registration and discovery. Ingress: L7 HTTP routing. Gateway: Next-gen HTTP routing and service ingress. NetworkPolicy: Application firewall.Pod Networking ModelAll pods can reach all other pods across nodes. The network drivers on each node and networking between pods are implemented by Kubelet CNI implementation.One implementation is using hosts as a router device while a routing entry is added for each pod. For example, Flannel host-gw mode and Calico BGP mode. Another implementation is using overlay networks where layer 2 frames are encapsulated into layer 4 UDP packets alongside a VxLAN header. For example, Flannel and Calico VxLAN mode.Service API ImplementationPod IP addresses are ephemeral. Service API exposes a group of pods via one IP (ClusterIP). This is how the API works: A client pod sends a DNS query of to KubeDNS service. The KubeProxy sends the query to a KubeDNS pod which returns the ClusterIP. The client sends a packet to the ClusterIP. The KubeProxy intercepts the packet and sends it to one of the server pods. If the server pod goes down, the client can retry with the same ClusterIP.kind: ServiceapiVersion: v1metadata: name: my-service namespace: defaultspec: selector: app: my-app ports: - port: 80 # for clients - targetPort: 9376 # for backend podsKubeProxy runs on every node in the cluster. It uses iptables, IPVS or userspace options to proxy traffic from pods.KubeProxy control plane accumulate changes to Endpoints and Services, then updates rules in the node. In the data plane, the sending KubeProxy recognizes ClusterIP/port and rewrites packets to the new destination (DNAT). The recipient KubeProxy un-DNAT the packets.To disambiguate, CNI ensures the Pod IPs work. KubeProxy redirects ClusterIP to Pod IP before sending over the network.Endpoint API ImplementationEndpoint objects are a list of IPs behind a Service. An Endpoint Controller manages them automatically.When a Service object is created, an Endpoint object is created that has a mapping of service name to pod addresses and ports. This object is fed into the rest of the system such as KubeDNS and KubeProxy.Ingress API ImplementationHTTP proxy and L7 routing rules that targets a service for each rule. Kubernetes define the API but implementations are all third party.Unlike the Ingress API, Service-type load balancers only work at L4 level.Ingress { hostname: foo.com paths: - path: /foo service: foo-svc - path: /bar service: bar-svc}Deep Dive: NodeLocal DNSDNS resource cost is high. There are more microservice addressed by names and more application libraries tending to use DNS names. The solution is to run a DNS cache on every node.The NodeLocal DNS implementation is deployed on each node as a Daemonset.Dummy network interface is created that binds to ClusterIP address of KubeDNS. Linux NOTRACK target is added with KubeDNS ClusterIP before any KubeProxy rules. This ensures that NodeLocal DNS can process the packets without them reaching KubeProxy.A watcher process removes the NOTRACK entries in the event that NodeLocalDNS fails. This defaults back to the original KubeDNS infrastructure.Deep Dive: EndpointSliceEndpoint objects are stored in the Etcd database. When one pod IP changes, the entire object has to be redistributed to all the KubeProxy. If Endpoint objects are large, it may also hit the maximum storage limit in Etcd.The solution is to represent one original Endpoint object with a set of EndpointSlice objects. A single update to pod IP will only require redistribution of one EndpointSlice object.The EndpointSlice controller slices from a Service object to create EndpointSlice objects.Interesting optimization problem: Keep number of slices low. Minimize changes to slices per update. Keep the amount of data sent low." }, { "title": "CMU MCDS Course Reviews", "url": "/posts/cmu-mcds-course-reviews/", "categories": "School", "tags": "CMU", "date": "2024-01-19 02:00:00 +0000", "snippet": "Over the last 1.5 years, I studied Master of Computational Data Science (MCDS) at Carnegie Mellon University. Inspired by blogs such as fanpu.io and wanshenl.me, I am going to outline my experience...", "content": "Over the last 1.5 years, I studied Master of Computational Data Science (MCDS) at Carnegie Mellon University. Inspired by blogs such as fanpu.io and wanshenl.me, I am going to outline my experiences for each course to hopefully help future students.Summer 2022 15513 - Introduction to Computer SystemsAs someone who does not have a systems background, this course was transformational. It started with the foundation of how hardware instructions work and build up to computer memory, processes and threads. I took it with a full time internship but fortunately the class was asynchronous. I watched the lectures on weekday nights and worked on the projects on the weekends. The workload was manageable except for weeks of Malloc Lab which were heavier. For graduate students, this course was only 6 units so the tuition fee was only half the normal cost. Doing well in this course is compulsory for MCDS students who wish to choose the Systems concentration. Notable projects include: Bomb lab: Reading through assembly code to decode its purpose. Malloc lab: Building a memory allocator where throughput and memory fragmentation is graded. Tsh lab: Building a simple shell to manage processes. Proxy lab: Web server to serve and cache static files concurrently. Fall 2022 10601 - Introduction to Machine LearningThis course focused on how each type of machine learning models work from the ground-up, aka all mathematics. There were 3 written exams and homeworks with written and programming components. I spent more effort on the written components and preparing for the exams. The programming contained straightforward instructions and were not too complex. For students with more machine learning background, the course code for the PhD level version is 10701. 11631 - Data Science SeminarA compulsary course for MCDS students. The focus was on reading and writing scientific papers. Every week students critique data science papers and submit paper summaries. There was also a component where students read the papers of the senior MCDS batch and critique them. Personally, I felt that the selected readings were too focused on the human-computer-interaction concentration of MCDS and did not include the other two concentrations (analytics and systems). The workload was the lowest compared to the other courses in the semester. 11637 - Foundation of Computational Data ScienceThere were some overlaps with 10601 in terms of the machine learning content. However, this course included the surrounding infrastructure around a machine learning model such as data processing and deploying the models. The course was asynchronous where students read the lectures on their own time which provided flexibility in terms of scheduling. The projects were time-consuming but not as technically complex as the systems related courses. Personally, I did not learn much more than the basic ML engineering experiences that I already had from internships. 15645 - Database SystemsThe most exciting course for the semester. I got to build components of the Bustub database system. The professor, Andy Pavlo, was also very engaging which led to a lot of participation during class. The contents ranged from the low level concepts such as how the database interacts with the disk and optimizing query plans to higher level concepts such as how distributed databases work. The projects were in C++ and emphasized on multi-threading support. Optimizations could lead to higher ranks on the leaderboard and extra credits. As someone without any C++ experience, this course had the most difficult projects for the semester but after investing significant amount of time, it was still possible to achieve top 10 leaderboard ranking. My lecture notes can be found here. Notable projects include: Storage Index: B+ tree data structure with concurrent protocols. Query Execution: Iterator query processing model and query plan optimization. Concurrency Control: Lock-based concurrency control with different isolation levels. Spring 2023 05839 - Interactive Data ScienceThe course was about visualization techniques in data science. The students have to read the lecture slides before attending the lectures afterwards. Contents included drawing charts, interpreting them and using frameworks to deploy them. There was also a final project where students could choose almost any dataset and draw visualizations in Streamlit. In my opinion, this was not sufficiently challenging for a graduate level course that was compulsory for MCDS students. A more appropriate audience would be freshmen students with not a lot of programming background. 11634 - Capstone Planning SeminarMCDS students would have to work on a capstone project spanning two semesters. This course covered how to produce documentations for a data science project and also helped the students to match with project mentors. Generally, the contents provided a good structure to plan out the capstone projects from scratch. However, a lot of documentation was required for submission with tight deadlines and this took some time away from the actual discussions of the projects. 15640 - Distributed SystemsIt was an introductory course on how to design and implement scalable distributed systems. The Spring version was taught in C and Java unlike the Fall version which was in Golang. I was fortunate to be taught by Professor Satya who implemented Andrew File System. The projects included implementing RPC, a distributed caching system, a scalable web service and the two-phased commit protocol. Some ideas overlap with 15645 such as transactions and logging. The projects were easier too. I spent about half the time on each project compared to 15645. My lecture notes can be found here. 15719 - Advanced Cloud ComputingRather than how to use cloud technologies, the course covered how each type is implemented. I really liked the course as now I have a clearer understanding on the abstractions behind services on the cloud providers. For a better understanding, I really recommend going through the original papers provided as the compulsory reading materials as the lecture notes condenses them too much. It would also be helpful for system design interviews as the papers explains the complexities behind each system very well. In terms of assessment, the exams tested the application of each concept in scenarios. The projects on Spark were challenging but the other projects on Terraform and Kubernetes were easy for a “systems course”. For MCDS students who took 15513, the course can be taken to replace 15619 - Cloud Computing.Fall 2023 11632 / 11635 - Data Science CapstoneThe time was meant for implementing the proposals that were submitted during 11634. Weekly standups had to be submitted in the form of videos and google forms. Every few weeks, the teams had to present the updates to Professor Nyberg. One recommendation is to be very particular about planning, especially to have roadmaps with timelines and task assignments. The workload varies extremely widely across each team. The same mentors are usually involved with multiple batches of MCDS students so I would highly recommend talking to the previous teams beforehand. 15641 - Computer NetworksOne of my favourite courses in CMU. Professor Sherry is a very friendly and engaging person which led to a lot of participation in class. The contents included a deep dive into each layer of TCP-IP model and network security. Even though I took a networking class before during undergraduate, I still found the projects exciting to work on. People could choose to work in pairs, but it was still very manageable to work on them alone. It was more challenging than 15640 but less than 15645. Projects were in C and included: Mixnet: Distributed spanning tree with link state routing to transport frames across nodes. TCP: Implementing features of TCP over UDP sockets. Writing 3-way handshake, flow control and congestion control from scratch really made the concepts stuck in my head. HTTP: Single-threaded HTTP server with Linux epoll to handle multiple clients. 17663 - Programming Language PragmaticsThe course spent about 60% on formally proving the properties of programming languages and 40% on the compiler implementation. As someone with little theory background, this was the toughest course for the semester. The proofs had to be written in SASyLF which checked the correctness during compilation. This short feedback loop made the learning process easier. The compiler was to be written in OCaml and transforms a subset of TypeScript into WebAssembly code. As a systems student, this component was a lot more engaging to me but I can now also appreciate the importance of formal reasoning. From my impression, it seemed that the other undergraduate students with more theory background found the course relatively easy." }, { "title": "Data Structures Behind Git", "url": "/posts/data-structures-behind-git/", "categories": "Tech", "tags": "Git", "date": "2022-05-13 00:22:00 +0000", "snippet": "The underlying data structure of a Git repository is just a directed acylic graph (DAG). Not only the core idea is simple, the implementation can be easily inspected in the .git directory. Let’s br...", "content": "The underlying data structure of a Git repository is just a directed acylic graph (DAG). Not only the core idea is simple, the implementation can be easily inspected in the .git directory. Let’s break it down.If Git is a graph, what are the “nodes”?There are three types of “nodes”, also known as Git Objects - Blobs, Trees and Commits. The article will run through an example usage of Git so that we can observe how each of them is created.Empty Git repositoryAfter initializing an empty Git repository, the .git directory is shown below. For the rest of the article, our focus will be on the .git/objects directory..|____branches|____config|____description|____HEAD|____hooks| |____applypatch-msg.sample| |____commit-msg.sample| |...|____info| |____exclude|____objects <--- Our focus| |____info| |____pack|____refs| |____heads| |____tagsGit Object - BlobWe will create a new file and add it to staging.echo \"Hello World\" > hello.txtgit add hello.txtNow we can find a new directory and file in .git/objects..|____objects| |____55| | |____7db03de997c86a4a028e1ebd3a1ceb225be238| |____info| |____pack...Note that the concatenation of the directory name and file name is a 20-byte hash digest.If we inspect the file, we will see that it is not a human-readable format.cat .git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238xKOR04bH/IAIThis is because Git stores content in a compressed binary format. If we uncompress with the appropriate algorithm, the contents can be seen as below. This Git Object is indicated to be a 12-byte Blob with data being Hello World\\n.blob 12Hello WorldRemember the 20-byte hash digest? To produce it, Git has actually run SHA-1 on the uncompressed data shown above. In other words, a Blob is simply contents of one file and is identified by SHA-1 hash of its contents.Note that our Blob does not contain any information about the file name hello.txt.Inspecting Git ObjectsConvenient to us, Git provides APIs to inspect the compressed data in Git Objects.# Inspect the type of Git Objectgit cat-file -t 557db# Inspect the content of Git Objectgit cat-file -p 557db# Type of Git Objectblob# Content of Git ObjectHello WorldGit Object - TreeNow that we understand what a Blob is, let’s create a new commit.git commit -m \"First commit\"Looking at .git/objects again, there are two new Git Objects created..|____objects| |____55| | |____7db03de997c86a4a028e1ebd3a1ceb225be238| |____97| | |____b49d4c943e3715fe30f141cc6f27a8548cee0e <-- New file 1| |____c5| | |____5df28adf8320cc4d15637b82e8a0b13422d955 <-- New file 2...If we inspect 97b49 with cat-file, the Git Object type and its contents are shown below.# Type of Git Objecttree# Content of Git Object100644 blob 557db03de997c86a4a028e1ebd3a1ceb225be238 hello.txtIt can be seen that this particular Git Object is a Tree. More specifically, it has a pointer to a Blob with hash digest 557db while naming it hello.txt. It also states that the file has a 644 permission.In the example, the Tree has one Blob pointer but in reality it can have multiple Blob pointers and even other Tree pointers. In other words, a Tree simply contains pointers to other Git Objects and is identified by the SHA-1 hash of its contents.Excluding the file names from Blobs is an intentional optimization by Git. If there are two files with duplicate content but different names, Git’s representation will be multiple pointers pointing to the same Blob.Git Object - CommitThere is still one more Git Object. If we inspect c55df with cat-file, the results are shown below.# Type of Git Objectcommit# Content of Git Objecttree 97b49d4c943e3715fe30f141cc6f27a8548cee0eauthor yarkhinephyo <yarkhinephyo@gmail.com> 1652402598 +0800committer yarkhinephyo <yarkhinephyo@gmail.com> 1652402598 +0800It can be seen that a Commit contains a pointer to a single Tree encompassing the contents of the commit and other bookkeeping details (such as author and timestamp). Similar to other Git Objects, a Commit is also identified by the SHA-1 hash of its contents.Putting it together - Directed acyclic graphecho \"Hello World\" > hello.txtgit add hello.txtgit commit \"First commit\"Considering all the pointers, the Git Objects resulting from these commands can be represented as a graph.DAG after first commit - Diagram by authorThis is essentially the data structure powering Git repositories, stored right in the .git/objects directory as compressed binary files.Adding a new commitLet’s see what happens with a new commit. We will modify hello.txt and add new_file.txt in the second commit.echo \"Bye\" >> hello.txtecho \"I love git\" > new_file.txtgit add hello.txt new_file.txtgit commit -m \"Second commit\"If we look at the .git/objects directory and inspect the new Git Objects with cat-file tool, it is possible to manually update the graph.DAG after second commit - Diagram by authorThere are two interesting observations.First, the new Commit has a pointer to the parent Commit in its contents. This means that whatever is in the ancestor Commits affects the SHA-1 calculation of the new Commit. Therefore, as long as we have the SHA-1 calculation of the latest commit, the integrity of Git history can be verified.Second, a new Blob is created after hello.txt has been modified and a new Tree stores a pointer to it. This is because Git Objects are immutable. Whatever changes made in a new commit would not mutate the previous Git Objects and modify the SHA-1 calculations.Merkle DAGThis DAG where each node has an identifier resulting from hashing its contents is called Merkel DAG. This data structure also plays an important role in Web3 applications.Resources Advanced Git: Graphs, Hashes and Compression by InfoQ Git Merkle DAG by John Williams" }, { "title": "JVM Garbage Collectors - Serial, Parallel, CMS", "url": "/posts/jvm-garbage-collectors/", "categories": "Tech", "tags": "Java", "date": "2022-05-06 09:00:00 +0000", "snippet": "Depending on the resources available and the performance metric of an application, different Garbage Collectors (GC) should be considered for the underlying Java Virtual Machine. This post explains...", "content": "Depending on the resources available and the performance metric of an application, different Garbage Collectors (GC) should be considered for the underlying Java Virtual Machine. This post explains the main idea behind the garbage collection process in JVM and summarizes the pros and cons of Serial GC, Parallel GC and Concurrent-Mark-Sweep GC.Garbage-First GC (G1) is out-of-scope for this post as it works very differently from the other algorithms (and I still have not wrap my head around it). This post also assumes familiarity with heap memory.Garbage Collection AlgorithmsThese symbols will be used to illustrate the memory allocation in heap.o - unvisitedx - visited<empty> - freeMark-Sweep: The objects in the heap that can be reached from root nodes (such as stack references) are marked as visited. While sweeping the memory, the regions occupied by the unvisited objects are updated to be free. As there are likely to be less contiguous regions after a collection, external fragmentation is likely to occur.Marked | x |o| x | o |x|Sweeped | x | | x | |x|Mark-Sweep-Compact: After marking, the visited objects are identified and compacted to the beginning of the memory region. This solves the external fragmentation issue, but more time is required as objects have to be moved and references have to be updated accordingly.Marked | x |o| x | o |x|Sweeped | x | | x | |x|Compacted | x | x |x| |Mark-Copy: After marking, the visited objects are relocated to another region. This accomplishes compaction of allocated memory at the same step. However, the disadvantage is that there is a need to maintain one more memory region.Marked | x |o| x | o |x| |Copied | | x | x |x| |During parts of a garbage collection, all application threads may be suspended. This is called stop-the-world pause. Long pauses are especially undesirable in interactive applications.Generational Garbage Collection in JVMThe Weak Generational Hypothesis states that most objects die young.In JVM, heap memory is divided into two regions - Young Generation and Old Generation. Newly created objects are stored in the Young Generation and the older ones are promoted to the Old Generation. With this separation, GC can work more often in a smaller region where dead objects are more likely to be found.<- Young Generation ->+--------------------+--------------------+| Eden Space | |+----------+---------+ Old Generation || S0 | S1 | |+----------+---------+--------------------+Young Generation: The region is divided into Eden Space where new objects are created, and S0/S1 Space where the visited objects from each garbage collection can be copied to. Naturally, Mark-Copy algorithm is used.Old Generation: As there is no delimited region for the visited objects to be copied to. Only Mark-Sweep and Mark-Sweep-Compact algorithms can be used.Serial GCJVM option: -XX:+UseSerialGCThis option uses Mark-Copy for the Young Generation and Mark-Sweep-Compact for the Old Generation. Both of the collectors are single-threaded. Without leveraging multiple cores present in modern processors, the stop-the-world pauses are longer. The advantage is that there is less resource overhead compared to other options.Parallel GCJVM option: -XX:+UseParallelGC -XX:+UseParallelOldGCSimilary to Serial GC, this option uses Mark-Copy for the Young Generation and Mark-Sweep-Compact for the Old Generation. Unlike Serial GC, multiple threads are run for the respective algorithms. As less time is spent on garbage collection, there is higher throughput for the application.Concurrent-Mark-Sweep (CMS) GCJVM option: -XX:+UseParNewGC -XX:+UseConcMarkSweepGCFor the Young Generation, this option is the same as Parallel GC. For the Old Generation, this option runs most of the job in Mark-Sweep concurrently with the application. This means that the application threads continue running during some parts of the garbage collection. Hence this option is less affected by stop-the-world pauses compared to the other two, making it preferred for interactive applications.As at least one thread is used for garbage collection all the time, the application has lower throughput. Without compaction, external fragmentation may also occur. When this happens, there is a fallback with Serial GC but it is very time-consuming.Resources Java Garbage Collection by Plumbr Garbage Collection in Java by Ranjith" }, { "title": "Solving HTTP/1 Problems With HTTP/2", "url": "/posts/solving-http1-problems-with-http2/", "categories": "Tech", "tags": "Networking", "date": "2022-05-02 03:09:00 +0000", "snippet": "HTTP/2 has made our applications faster and more robust by providing protocol enhancements over HTTP/1. This post only focuses on the major pain points of HTTP/1 and how the new protocol has been e...", "content": "HTTP/2 has made our applications faster and more robust by providing protocol enhancements over HTTP/1. This post only focuses on the major pain points of HTTP/1 and how the new protocol has been engineered to overcome them. It is assumed that the reader is familiar with how HTTP/1 works.Problems with HTTP/1Head-of-line blocking: Browsers typically only allow 6 parallel TCP connections per domain. If the initial requests are not complete, the subsequent requests will be blocked.Unnecessary resource utilization: With HTTP/1, a single connection is created for every request, even if multiple requests are directed to the same server. As the server has to maintain states for each connection, there is an inefficient utilization of resources.Overhead of headers: Headers in HTTP/1 are in the human-readable text format instead of being encoded to be more space-efficient. As there are numerous headers in complex HTTP requests and responses, it can become a significant overhead.Binary Framing “Layer”In HTTP/2, there is a binary framing layer that exists between the original HTTP API exposed to the applications and the transport layer. The rest of TCP/IP stack is unaffected. As long as both client and server implements HTTP/2, the applications will also continue to function as usual.Request and response multiplexingIn HTTP/1, each HTTP request creates a separate TCP connection as shown below.| - Application - | - Transport - | request_1 --> connection_1 request_2 --> connection_2In HTTP/2, the binary framing layer breaks down each request into units called frames. These frames are interleaved and sent to the transport layer as application data. The transport layer is oblivious to the process and carries on with its own responsibilities. At the server’s end, the binary framing layer reconstruct the requests from the frames.| - Application ---------------- | - Transport - | | Binary Framing | request_1 ---> frames ---> connection_1 request_2 -/ HTTP/2 FramesTo be specific, each HTTP request is broken down into HEADERS frame and DATA frame/s. The names are self-explanatory. HEADERS frame include HTTP headers and DATA frame/s include the body.The diagram below shows the structure of a frame.+-----------------------------------------------+| Length (24) |+---------------+---------------+---------------+| Type (8) | Flags (8) |+-+-------------+---------------+-------------------------------+|R| Stream Identifier (31) |+=+=============================================================+| Frame Payload (0...) ...+---------------------------------------------------------------+HTTP/2 StreamsNotice that each HTTP/2 frame has an associated stream identifier which identifies each bidirectional flow of bytes. For example, all the frames in a single request-response exchange will have the same stream identifier.This means that when frames from different requests are interleaved with one another, the receiving binary framing layer can reconstruct them back into independent streams.Interleaving frames with different stream identifiersIn other words, the hierarchical relationship between connection, stream and frame can be represented as shown below.Logical relationship between frames and streamsHeader compressionAside from multiplexing HTTP requests over a single TCP connection, HTTP/2 also provides a mechanism for header compression. Instead of transporting textual data, both server and client maintain identical lookup tables to remember the headers that have been used. In the subsequent communication, only the pointers into the lookup table are sent over the network. Tests have show that on average, the header size is reduced around 85%-88%.LimitationHTTP/2 solves the head-of-line blocking problem from parallel TCP connections. However, this creates another problem at the TCP level. Due to the nature of TCP implementation, one lost packet can make all the streams wait until the packet is re-transmitted and received.HTTP/3 addresses this issue by communicating over QUIC (TCP-like protocol over UDP) instead of TCP.Resources A Brief History of SPDY and HTTP/2 by Ilya and Surma HTTP2 by High Performance Browser Networking" }, { "title": "Choosing Between UTF Encodings", "url": "/posts/choosing-between-utf-encodings/", "categories": "Tech", "tags": "Data-Engineering", "date": "2022-04-27 04:07:00 +0000", "snippet": "Have you occasionally chosen a character encoding such as UTF-8 during reading and writing files while wondering its purpose? I have! This post explains various UTF (Unicode Transformation Format) ...", "content": "Have you occasionally chosen a character encoding such as UTF-8 during reading and writing files while wondering its purpose? I have! This post explains various UTF (Unicode Transformation Format) algorithms such as UTF-8, UTF-16, UTF32 and how to choose between them.Unicode character setUnicode character set defines a unique number for almost all characters used in modern texts today. The standard ensures that given a number, also known as a code point, different softwares will decode it as the same character. Character Decimal Representation Code (Read Hexadecimal) A 65 U+41 B 66 U+42 我 25105 U+6211 😋 128523 U+1F60B The Unicode character set ranges from 0x0 to 0x10FFFF (21-bits range).UTF-32UTF stands for Unicode Transformation Format. It encodes integer code points into byte representations on a machine. For example, if 4 bytes are allocated to a character at each time, four-byte representations are shown below. Character Byte Representation (Read Hexadecimal) A 0x00000041 B 0x00000042 我 0x00006211 😋 0x0001F60B This is exactly what UTF-32 does. It pads every code point with zeros into 32 bits. This is more than sufficient for the 21-bits range of Unicode character set.However, the approach is space-inefficient. For example, if there are only English letters in a document (U+41 to U+7A), only one byte is necessary to represent each character. However, UTF-32 will still pad with three bytes to form four-byte representations, resulting as 300% increase in storage.UTF-16UTF-16 mitigates the problem by representing U+0 to U+FFFF with two bytes and U+10000 to U+10FFFF with four bytes.Characters from almost all modern languages are found in the first 216 code points (See Basic Multilingual Plane). If a document only contains these code points, UTF-16 will mainly use two-byte representations instead, meaning storage is cut by 50% from using UTF-32.To represent larger code points, UTF-16 employs a concept called surrogate pairs. High surrogates are code points from U+D800 to U+DBFF and low surrogates are code points from U+DC00 to U+DFFF. There are no character mappings at these ranges and they only have meaningful representations when paired. The example below may present a clearer picture.High surrogate --> U+D800 to U+DBFF --> 110110 concat with any 10 bitsLow surrogate --> U+DC00 to U+DFFF --> 110111 concat with any 10 bitsCharacter: 😋Unicode : U+1F60BBinary : 0b11111011000001011Binary padded 20-bits: 0b00011111011000001011 <--- A --><--- B --> (10 bits) (10 bits)High surrogate: 110110 concat A = 1101100001111101 (16 bits)Low surrogate : 110111 concat B = 1101111000001011 (16 bits)If a decoder sees a two-byte representation starting with 110110 or 110111 bits, it can infer that this is part of a surrogate pair and immediately identify the other surrogate. The binary representation of the original character can be reconstructed afterwards.UTF-8ASCII characters compose the first 27 code points. Most of the time when coding or writing English articles, you may mostly end up using these characters. As these code points can be represented with one byte, two-byte representations of UTF-16 still results in wasted storage.Depending on the range of Unicode character set, UTF-8 uses one, two, three or four-byte representations. The encoding pseudocode is shown below.if code point < 2^7 # Covers ASCII pad with zeros till 8 bits 1st byte = 8 bitselse if code point < 2^11 # Covers other Latin alphabets pad with zeros till 11 bits # (5 + 6) 1st byte = \"110\" concat 5 bits 2nd byte = \"10\" concat 6 bitselse if code point < 2^16 # Covers Basic Multilingual Plane pad with zeros till 16 bits # (4 + 6 + 6) 1st byte = \"1110\" concat 4 bits 2nd byte = \"10\" concat 6 bits 3rd byte = \"10\" concat 6 bitselse if code point < 2^21 # Covers 21-bit Unicode range pad with zeros till 21 bits # (3 + 6 + 6 + 6) 1st byte = \"11110\" concat 3 bits 2nd byte = \"10\" concat 6 bits 3rd byte = \"10\" concat 6 bits 4rd byte = \"10\" concat 6 bitsAs texts encoded in ASCII never appear as multi-byte sequences, UTF-8 can be used to decode it directly. The backward compatibility is one of the reasons why it has been adopted at a large scale.How to choose between UTF-8, UTF-16, UTF-32If backward compatibility to ASCII is preferred and most characters are English text, UTF-8 is a good choice.If most characters are from non-English languages, UTF-16 is preferred because it uses two-byte representations for Basic Multilingual Plane as compared to UTF-8 which uses three-byte representations.UTF-32 is rarely used but in theory, the fixed-width encoding without transformations allows faster encoding and decoding of characters.Resources UTF-8 vs UTF-16 vs UTF-32 on StackOverflow How UTF-16 encodes 21-bit unicode Unicode Encoding! by EmNudge" }, { "title": "How the Same-Origin Policy Mitigates CSRF", "url": "/posts/how-the-same-origin-policy-mitigates-csrf/", "categories": "Tech", "tags": "Networking", "date": "2022-04-20 01:30:00 +0000", "snippet": "What is CSRF?A cross site request forgery (CSRF) attack occurs when a web browser is tricked into executing an unwanted action in an application that a user is logged in.For example, User A may be ...", "content": "What is CSRF?A cross site request forgery (CSRF) attack occurs when a web browser is tricked into executing an unwanted action in an application that a user is logged in.For example, User A may be logged onto Bank XYZ in the browser which uses cookies for authentication.Let’s say the a transfer request like this -GET http://bank-zyz.com/transfer?from=UserA&to=UserB&amt=100 HTTP/1.1Then a malicious actor can embed a request with similar signatures inside an innocent looking hyperlink.<a href=\"http://bank-xyz.com/transfer?from=UserA&to=MaliciousActor&amt=10000 HTTP/1.1></a>If User A clicks on the hyperlink, the web browser sends the request together with the session cookie.The funds are then unintentionally transferred out of User A’s account.When the same-origin-policy does not help with CSRFThe same-origin policy prevents a page from accessing results of cross domain requests.It prevents a malicious website from accessing another website’s resources such as static files and cookies.Even though the policy prevents cross-origin access of resources, it does not prevent the requests from being sent.In the Bank XYZ example, a GET request with relevant cookies triggers the server to transfer the funds before returning the 200 OK response.As shown in the diagram below, the same-origin policy only prevents the access of resouces, which in this case is reading the HTTP response.Since the request (with cookies) can still be sent, the hyperlink can still trigger the transfer of funds.Image by Author - The policy would only prevent a cross-origin access of HTTP response (Step 3)Note: For more complex HTTP requests, a preflight OPTIONS request is sent beforehand to check for relevant CORS headers.In that scenario, an unexpected cross-origin request will not reach Bank XYZ’s website at all.How the same-origin-policy actually mitigates CSRFTo prevent CSRF, Bank XYZ can generate an unpredictable token for each client which is validated in the subsequent requests.For example, a hidden HTML field can allow the token to be included in subsequent form submissions.<input type=\"hidden\" name=\"csrf-token\" value=\"CIwNZNlR4XbisJF39I8yWnWX9wX4WFoz\" />Other websites running in User A’s browser do not have access to the form field due to the same-origin-policy.So malicious scripts from other origins can no longer make the same request to transfer funds.Resources StackOverFlow on why SOP is not enough not prevent CSRF CSRF by Imperva" }, { "title": "Git Submodules Cheatsheet", "url": "/posts/git-submodules-cheatsheet/", "categories": "Tech", "tags": "Git", "date": "2022-04-16 16:30:00 +0000", "snippet": "Git Submodules allows one Git repository to be a subdirectory of another. I keep forgetting the commands so I have created a 2-minute refresher for my future reference.Adding a submoduleTo add a su...", "content": "Git Submodules allows one Git repository to be a subdirectory of another. I keep forgetting the commands so I have created a 2-minute refresher for my future reference.Adding a submoduleTo add a submodule to a project, run the command as shown below. Git will clone the submodule to the path provided and create a new .gitmodules file to store the information.git submodule add <remote-url> <path-to-module>Note that the <path-to-module> is now tracked by the parent repository as a commit ID instead of a subdirectory of contents. Treat it as a file for all practical purposes.git add <path-to-module> .gitmodulesgit commit -m \"Added submodule\"Pushing an updated submoduleOnly the submodule’s commit ID is inspected by the parent repository. When the submodule’s commit is modified, the parent repository will react similarly to how a file has been modified. Add the modified “file” to staging and commit as usual.git add <path-to-module>git commit -m \"Updated submodule\"Pulling an updated submoduleAfter pulling changes from the parent repository, only the submodule’s tracked commit ID will be updated, not its contents. Manually update the contents of the submodule to synchronize with the updated commit ID.# This updates the commit IDs of submodulesgit pull origin main# Update the contents of the submodulesgit submodule update --init --recursiveCloning a repository containing submodulesAdd a --recursive flag.git clone --recursive <module>Resources Git Tools Submodules by Git Scm" }, { "title": "Primer to Scale-invariant Feature Transform", "url": "/posts/primer-to-sift/", "categories": "Tech", "tags": "Data-Science", "date": "2022-04-13 01:30:00 +0000", "snippet": "Scale-invariant Feature Transform, also known as SIFT, is a method to consistently represent features in an image even under different scales, rotations and lighting conditions. Since the video ser...", "content": "Scale-invariant Feature Transform, also known as SIFT, is a method to consistently represent features in an image even under different scales, rotations and lighting conditions. Since the video series by First Principles of Computer Vision covers the details very well, the post covers mainly my intuition. The topic requires prior knowledge on using Laplacian of Gaussian for edge detection in images.Why extract features?Image by First Principles of Computer VisionConsider the two images. How can the computer recognize that the object in the left is included inside the image on the right? One way is to use template-based matching where the left image is overlapped onto the right. Then some form of similarity measure can be calculated as it is shifted across the right image.Problem: To ensure different scales are accounted for, we would need templates of different sizes. To check for different orientations, we would need a template for every unit of angle. To overcome occlusion, we may even need to split the left image into multiple pieces and check if each of them matches.For the example above, our brains recognize the eye and the faces to locate the book. Our eyes do not scan every pixel, and we are not affected by the differences in scale and rotation. Similarly it will be great if we can 1) extract only interesting features from an image and 2) transform them into representations that are consistent across different scenes.Good requirements for feature representationPoints of Interest: Blob-like features with rich details are preferred over simple corners or edges.Insensitive to Scale: The feature representation should be normalized to its size.Insensitive to Rotation: The feature representation should be able to undo the effects of rotation.Insensitive to Lighting: The feature representation should be consistent under different lighting conditions.Blob detection - Scale-normalized points of interestImage from Princeton CS429 - 1D edge detectionIn traditional edge detection, a Laplacian operator can be applied to an image through convolution. Edges can be identified from the ripples in the response.Image from Princeton CS429 - 1D blob detectionIf multiple edges are at the right distance, there will be a single strong ripple caused by constructive interference. If this response is sufficiently strong, the location is identified as a blob representing a feature. Intuitively, complex features will be chosen compared to simple edges as constructive interferences cannot be produced by single edges.From the same diagram, we can also see that not all collection of edges result in singular ripples with a particular Laplacian operator. By increasing the sigma (σ) of the Laplacian (making the kernel “fatter”), the constructive interference will occur when edges are further apart. If we apply the Laplacian operators many times with varying σ’s, blobs of different scales can be identified each time.Image from Princeton CS429 - Increasing σ to identify larger blobsWait but if the σ is larger, the Laplacian response will be weaker (shown above). Intuitively, if the responses by larger blobs are penalized for their sizes. Does that means the selected features will be mostly tiny?Image from Princeton CS429 - Normalized Laplacian of the Gaussian (NLoG)We solve this by multiplying the Laplacian response with σ2 for normalization. (This works out because the Laplacian is the 2nd Gaussian derivative) Intuitively, this means that the response now only indicates the complexity of the features without any effect from their sizes.3 x 3 x 3 kernels to find local extremasImagine the Laplacian response represented as a matrix with x-y plane for image dimensions and z axis for various σ. We can slide an n x n x n kernel to find the local extremas. The resulting x-y coordinates would represent the centers of the blobs and σ would correspond to their sizes.With this technique, blobs can be extracted to represent complex features with the sizes normalized.Countering the effects of rotationImage from Princeton CS429To assign an orientation to each feature, it can be divided into smaller windows as shown above. Then the pixel gradient for each window can be computed to produce a histogram of gradient directions. The most prominent direction can be assigned as the principle orientation of the feature.Image by AuthorIn the example above, blobs are identified in both images representing the same feature. The black arrows are the principle orientations. After rescaling the blob sizes with the corresponding σ’s, the effect of rotation is eliminated by aligning with respect to the principle orientations.Countering the effects of lighting conditionsImage from Princeton CS429 - Pixels to SIFT descriptorsInstead of comparing each blob directly (pixel-by-pixel), we can produce a unique representation that is invariant to lighting conditions. As shown above, the image can be broken into smaller windows (4 x 4) where each histogram of the gradients is computed. If each histogram only consider 8 directions, there will be 8 dimensions per window. Even with only 16 windows per blob, each feature representation will be of 128 dimensions (16 x 8) which can be robust.These feature representations are known more formally as SIFT descriptors.ConclusionImage from OpenCV documentationFor matching images, SIFT descriptors in two images can be directly compared against one another through similarity measurements. If a large number of them matches, it is likely that the same objects are observed in both images. In practice, nearest neighbor algorithms such as FLANN are used to match the features between images.Resources SIFT Detector by First Principles of Computer Vision Feature Detectors and Descriptors - Princeton CS429" }, { "title": "Intuition Behind the Attention Head of Transformers", "url": "/posts/intuition-behind-the-attention-head/", "categories": "Tech", "tags": "Data-Science", "date": "2022-04-09 06:20:00 +0000", "snippet": "Even as I frequently use transformers for NLP projects, I have struggled with the intuition behind the multi-head attention mechanism outlined in the paper - Attention Is All You Need. This post wi...", "content": "Even as I frequently use transformers for NLP projects, I have struggled with the intuition behind the multi-head attention mechanism outlined in the paper - Attention Is All You Need. This post will act as a memo for my future self.Limitation of only using word embeddingsConsider the sequence of words - pool beats badminton. For the purpose of machine learning tasks, we can use word embeddings to represent each of them. The representation can be a matrix of three word embeddings.If we take a closer look, the word pool has multiple meanings. It can mean a swimming pool, some cue sports or a collection of things such as money. Humans can easily perceive the correct interpretation because of the word badminton. However, the word embedding of pool includes all the possible interpretations learnt from the training corpus.Can we add more context to the embedding representing pool? Optimally, we want it to be “aware” of the word badminton more than the word beats.My intuition behind the self-attention mechanismConsider that matrix A represents the sequence - pool beats badminton. There are three words (rows) and the word embedding has four dimensions (columns). The first dimension represents the concept of sports. Naturally, we expect the words pool and badminton to have more similarity in this dimension.Diagram by AuthorA = np.array([ [0.5, 0.1, 0.1, 0.2], [0.1, 0.5, 0.2, 0.1], [0.5, 0.1, 0.2, 0.1],])If we do a matrix multiplication between A and AT, the resulting matrix will be the dot-product similarities between all possible pairs of words. For example, the word pool is more similar to badminton than the word beats. In other words, this matrix hints that the word badminton should be more important than the word beats when adding more context to the word embedding of pool.Diagram by AuthorA_At = np.matmul(A, A.T)>>> A_Atarray([[0.31, 0.14, 0.3 ], [0.14, 0.31, 0.15], [0.3 , 0.15, 0.31]])By applying the softmax function across each word, we can ensure that these “similarity scores” add up to 1.0.The last step is to do another matrix multiplication with matrix A. In a way, this step consolidates the contexts of the entire sequence to each embedding in an “intelligent” manner. In the example below, both embeddings of beats and badminton are added to pool but with different weights depending on their similarities with pool.Diagram by Authoroutput = np.round( np.matmul(softmax(A_At, axis=1), A), 2)>>> outputarray([[0.38, 0.22, 0.16, 0.14], [0.35, 0.25, 0.17, 0.13], [0.38, 0.22, 0.17, 0.13]])Notice that the output matrix has the same dimensions (3 x 4) as the original input A. The intuition is that each word vector is now enriched with more information. This is the gist of the self-attention mechanism.Scaled Dot-Product AttentionThe picture below shows the Scaled Dot-Product Attention from the paper. The core operations are the same as the example we explored. Notice that scaling is added before softmax to ensure stable gradients, and there is an optional masking operation. Inputs are also termed as Q, K and V.Image taken from Attention Is All You Need paperThe Scaled Dot-Product Attention can be represented as attention(Q, K, V) function.Diagram by Frank Odom on MediumAdding trainable weights with linear layersThe initial example that we use can be represented as attention(A, A, A), where matrix A contains the word embeddings of pool, beats and badminton. So far there are no weights involved. We can make a simple adjustment to add trainable parameters.Imagine we have (m x m) matrices MQ, MK and MV where m matches the dimension of word embeddings in A. Instead of passing matrix A directly to the function, we can calculate Q = A MQ, K = A MK and V = A MV which will be the same sizes as A. Then we apply attention(Q, K, V) afterwards. In neural network, this is akin to adding a linear layer before each input into the Scaled Dot-Product Attention.To complete the Single-Head Attention mechanism, we just need to add another linear layer after the output from the Scaled Dot-Product Attention. The idea of expanding to the Multi-Head Attention in the paper is relatively simpler to grasp.Diagram by Frank Odom on MediumResources Series on Attention by Rasa Algorithm Whiteboard The Illustrated Transformer by Jay Alammer" }, { "title": "Quick History of JavaScript Module Ecosystem", "url": "/posts/javascript-module-ecosystem/", "categories": "Tech", "tags": "JavaScript", "date": "2022-04-07 03:02:00 +0000", "snippet": "IIFE - Initial Concept of JS ModulesImmediately-invoked Function Expression are anonymous functions that wrap around code blocks to be imported. In the example below, the inner function sayHi() can...", "content": "IIFE - Initial Concept of JS ModulesImmediately-invoked Function Expression are anonymous functions that wrap around code blocks to be imported. In the example below, the inner function sayHi() cannot be accessed outside the anonymous function. The anonymous function itself also does not have a name so it does not pollute the global scope.// script1.js(function () { var userName = \"Steve\"; function sayHi(name) { console.log(\"Hi \" + name); } sayHi(userName);})();If this script is included as shown below, no variable name collision can occur with other scripts such as script2.js.<!DOCTYPE html><html> <head> <title>JavaScript Demo</title> <script src=\"script1.js\"></script> <script src=\"script2.js\"></script> </head> <body> <h1>IIFE Demo</h1> </body></html>Problems with IIFEWhat if script2.js wants to use the sayHi() function defined in script1.js? We can pass a common global variable through the two IIFE modules as shown below.// script1.js(function (window) { function sayHi(name) { console.log(\"Hi \" + name); } window.script1 = { sayHi };})(window);// script2.js(function (window) { function sayHiBye(name) { window.script1.sayHi(name); console.log(\"Bye \" + name); } var userName = \"Jenny\"; sayHiBye(userName);})(window);This solves the immediate problem, but generates other issues.If we reorder script1.js and script2.js, the code will break as the window object will not have the script1 object by the time script2.js starts to load.There is also the problem of what common variable to pass between the two IIFE. One company may use the window object but another may create a new app object in the global scope. No strict standards means incompatiblity issues.CommonJS - Solving the problems of IIFECommonJS is a series of specifications for development of JavaScript applications in non-browser environments. One of the specifications is the API for importing and exporting of modules. This is where require() and module.exports are introduced.There is no more need for passing around a global variable or wrapping an anonymous function around every code blocks for export.// script1.jsfunction sayHi(name) { console.log(\"Hi \" + name);}module.exports.sayHi = sayHi;// script2.jsscript1 = require(\"./script1.js\");function sayHiBye(name) { script1.sayHi(name); console.log(\"Bye \" + name);}var userName = \"Jenny\";sayHiBye(userName);However, CommonJS was not meant for the browser environment. The specifications also do not support asychronous loading of the modules which is important in the browser environment for the user experience.Module Bundler - CommonJS style modules in the BrowserModule bundlers such as Webpack solves the incompatibility problem by bundling CommonJS modules for usage in the browser. The modules are loaded into a single bundle.js file such that individual dependencies are satisfied, which can be loaded onto the page with the a single <script> tag.For the example above, webpack can produce a single bundle.js with script2.js as an entry. The bundle will include script1.js first as it understands the dependency graph. By including the bundle.js into HTML as shown below, the abovementioned problems with CommonJS are fixed.<!DOCTYPE html><html> <head> <title>JavaScript Demo</title> <script src=\"bundle.js\"></script> </head> <body> <h1>Webpack Demo</h1> </body></html>ES6 - Module system as part of JavaScript standardES6 is a JavaScript standard introduced in 2015 that finally introduced a module system for JavaScript in the browsers. ES6 modules utilize import and export keywords. Unlike CommonJS, webpack is not necessary for browser compatibility. We only need to add a type=\"module\" attribute inside the HTML <script> tag and everything will work out of the box.<!DOCTYPE html><html> <head> <title>JavaScript Demo</title> <script type=\"module\" src=\"script2.js\"></script> </head> <body> <h1>ES6 Demo</h1> </body></html>// script1.jsfunction sayHi(name) { console.log(\"Hi \" + name);}export default { sayHi };// script2.jsimport script1 from './script1.js';function sayHiBye(name) { script1.sayHi(name); console.log(\"Bye \" + name);}var userName = \"Jenny\";sayHiBye(userName);Why are bundlers still used for browser scripts?Backward Compatibility: ES6 modules are not recognized in the older versions of the browsers. Bundlers allow developers to work with the more modern ES6 syntax while the code remains compatible with older browsers.Size Reduction: Minifying code with bundlers reduces file sizes which will lead to faster page loads.Code Splitting: Bundlers can split code into chunks which can then be loaded on demand or in parallel.Caching Support: Webpack can be configured to name the bundles with the hash of their contents. Browsers will only fetch scripts from the server if the hashes no longer match.Resources JavaScript Modules by uidotdev ES6 Modules in the Browser by David Gilbertson JavaScript Module Systems Showdown by Auth0" }, { "title": "Speed up Python applications With Ctypes Library", "url": "/posts/speed-up-python-with-ctypes/", "categories": "Tech", "tags": "Python, C, Concurrency", "date": "2022-04-05 02:00:00 +0000", "snippet": "There are multiple ways to speed up computations in Python. The cython language compiles Python-like syntax into CPython extensions. Libraries such as numpy provides methods to manipulate large arr...", "content": "There are multiple ways to speed up computations in Python. The cython language compiles Python-like syntax into CPython extensions. Libraries such as numpy provides methods to manipulate large arrays and matrices efficiently with underlying C data structures.In this post, I will be discussing the ctypes module. It provides C-compatible data types to so that Python functions can use C-compiled shared libraries. Therefore, we can offload computationally intensive modules of a Python application into C where the developers will have more fine-grained control. To my surprise, this comes as part of the Python standard library, so no external dependencies are required!Code to optimize - prime number checkerI have created a sample program that we can speed up afterwards using the ctypes module. The num_primes() calculates the total number of primes in a list by looping through each item.# prime.pydef is_prime(num: int): for i in range(2, int(num**(0.5))): if num % i == 0: return 1 return 0def num_primes(num_list: List[int]): count = 0 for num in num_list: count += is_prime(num) return countLet’s see the number of primes in a list of 1 million integers. Note that we use consecutive numbers for the example but it does not have to be.from prime import num_primesMAX_NUM = 1000000num_list = list(range(MAX_NUM))def timeit_function(): return num_primes(num_list)print(timeit_function())It takes around 3.4 seconds to run. How can we speed this up?>>> python -m timeit -n 5 -s 'import test_python as t' 't.timeit_function()'Primes: 9212955 loops, best of 5: 3.4 sec per loopWhy Python threading module does not workAs Python has a threading module, one idea is to parallalize calculations across the entire list by using multiple threads. However, this is not possible due to Python’s Global Interpreter Lock (GIL), which prevents multiple threads in a process from executing Python bytecode at the same time. Hence for non-I/O operations, there will not be any speed up.Rewriting prime checker in CThe prime checker is reimplemented in C as shown below, then compiled it into a shared library prime.so. Note that the program logic is exactly the same.// prime.c#include <stdio.h>#include <math.h>int is_prime(int num) { for (int i=2; i<(int)sqrt(num); i++) { if (num % i == 0) return 1; } return 0;} int num_primes(int arrSize, int *numArr) { int count = 0; for (int i=0; i<arrSize; i++) count += is_prime(numArr[i]); return count;}Calling the C-compiled prime checker with ctypesThe ctypes library provides C-compatible data types in Python. All we need to do is load the shared library with CDLL() API and then declare the parameters/ return types accordingly with argtypes and restype attributes.from ctypes import *# Load the shared librarylib = CDLL(\"./libprime.so\")# Declare the return data as 32-bit intlib.num_primes.restype = c_int32# Declare the arguments as a 32-bit int and a pointer for 32-bit int (for list)lib.num_primes.argtypes = [c_int32, POINTER(c_int32)]Afterwards, the num_primes() in the shared library can be called! Note that the num_list has to be converted from Python list into a contiguous array of C with a method provided by ctypes.MAX_NUM = 1000000num_list = list(range(MAX_NUM))def timeit_function(): # num_list is converted into an integer array of size MAX_NUM return lib.num_primes(MAX_NUM, (c_int32 * MAX_NUM)(*num_list))print(f\"Primes: {timeit_function()}\")For the same input of 1 million integers, the speed up is significant just by offloading the same program logic to C code. It makes sense because contiguous arrays in C can leverage caching mechanisms better than lists in Python.>>> python -m timeit -n 5 -s 'import test_ctypes as t' 't.timeit_function()'Primes: 9212955 loops, best of 5: 482 msec per loopMultithreading in the C shared library with POSIX pthreadsThere is one more benefit of offloading the work to C. Since the shared library is not under Python’s GIL, we can now use multithreading in C to parallelize the computations!In the code below, the integer array is split evenly into 4 subarrays and 4 threads are spawned with POSIX pthreads to do parallel work. Each thread runs thread_function() to check the numbers in the array without any overlap between threads. The counts of prime numbers are added into countByThreads array which are summed up after the child threads have terminated.#define NUM_THREADS 4 // 4 threads used// Global variables for spawn threads to accessint *gArrSize = 0; // Ptr for array sizeint *gNumArr = 0; // Ptr for input arrayint countByThreads[NUM_THREADS] = { 0 }; // Prime counts of each threadpthread_t tids[NUM_THREADS] = { 0 }; // IDs of each thread// Function run by each threadvoid *thread_function(void *vargp) { // Each thread has a different offset int offset = (*(int*) vargp); int count = 0; // Split the array items evenly across threads for (int i=offset; i < *gArrSize; i+=NUM_THREADS) count += is_prime(gNumArr[i]); countByThreads[offset] += count; free(vargp);}int num_primes(int arrSize, int *numArr) { gArrSize = &arrSize; gNumArr = numArr; for(int i=0; i < NUM_THREADS; i++) { int *offset = (int*) malloc(sizeof(int)); *offset = i; if(pthread_create(&tids[i], NULL, thread_function, (void *) offset) == -1) exit(1); } int count = 0; for(int i=0; i < NUM_THREADS; i++) { if(pthread_join(tids[i], NULL) == -1) exit(1); // Combine counts from each thread after termination count += countByThreads[i]; countByThreads[i] = 0; } return count;}We have further sped up the code execution although there is an additional overhead of managing threads.>>> python -m timeit -n 5 -s 'import test_ctypes_pthread as t' 't.timeit_function()'Primes: 9212955 loops, best of 5: 322 msec per loopCalling the C-compiled prime checker with Python threadingRemember the threading module in Python just now? Another neat thing about ctypes is that the program releases the GIL as long as the execution is inside the C-compiled shared library. So instead of POSIX pthreads in C, we can generate the threads with threading instead!from ctypes import *# Load the shared librarylib = CDLL(\"./libprime.so\")# Declare the return data as 32-bit integerlib.num_primes.restype = c_int32# Declare the arguments as a 32-bit integer & a pointer for 32-bit integer (for list)lib.num_primes.argtypes = [c_int32, POINTER(c_int32)]Afterwards, the num_primes() in the shared library can be called! Note that the num_list has to be converted from Python list into a contiguous array with a method provided by ctypes.MAX_NUM = 1000000NUM_THREADS = 4# Prime counts per threadcount_list = [0 for _ in range(NUM_THREADS)]# One list of numbers for each threadnum_list_list = []# Split the list for multiple threadsfor i in range(NUM_THREADS): num_list = list(range(i, MAX_NUM, NUM_THREADS)) num_list_list.append(num_list)# Function run by each threaddef thread_function(i, num_list, count_list): len_num_list = len(num_list) count_list[i] = lib.num_primes(len_num_list, (c_int32 * len_num_list)(*num_list))def timeit_function(): threads = [] for i in range(NUM_THREADS): t = threading.Thread(target=thread_function, args=(i, num_list_list[i], count_list)) t.start() threads.append(t) for thread in threads: thread.join() return sum(count_list) # Combine counts from each thread print(f\"Primes: {timeit_function()}\")For this example, the speed up is comparable to using pthreads.>>> python -m timeit -n 5 -s 'import test_ctypes_threading as t' 't.timeit_function()'Primes: 9212955 loops, best of 5: 313 msec per loopThe code demonstrations can be found here.Resources Ctypes Made Easy by Dublin User Group Bypassing GIL with ctypes by Christopher Swenson Python ctypes documentation" }, { "title": "Generators - The Beginning of Asynchronicity in Python", "url": "/posts/async-io-event-loop/", "categories": "Tech", "tags": "Python, Concurrency", "date": "2022-04-03 09:00:00 +0000", "snippet": "If you have worked with asynchronous programming in Python, you may have used the async and await keywords before. It turns out that Python Generators are actually the building blocks of these abst...", "content": "If you have worked with asynchronous programming in Python, you may have used the async and await keywords before. It turns out that Python Generators are actually the building blocks of these abstractions. This article explain their relationship in a greater detail.CoroutinesFor single-threaded asynchronous programming to work in Python, we need a mechanism to “pause” function calls. For example if a particular function involves fetching something from a database, we would like to “pause” the function’s execution and schedule something else until the response is received. However in traditional Python functions, the return keyword frees up the internal state at the end of invocation…It turns out that generators in Python can achieve similar purpose! With generators, the yield keyword gives up the control of the thread while the internal state is saved until the next invocation. So we can do some multitasking with a scheduler as shown below.def gen_one(): print(\"Gen one doing some work\") yield print(\"Gen one doing more work\") yielddef gen_two(): print(\"Gen two doing some work\") yield print(\"Gen two doing more work\") yielddef scheduler(): g1 = gen_one() g2 = gen_two() next(g1) next(g2) next(g1) next(g2)>>> scheduler()Gen one doing some workGen two doing some workGen one doing more workGen two doing more workCoroutine is the term for suspendable functions. As generators cannot take in values like normal functions, new methods are introduced in PEP 342 including .send() that allows passing of parameters (and also .throw() and .close()).def coroutine_one(): print(\"Coroutine one doing some work\") data = (yield) print(f\"Received data: {data}\") print(\"Coroutine one doing more work\") yieldcor1 = coroutine_one()cor1.send(None)cor1.send(\"lorem ipsum\")Coroutine one doing some workReceived data: lorem ipsumCoroutine one doing more workLet’s refer to generators as coroutines from now.Nested coroutinesAnother problem we have is that nested coroutines would not work with current syntax. As shown below, how will coroutine_three() call coroutine_one() and coroutine_two()? It is just a function that has two coroutine objects but has no ability to schedule them!def coroutine_one(): print(\"Coroutine one doing some work\") yield print(\"Coroutine one doing more work\") yielddef coroutine_two(): print(\"Coroutine two doing some work\") yield print(\"Coroutine two doing more work\") yield# Will not work as intendeddef coroutine_three(): coroutine_one() coroutine_two()To solve this, PEP 380 introduces the yield from operator. This allows a section of code containing yield to be factored out and placed in another generator. So in essence the yield calls are “flattened” so that the same scheduler that handles coroutine_three() can handle the nested coroutines. Furthermore, if the inner coroutines use return, the values can made available to coroutine_three(), just like traditional nested functions!def coroutine_three(): yield from coroutine_one() yield from coroutine_two()# Equivalent code# The 'yield' calls in subgenerators are flatteneddef coroutine_three(): print(\"Coroutine one doing some work\") yield print(\"Coroutine one doing more work\") yield print(\"Coroutine two doing some work\") yield print(\"Coroutine two doing more work\") yieldBetter scheduler functionThe previous scheduler in the example interleaves the two coroutines manually. A more automatic implementation will be using a queue as shown below.from collections import dequedef scheduler(coroutines): q = deque(coroutines) while q: try: coroutine = q.popleft() results = coroutine.send(None) q.append(coroutine) except StopIteration: pass>>> scheduler([coroutine_one(), coroutine_two()])Coroutine one doing some workCoroutine two doing some workCoroutine one doing more workCoroutine two doing more workHow coroutines help with asynchronous I/ODuring I/O operations, a synchronous function will block the main thread until the I/O is ready. To carry out asychronous work on a single thread, a good way is for the scheduler to check all the coroutines in the queue and only allow those which are “ready” to run.In the example below, coroutine_four() has to fetch data through I/O operation. While it is suspended as the kernel populates the read buffer, the scheduler allows other coroutines to occupy the thread. The scheduler only allows coroutine_four() to execute again when the I/O is ready.def fetch_data(): print(\"Fetching data awaiting IO..\") # Suspends coroutine while awaiting IO to be ready yield # Let's assume that the scheduler only reschedules # the coroutine again when IO is ready print(\"Fetching data IO ready..\") # Mocked data return 10def coroutine_four(): print(\"Coroutine four doing some work\") data = yield from fetch_data() # I/O related coroutine print(\"Coroutine four doing more work with data: \" + str(data)) yield>>> scheduler([coroutine_one(), coroutine_four()])Coroutine one doing some workCoroutine four doing some workFetching data awaiting IO..Coroutine one doing more workFetching data IO ready..Coroutine four doing more work with data: 10How does the scheduler check for I/O completion?In the previous example, the I/O completion is mocked inside the fetch_data() coroutine. In reality, how does the scheduler know when the I/O is complete?This is where AsyncIO library comes in. It introduces concepts called Future and the Event Loop. Future objects are just coroutines that track whether the results (such as I/O) are ready or not. The Event Loop is just a for-loop that continuously schedules and runs coroutines, similar to our scheduler() function in the examples. At each iteration, the scheduler also polls for I/O operations to see which file descriptors are ready for I/O.In essence, this is the pseudocode of the Event Loop in asyncio.while the event loop is not stopped: poll for I/O and schedule reads/writes that are ready schedule the coroutines set for a 'later' time run the scheduled coroutinesCleaner code with async-awaitEven though coroutines work well with the yield keyword of generators, it was not the original intention of the feature. From Python 3.5 onwards, coroutines were made first-class features with the introduction of async and await keywords.There are some implementation differences but the main features remain the same. For example, assuming that fetch_data() returns an awaitable object, the coroutine_four() can be rewritten as shown below.async def coroutine_four(): print(\"Coroutine four doing some work\") data = await fetch_data() print(\"Coroutine four doing more work with data: \" + str(data))The same coroutine methods such as .send() will still work but the purpose now a lot clearer!Resources AsyncIO Event Loop by EdgeDB AsyncIO by Real Python Yield to Async-await by Mleue Event Loop by Lei Mao" } ] diff --git a/assets/js/data/swcache.js b/assets/js/data/swcache.js new file mode 100644 index 0000000..6bb18b0 --- /dev/null +++ b/assets/js/data/swcache.js @@ -0,0 +1 @@ +const resource = [ /* --- CSS --- */ '/assets/css/jekyll-theme-chirpy.css', /* --- PWA --- */ '/app.js', '/sw.js', /* --- HTML --- */ '/index.html', '/404.html', '/categories/', '/tags/', '/archives/', '/about/', /* --- Favicons & compressed JS --- */ '/assets/img/favicons/android-chrome-192x192.png', '/assets/img/favicons/android-chrome-512x512.png', '/assets/img/favicons/apple-touch-icon.png', '/assets/img/favicons/favicon-16x16.png', '/assets/img/favicons/favicon-32x32.png', '/assets/img/favicons/favicon.ico', '/assets/img/favicons/mstile-150x150.png', '/assets/js/dist/categories.min.js', '/assets/js/dist/commons.min.js', '/assets/js/dist/home.min.js', '/assets/js/dist/misc.min.js', '/assets/js/dist/page.min.js', '/assets/js/dist/post.min.js' ]; /* The request url with below domain will be cached */ const allowedDomains = [ 'www.googletagmanager.com', 'www.google-analytics.com', 'yarkhinephyo.github.io', 'fonts.gstatic.com', 'fonts.googleapis.com', 'cdn.jsdelivr.net', 'polyfill.io' ]; /* Requests that include the following path will be banned */ const denyUrls = []; diff --git a/assets/js/dist/categories.min.js b/assets/js/dist/categories.min.js new file mode 100644 index 0000000..d5296c5 --- /dev/null +++ b/assets/js/dist/categories.min.js @@ -0,0 +1,4 @@ +/*! + * Chirpy v6.4.2 | © 2019 Cotes Chung | MIT Licensed | https://github.com/cotes2020/jekyll-theme-chirpy/ + */ +!function(){"use strict";const o=$(".mode-toggle");function t(o){var t=function(o,t){if("object"!=typeof o||!o)return o;var s=o[Symbol.toPrimitive];if(void 0!==s){var e=s.call(o,t||"default");if("object"!=typeof e)return e;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(o)}(o,"string");return"symbol"==typeof t?t:String(t)}function s(o,s,e){return(s=t(s))in o?Object.defineProperty(o,s,{value:e,enumerable:!0,configurable:!0,writable:!0}):o[s]=e,o}const e=$("body"),a="sidebar-display";class r{static toggle(){!1===r.isExpanded?e.attr(a,""):e.removeAttr(a),r.isExpanded=!r.isExpanded}}s(r,"isExpanded",!1);const l=$("#sidebar-trigger"),n=$("#search-trigger"),i=$("#search-cancel"),c=$("#main-wrapper>.container>.row"),f=$("#topbar-title"),d=$("search"),u=$("#search-result-wrapper"),p=$("#search-results"),b=$("#search-input"),g=$("#search-hints"),m=$("html,body"),C="loaded",v="unloaded",h="input-focus",w="d-flex";class y{static on(){y.offset=window.scrollY,m.scrollTop(0)}static off(){m.scrollTop(y.offset)}}s(y,"offset",0),s(y,"resultVisible",!1);class k{static on(){l.addClass(v),f.addClass(v),n.addClass(v),d.addClass(w),i.addClass(C)}static off(){i.removeClass(C),d.removeClass(w),l.removeClass(v),f.removeClass(v),n.removeClass(v)}}class T{static on(){y.resultVisible||(y.on(),u.removeClass(v),c.addClass(v),y.resultVisible=!0)}static off(){y.resultVisible&&(p.empty(),g.hasClass(v)&&g.removeClass(v),u.addClass(v),c.removeClass(v),y.off(),b.val(""),y.resultVisible=!1)}}function x(){return i.hasClass(C)}const E=$(".collapse");$(".code-header>button").children().attr("class"),function(){const o=$(window),t=$("#back-to-top");o.on("scroll",(()=>{o.scrollTop()>50?t.fadeIn():t.fadeOut()})),t.on("click",(()=>{o.scrollTop(0)}))}(),[...document.querySelectorAll('[data-bs-toggle="tooltip"]')].map((o=>new bootstrap.Tooltip(o))),0!==o.length&&o.off().on("click",(o=>{const t=$(o.target);let s=t.prop("tagName")==="button".toUpperCase()?t:t.parent();modeToggle.flipMode(),s.trigger("blur")})),$("#sidebar-trigger").on("click",r.toggle),$("#mask").on("click",r.toggle),n.on("click",(function(){k.on(),T.on(),b.trigger("focus")})),i.on("click",(function(){k.off(),T.off()})),b.on("focus",(function(){d.addClass(h)})),b.on("focusout",(function(){d.removeClass(h)})),b.on("input",(()=>{""===b.val()?x()?g.removeClass(v):T.off():(T.on(),x()&&g.addClass(v))})),E.on("hide.bs.collapse",(function(){const o="h_"+$(this).attr("id").substring(2);o&&($("#".concat(o," .far.fa-folder-open")).attr("class","far fa-folder fa-fw"),$("#".concat(o," i.fas")).addClass("rotate"),$("#".concat(o)).removeClass("hide-border-bottom"))})),E.on("show.bs.collapse",(function(){const o="h_"+$(this).attr("id").substring(2);o&&($("#".concat(o," .far.fa-folder")).attr("class","far fa-folder-open fa-fw"),$("#".concat(o," i.fas")).removeClass("rotate"),$("#".concat(o)).addClass("hide-border-bottom"))}))}(); diff --git a/assets/js/dist/commons.min.js b/assets/js/dist/commons.min.js new file mode 100644 index 0000000..7af3cd8 --- /dev/null +++ b/assets/js/dist/commons.min.js @@ -0,0 +1,4 @@ +/*! + * Chirpy v6.4.2 | © 2019 Cotes Chung | MIT Licensed | https://github.com/cotes2020/jekyll-theme-chirpy/ + */ +!function(){"use strict";const e=$(".mode-toggle");function s(e){var s=function(e,s){if("object"!=typeof e||!e)return e;var o=e[Symbol.toPrimitive];if(void 0!==o){var t=o.call(e,s||"default");if("object"!=typeof t)return t;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===s?String:Number)(e)}(e,"string");return"symbol"==typeof s?s:String(s)}function o(e,o,t){return(o=s(o))in e?Object.defineProperty(e,o,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[o]=t,e}const t=$("body"),r="sidebar-display";class a{static toggle(){!1===a.isExpanded?t.attr(r,""):t.removeAttr(r),a.isExpanded=!a.isExpanded}}o(a,"isExpanded",!1);const l=$("#sidebar-trigger"),i=$("#search-trigger"),n=$("#search-cancel"),c=$("#main-wrapper>.container>.row"),d=$("#topbar-title"),f=$("search"),u=$("#search-result-wrapper"),p=$("#search-results"),g=$("#search-input"),m=$("#search-hints"),b=$("html,body"),v="loaded",C="unloaded",h="input-focus",y="d-flex";class w{static on(){w.offset=window.scrollY,b.scrollTop(0)}static off(){b.scrollTop(w.offset)}}o(w,"offset",0),o(w,"resultVisible",!1);class k{static on(){l.addClass(C),d.addClass(C),i.addClass(C),f.addClass(y),n.addClass(v)}static off(){n.removeClass(v),f.removeClass(y),l.removeClass(C),d.removeClass(C),i.removeClass(C)}}class T{static on(){w.resultVisible||(w.on(),u.removeClass(C),c.addClass(C),w.resultVisible=!0)}static off(){w.resultVisible&&(p.empty(),m.hasClass(C)&&m.removeClass(C),u.addClass(C),c.removeClass(C),w.off(),g.val(""),w.resultVisible=!1)}}function x(){return n.hasClass(v)}!function(){const e=$(window),s=$("#back-to-top");e.on("scroll",(()=>{e.scrollTop()>50?s.fadeIn():s.fadeOut()})),s.on("click",(()=>{e.scrollTop(0)}))}(),[...document.querySelectorAll('[data-bs-toggle="tooltip"]')].map((e=>new bootstrap.Tooltip(e))),0!==e.length&&e.off().on("click",(e=>{const s=$(e.target);let o=s.prop("tagName")==="button".toUpperCase()?s:s.parent();modeToggle.flipMode(),o.trigger("blur")})),$("#sidebar-trigger").on("click",a.toggle),$("#mask").on("click",a.toggle),i.on("click",(function(){k.on(),T.on(),g.trigger("focus")})),n.on("click",(function(){k.off(),T.off()})),g.on("focus",(function(){f.addClass(h)})),g.on("focusout",(function(){f.removeClass(h)})),g.on("input",(()=>{""===g.val()?x()?m.removeClass(C):T.off():(T.on(),x()&&m.addClass(C))}))}(); diff --git a/assets/js/dist/home.min.js b/assets/js/dist/home.min.js new file mode 100644 index 0000000..c15c7c3 --- /dev/null +++ b/assets/js/dist/home.min.js @@ -0,0 +1,4 @@ +/*! + * Chirpy v6.4.2 | © 2019 Cotes Chung | MIT Licensed | https://github.com/cotes2020/jekyll-theme-chirpy/ + */ +!function(){"use strict";const t=$(".mode-toggle");function e(t){var e=function(t,e){if("object"!=typeof t||!t)return t;var a=t[Symbol.toPrimitive];if(void 0!==a){var s=a.call(t,e||"default");if("object"!=typeof s)return s;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:String(e)}function a(t,a,s){return(a=e(a))in t?Object.defineProperty(t,a,{value:s,enumerable:!0,configurable:!0,writable:!0}):t[a]=s,t}const s=$("body"),o="sidebar-display";class r{static toggle(){!1===r.isExpanded?s.attr(o,""):s.removeAttr(o),r.isExpanded=!r.isExpanded}}a(r,"isExpanded",!1);const i=$("#sidebar-trigger"),l=$("#search-trigger"),n=$("#search-cancel"),c=$("#main-wrapper>.container>.row"),d=$("#topbar-title"),u=$("search"),m=$("#search-result-wrapper"),f=$("#search-results"),p=$("#search-input"),g=$("#search-hints"),h=$("html,body"),b="loaded",v="unloaded",C="input-focus",y="d-flex";class w{static on(){w.offset=window.scrollY,h.scrollTop(0)}static off(){h.scrollTop(w.offset)}}a(w,"offset",0),a(w,"resultVisible",!1);class T{static on(){i.addClass(v),d.addClass(v),l.addClass(v),u.addClass(y),n.addClass(b)}static off(){n.removeClass(b),u.removeClass(y),i.removeClass(v),d.removeClass(v),l.removeClass(v)}}class k{static on(){w.resultVisible||(w.on(),m.removeClass(v),c.addClass(v),w.resultVisible=!0)}static off(){w.resultVisible&&(f.empty(),g.hasClass(v)&&g.removeClass(v),m.addClass(v),c.removeClass(v),w.off(),p.val(""),w.resultVisible=!1)}}function x(){return n.hasClass(b)}$(".collapse");$(".code-header>button").children().attr("class");const E="data-src",j="data-lqip",M={SHIMMER:"shimmer",BLUR:"blur"};function S(t){$(this).parent().removeClass(t)}function A(){this.complete&&(this.hasAttribute(j)?S.call(this,M.BLUR):S.call(this,M.SHIMMER))}function F(){const t=$(this),e=t.attr(E);t.attr("src",encodeURI(e)),t.removeAttr(E)}class R{static get attrTimestamp(){return"data-ts"}static get attrDateFormat(){return"data-df"}static get locale(){return $("html").attr("lang").substring(0,2)}static getTimestamp(t){return Number(t.attr(R.attrTimestamp))}static getDateFormat(t){return t.attr(R.attrDateFormat)}}!function(){const t=$(window),e=$("#back-to-top");t.on("scroll",(()=>{t.scrollTop()>50?e.fadeIn():e.fadeOut()})),e.on("click",(()=>{t.scrollTop(0)}))}(),[...document.querySelectorAll('[data-bs-toggle="tooltip"]')].map((t=>new bootstrap.Tooltip(t))),0!==t.length&&t.off().on("click",(t=>{const e=$(t.target);let a=e.prop("tagName")==="button".toUpperCase()?e:e.parent();modeToggle.flipMode(),a.trigger("blur")})),$("#sidebar-trigger").on("click",r.toggle),$("#mask").on("click",r.toggle),l.on("click",(function(){T.on(),k.on(),p.trigger("focus")})),n.on("click",(function(){T.off(),k.off()})),p.on("focus",(function(){u.addClass(C)})),p.on("focusout",(function(){u.removeClass(C)})),p.on("input",(()=>{""===p.val()?x()?g.removeClass(v):k.off():(k.on(),x()&&g.addClass(v))})),dayjs.locale(R.locale),dayjs.extend(window.dayjs_plugin_localizedFormat),$("[".concat(R.attrTimestamp,"]")).each((function(){const t=dayjs.unix(R.getTimestamp($(this))),e=t.format(R.getDateFormat($(this)));$(this).text(e),$(this).removeAttr(R.attrTimestamp),$(this).removeAttr(R.attrDateFormat);const a=$(this).attr("data-bs-toggle");if(void 0===a||"tooltip"!==a)return;const s=t.format("llll");$(this).attr("data-bs-title",s),new bootstrap.Tooltip($(this))})),function(){const t=$("article img");t.length&&t.on("load",A),$('article img[loading="lazy"]').each((function(){this.complete&&S.call(this,M.SHIMMER)}));const e=$("article img[".concat(j,'="true"]'));e.length&&e.each(F)}()}(); diff --git a/assets/js/dist/misc.min.js b/assets/js/dist/misc.min.js new file mode 100644 index 0000000..1b660db --- /dev/null +++ b/assets/js/dist/misc.min.js @@ -0,0 +1,4 @@ +/*! + * Chirpy v6.4.2 | © 2019 Cotes Chung | MIT Licensed | https://github.com/cotes2020/jekyll-theme-chirpy/ + */ +!function(){"use strict";const t=$(".mode-toggle");function e(t){var e=function(t,e){if("object"!=typeof t||!t)return t;var a=t[Symbol.toPrimitive];if(void 0!==a){var s=a.call(t,e||"default");if("object"!=typeof s)return s;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:String(e)}function a(t,a,s){return(a=e(a))in t?Object.defineProperty(t,a,{value:s,enumerable:!0,configurable:!0,writable:!0}):t[a]=s,t}const s=$("body"),o="sidebar-display";class r{static toggle(){!1===r.isExpanded?s.attr(o,""):s.removeAttr(o),r.isExpanded=!r.isExpanded}}a(r,"isExpanded",!1);const l=$("#sidebar-trigger"),i=$("#search-trigger"),n=$("#search-cancel"),c=$("#main-wrapper>.container>.row"),d=$("#topbar-title"),u=$("search"),f=$("#search-result-wrapper"),m=$("#search-results"),p=$("#search-input"),g=$("#search-hints"),b=$("html,body"),h="loaded",v="unloaded",C="input-focus",y="d-flex";class w{static on(){w.offset=window.scrollY,b.scrollTop(0)}static off(){b.scrollTop(w.offset)}}a(w,"offset",0),a(w,"resultVisible",!1);class T{static on(){l.addClass(v),d.addClass(v),i.addClass(v),u.addClass(y),n.addClass(h)}static off(){n.removeClass(h),u.removeClass(y),l.removeClass(v),d.removeClass(v),i.removeClass(v)}}class k{static on(){w.resultVisible||(w.on(),f.removeClass(v),c.addClass(v),w.resultVisible=!0)}static off(){w.resultVisible&&(m.empty(),g.hasClass(v)&&g.removeClass(v),f.addClass(v),c.removeClass(v),w.off(),p.val(""),w.resultVisible=!1)}}function x(){return n.hasClass(h)}$(".collapse");$(".code-header>button").children().attr("class");class j{static get attrTimestamp(){return"data-ts"}static get attrDateFormat(){return"data-df"}static get locale(){return $("html").attr("lang").substring(0,2)}static getTimestamp(t){return Number(t.attr(j.attrTimestamp))}static getDateFormat(t){return t.attr(j.attrDateFormat)}}!function(){const t=$(window),e=$("#back-to-top");t.on("scroll",(()=>{t.scrollTop()>50?e.fadeIn():e.fadeOut()})),e.on("click",(()=>{t.scrollTop(0)}))}(),[...document.querySelectorAll('[data-bs-toggle="tooltip"]')].map((t=>new bootstrap.Tooltip(t))),0!==t.length&&t.off().on("click",(t=>{const e=$(t.target);let a=e.prop("tagName")==="button".toUpperCase()?e:e.parent();modeToggle.flipMode(),a.trigger("blur")})),$("#sidebar-trigger").on("click",r.toggle),$("#mask").on("click",r.toggle),i.on("click",(function(){T.on(),k.on(),p.trigger("focus")})),n.on("click",(function(){T.off(),k.off()})),p.on("focus",(function(){u.addClass(C)})),p.on("focusout",(function(){u.removeClass(C)})),p.on("input",(()=>{""===p.val()?x()?g.removeClass(v):k.off():(k.on(),x()&&g.addClass(v))})),dayjs.locale(j.locale),dayjs.extend(window.dayjs_plugin_localizedFormat),$("[".concat(j.attrTimestamp,"]")).each((function(){const t=dayjs.unix(j.getTimestamp($(this))),e=t.format(j.getDateFormat($(this)));$(this).text(e),$(this).removeAttr(j.attrTimestamp),$(this).removeAttr(j.attrDateFormat);const a=$(this).attr("data-bs-toggle");if(void 0===a||"tooltip"!==a)return;const s=t.format("llll");$(this).attr("data-bs-title",s),new bootstrap.Tooltip($(this))}))}(); diff --git a/assets/js/dist/page.min.js b/assets/js/dist/page.min.js new file mode 100644 index 0000000..c1b5b6a --- /dev/null +++ b/assets/js/dist/page.min.js @@ -0,0 +1,4 @@ +/*! + * Chirpy v6.4.2 | © 2019 Cotes Chung | MIT Licensed | https://github.com/cotes2020/jekyll-theme-chirpy/ + */ +!function(){"use strict";const t=$(".mode-toggle");function e(t){var e=function(t,e){if("object"!=typeof t||!t)return t;var o=t[Symbol.toPrimitive];if(void 0!==o){var s=o.call(t,e||"default");if("object"!=typeof s)return s;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:String(e)}function o(t,o,s){return(o=e(o))in t?Object.defineProperty(t,o,{value:s,enumerable:!0,configurable:!0,writable:!0}):t[o]=s,t}const s=$("body"),n="sidebar-display";class a{static toggle(){!1===a.isExpanded?s.attr(n,""):s.removeAttr(n),a.isExpanded=!a.isExpanded}}o(a,"isExpanded",!1);const r=$("#sidebar-trigger"),i=$("#search-trigger"),l=$("#search-cancel"),c=$("#main-wrapper>.container>.row"),u=$("#topbar-title"),d=$("search"),f=$("#search-result-wrapper"),p=$("#search-results"),m=$("#search-input"),g=$("#search-hints"),h=$("html,body"),b="loaded",v="unloaded",C="input-focus",w="d-flex";class y{static on(){y.offset=window.scrollY,h.scrollTop(0)}static off(){h.scrollTop(y.offset)}}o(y,"offset",0),o(y,"resultVisible",!1);class k{static on(){r.addClass(v),u.addClass(v),i.addClass(v),d.addClass(w),l.addClass(b)}static off(){l.removeClass(b),d.removeClass(w),r.removeClass(v),u.removeClass(v),i.removeClass(v)}}class S{static on(){y.resultVisible||(y.on(),f.removeClass(v),c.addClass(v),y.resultVisible=!0)}static off(){y.resultVisible&&(p.empty(),g.hasClass(v)&&g.removeClass(v),f.addClass(v),c.removeClass(v),y.off(),m.val(""),y.resultVisible=!1)}}function T(){return l.hasClass(b)}$(".collapse");const E=".code-header>button",A="fas fa-check",x="timeout",M="data-title-succeed",R="data-bs-original-title",I=2e3;function V(t){if($(t)[0].hasAttribute(x)){let e=$(t).attr(x);if(Number(e)>Date.now())return!0}return!1}function q(t){$(t).attr(x,Date.now()+I)}function N(t){$(t).removeAttr(x)}const P=$(E).children().attr("class");const U="data-src",j="data-lqip",B={SHIMMER:"shimmer",BLUR:"blur"};function H(t){$(this).parent().removeClass(t)}function O(){this.complete&&(this.hasAttribute(j)?H.call(this,B.BLUR):H.call(this,B.SHIMMER))}function z(){const t=$(this),e=t.attr(U);t.attr("src",encodeURI(e)),t.removeAttr(U)}!function(){const t=$(window),e=$("#back-to-top");t.on("scroll",(()=>{t.scrollTop()>50?e.fadeIn():e.fadeOut()})),e.on("click",(()=>{t.scrollTop(0)}))}(),[...document.querySelectorAll('[data-bs-toggle="tooltip"]')].map((t=>new bootstrap.Tooltip(t))),0!==t.length&&t.off().on("click",(t=>{const e=$(t.target);let o=e.prop("tagName")==="button".toUpperCase()?e:e.parent();modeToggle.flipMode(),o.trigger("blur")})),$("#sidebar-trigger").on("click",a.toggle),$("#mask").on("click",a.toggle),i.on("click",(function(){k.on(),S.on(),m.trigger("focus")})),l.on("click",(function(){k.off(),S.off()})),m.on("focus",(function(){d.addClass(C)})),m.on("focusout",(function(){d.removeClass(C)})),m.on("input",(()=>{""===m.val()?T()?g.removeClass(v):S.off():(S.on(),T()&&g.addClass(v))})),function(){const t=$("article img");t.length&&t.on("load",O),$('article img[loading="lazy"]').each((function(){this.complete&&H.call(this,B.SHIMMER)}));const e=$("article img[".concat(j,'="true"]'));e.length&&e.each(z)}(),$(".popup")<=0||$(".popup").magnificPopup({type:"image",closeOnContentClick:!0,showCloseBtn:!1,zoom:{enabled:!0,duration:300,easing:"ease-in-out"}}),function(){if($(E).length){const t=new ClipboardJS(E,{target:t=>t.parentNode.nextElementSibling.querySelector("code .rouge-code")});[...document.querySelectorAll(E)].map((t=>new bootstrap.Tooltip(t,{placement:"left"}))),t.on("success",(t=>{t.clearSelection();const e=t.trigger;V(e)||(!function(t){$(t).children().attr("class",A)}(e),function(t){const e=$(t).attr(M);$(t).attr(R,e).tooltip("show")}(e),q(e),setTimeout((()=>{!function(t){$(t).tooltip("hide").removeAttr(R)}(e),function(t){$(t).children().attr("class",P)}(e),N(e)}),I))}))}const t=$("#copy-link");t.on("click",(t=>{let e=$(t.target);V(e)||navigator.clipboard.writeText(window.location.href).then((()=>{const t=e.attr(R),o=e.attr(M);e.attr(R,o).tooltip("show"),q(e),setTimeout((()=>{e.attr(R,t),N(e)}),I)}))})),t.on("mouseleave",(function(t){$(t.target).tooltip("hide")}))}()}(); diff --git a/assets/js/dist/post.min.js b/assets/js/dist/post.min.js new file mode 100644 index 0000000..2c64251 --- /dev/null +++ b/assets/js/dist/post.min.js @@ -0,0 +1,4 @@ +/*! + * Chirpy v6.4.2 | © 2019 Cotes Chung | MIT Licensed | https://github.com/cotes2020/jekyll-theme-chirpy/ + */ +!function(){"use strict";const t=$(".mode-toggle");function e(t){var e=function(t,e){if("object"!=typeof t||!t)return t;var o=t[Symbol.toPrimitive];if(void 0!==o){var a=o.call(t,e||"default");if("object"!=typeof a)return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:String(e)}function o(t,o,a){return(o=e(o))in t?Object.defineProperty(t,o,{value:a,enumerable:!0,configurable:!0,writable:!0}):t[o]=a,t}const a=$("body"),r="sidebar-display";class s{static toggle(){!1===s.isExpanded?a.attr(r,""):a.removeAttr(r),s.isExpanded=!s.isExpanded}}o(s,"isExpanded",!1);const i=$("#sidebar-trigger"),n=$("#search-trigger"),l=$("#search-cancel"),c=$("#main-wrapper>.container>.row"),u=$("#topbar-title"),d=$("search"),m=$("#search-result-wrapper"),f=$("#search-results"),p=$("#search-input"),g=$("#search-hints"),h=$("html,body"),b="loaded",v="unloaded",C="input-focus",w="d-flex";class y{static on(){y.offset=window.scrollY,h.scrollTop(0)}static off(){h.scrollTop(y.offset)}}o(y,"offset",0),o(y,"resultVisible",!1);class S{static on(){i.addClass(v),u.addClass(v),n.addClass(v),d.addClass(w),l.addClass(b)}static off(){l.removeClass(b),d.removeClass(w),i.removeClass(v),u.removeClass(v),n.removeClass(v)}}class T{static on(){y.resultVisible||(y.on(),m.removeClass(v),c.addClass(v),y.resultVisible=!0)}static off(){y.resultVisible&&(f.empty(),g.hasClass(v)&&g.removeClass(v),m.addClass(v),c.removeClass(v),y.off(),p.val(""),y.resultVisible=!1)}}function k(){return l.hasClass(b)}$(".collapse");const x=".code-header>button",A="fas fa-check",E="timeout",j="data-title-succeed",D="data-bs-original-title",M=2e3;function F(t){if($(t)[0].hasAttribute(E)){let e=$(t).attr(E);if(Number(e)>Date.now())return!0}return!1}function R(t){$(t).attr(E,Date.now()+M)}function q(t){$(t).removeAttr(E)}const I=$(x).children().attr("class");const N="data-src",V="data-lqip",P={SHIMMER:"shimmer",BLUR:"blur"};function U(t){$(this).parent().removeClass(t)}function z(){this.complete&&(this.hasAttribute(V)?U.call(this,P.BLUR):U.call(this,P.SHIMMER))}function B(){const t=$(this),e=t.attr(N);t.attr("src",encodeURI(e)),t.removeAttr(N)}class H{static get attrTimestamp(){return"data-ts"}static get attrDateFormat(){return"data-df"}static get locale(){return $("html").attr("lang").substring(0,2)}static getTimestamp(t){return Number(t.attr(H.attrTimestamp))}static getDateFormat(t){return t.attr(H.attrDateFormat)}}0!==t.length&&t.off().on("click",(t=>{const e=$(t.target);let o=e.prop("tagName")==="button".toUpperCase()?e:e.parent();modeToggle.flipMode(),o.trigger("blur")})),$("#sidebar-trigger").on("click",s.toggle),$("#mask").on("click",s.toggle),n.on("click",(function(){S.on(),T.on(),p.trigger("focus")})),l.on("click",(function(){S.off(),T.off()})),p.on("focus",(function(){d.addClass(C)})),p.on("focusout",(function(){d.removeClass(C)})),p.on("input",(()=>{""===p.val()?k()?g.removeClass(v):T.off():(T.on(),k()&&g.addClass(v))})),function(){const t=$("article img");t.length&&t.on("load",z),$('article img[loading="lazy"]').each((function(){this.complete&&U.call(this,P.SHIMMER)}));const e=$("article img[".concat(V,'="true"]'));e.length&&e.each(B)}(),$(".popup")<=0||$(".popup").magnificPopup({type:"image",closeOnContentClick:!0,showCloseBtn:!1,zoom:{enabled:!0,duration:300,easing:"ease-in-out"}}),dayjs.locale(H.locale),dayjs.extend(window.dayjs_plugin_localizedFormat),$("[".concat(H.attrTimestamp,"]")).each((function(){const t=dayjs.unix(H.getTimestamp($(this))),e=t.format(H.getDateFormat($(this)));$(this).text(e),$(this).removeAttr(H.attrTimestamp),$(this).removeAttr(H.attrDateFormat);const o=$(this).attr("data-bs-toggle");if(void 0===o||"tooltip"!==o)return;const a=t.format("llll");$(this).attr("data-bs-title",a),new bootstrap.Tooltip($(this))})),function(){if($(x).length){const t=new ClipboardJS(x,{target:t=>t.parentNode.nextElementSibling.querySelector("code .rouge-code")});[...document.querySelectorAll(x)].map((t=>new bootstrap.Tooltip(t,{placement:"left"}))),t.on("success",(t=>{t.clearSelection();const e=t.trigger;F(e)||(!function(t){$(t).children().attr("class",A)}(e),function(t){const e=$(t).attr(j);$(t).attr(D,e).tooltip("show")}(e),R(e),setTimeout((()=>{!function(t){$(t).tooltip("hide").removeAttr(D)}(e),function(t){$(t).children().attr("class",I)}(e),q(e)}),M))}))}const t=$("#copy-link");t.on("click",(t=>{let e=$(t.target);F(e)||navigator.clipboard.writeText(window.location.href).then((()=>{const t=e.attr(D),o=e.attr(j);e.attr(D,o).tooltip("show"),R(e),setTimeout((()=>{e.attr(D,t),q(e)}),M)}))})),t.on("mouseleave",(function(t){$(t.target).tooltip("hide")}))}(),document.querySelector("main h2")&&tocbot.init({tocSelector:"#toc",contentSelector:".content",ignoreSelector:"[data-toc-skip]",headingSelector:"h2, h3, h4",orderedList:!1,scrollSmooth:!1}),function(){const t=$(window),e=$("#back-to-top");t.on("scroll",(()=>{t.scrollTop()>50?e.fadeIn():e.fadeOut()})),e.on("click",(()=>{t.scrollTop(0)}))}(),[...document.querySelectorAll('[data-bs-toggle="tooltip"]')].map((t=>new bootstrap.Tooltip(t)))}(); diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 0000000..ed266cf --- /dev/null +++ b/categories/index.html @@ -0,0 +1 @@ + Categories | Phyo's Log
Categories
diff --git a/categories/school/index.html b/categories/school/index.html new file mode 100644 index 0000000..88fb282 --- /dev/null +++ b/categories/school/index.html @@ -0,0 +1 @@ + School | Phyo's Log
Category
diff --git a/categories/tech/index.html b/categories/tech/index.html new file mode 100644 index 0000000..0b02441 --- /dev/null +++ b/categories/tech/index.html @@ -0,0 +1 @@ + Tech | Phyo's Log
Category
diff --git a/feed.xml b/feed.xml new file mode 100644 index 0000000..8346ef7 --- /dev/null +++ b/feed.xml @@ -0,0 +1 @@ + https://yarkhinephyo.github.io/Phyo's LogHi, my name is Yar Khine Phyo. This is a technical blog on computer science and software engineering related concepts that I will contribute to from time to time. 2024-01-24T04:32:36+00:00 Yar Khine Phyo https://yarkhinephyo.github.io/ Jekyll © 2024 Yar Khine Phyo /assets/img/favicons/favicon.ico /assets/img/favicons/favicon-96x96.png Learning Points: Weaviate Vector Database2024-01-24T04:20:00+00:00 2024-01-24T04:20:00+00:00 https://yarkhinephyo.github.io/posts/weaviate-vector-database/ Yar Khine Phyo This video is a deep dive on the vector database Weaviate as part of the weekly database seminars by CMU Database Group. The presenter is Etienne Dilocker from Weaviate. Why a Vector Database Instead of indexing literal keywords from paragraphs, meanings (embeddings) can be indexed for search purposes. LLMs tend to hallucinate less when some context around the question is given. Retrieval A... Learning Points: Streamlining FedRAMP Compliance with CNCF2024-01-21T03:55:00+00:00 2024-01-21T03:55:00+00:00 https://yarkhinephyo.github.io/posts/streamlining-fedramp-compliance/ Yar Khine Phyo This video is about streamlining FedRAMP compliance with CNCF technologies. The presenters are Ali Monfre and Vlad Ungureanu from Palantir Technologies. FedRAMP Overview FedRAMP is the accreditation required for companies to sell SaaS solutions to the government instead of on-prem solutions. General steps include: Identify a sponsor to join the authorization board. Document security con... Learning Points: Snowflake Iceberg, Streaming, Unistore2024-01-21T02:20:00+00:00 2024-01-21T02:20:00+00:00 https://yarkhinephyo.github.io/posts/snowflake-new-streams-talk/ Yar Khine Phyo This video is about Snowflake Iceberg Tables, Streaming Ingest and Unistore. The presenters are N.Single, T.Jones and A.Motivala as part of the Database Seminar Series by CMU Database Group. Problems with Traditional Data Lakes Traditional data lakes use file systems as the metadata layer. For example, data for each table is organized in a directory. Partitioning is implemented through nested... Learning Points: Scaling Monitoring from Prometheus to M32024-01-20T03:30:00+00:00 2024-01-21T02:30:54+00:00 https://yarkhinephyo.github.io/posts/scaling-monitoring-with-m3/ Yar Khine Phyo I enjoy watching 45 minutes to 1 hour long technical talks at conferences. Unfortunately, I am not retaining the knowledge as long as I would like to. From now on, I am going to try summarizing my takeaways for each videos to improve my own retention. This video is about scaling monitoring from Prometheus to M3 at Databricks presented by YY Wan and Nick Lanham. Context Prometheus based monit... Learning Points: Kubernetes Networking Deep Dive2024-01-20T03:30:00+00:00 2024-01-20T03:30:00+00:00 https://yarkhinephyo.github.io/posts/kubernetes-networking-intro-and-deep-dive/ Yar Khine Phyo This video is about how networking works in Kubernetes by Bowei Du and Tim Hockin from Google. Networking APIs Exposed by Kubernetes Service, Endpoint: Service registration and discovery. Ingress: L7 HTTP routing. Gateway: Next-gen HTTP routing and service ingress. NetworkPolicy: Application firewall. Pod Networking Model All pods can reach all other pods across nodes. The network... diff --git a/index.html b/index.html new file mode 100644 index 0000000..b5b5fe4 --- /dev/null +++ b/index.html @@ -0,0 +1 @@ + Phyo's Log
Phyo's Log
diff --git a/norobots/index.html b/norobots/index.html new file mode 100644 index 0000000..3a9e350 --- /dev/null +++ b/norobots/index.html @@ -0,0 +1,11 @@ + + + + Redirecting… + + + + +

Redirecting…

+ Click here if you are not redirected. + diff --git a/page2/index.html b/page2/index.html new file mode 100644 index 0000000..4c25a5a --- /dev/null +++ b/page2/index.html @@ -0,0 +1 @@ + Phyo's Log
Phyo's Log
diff --git a/page3/index.html b/page3/index.html new file mode 100644 index 0000000..fdb27e6 --- /dev/null +++ b/page3/index.html @@ -0,0 +1 @@ + Phyo's Log
Phyo's Log
diff --git a/page4/index.html b/page4/index.html new file mode 100644 index 0000000..ad4d1a7 --- /dev/null +++ b/page4/index.html @@ -0,0 +1 @@ + Phyo's Log
Phyo's Log
diff --git a/posts/async-io-event-loop/index.html b/posts/async-io-event-loop/index.html new file mode 100644 index 0000000..83737ab --- /dev/null +++ b/posts/async-io-event-loop/index.html @@ -0,0 +1,231 @@ + Generators - The Beginning of Asynchronicity in Python | Phyo's Log
Post

Generators - The Beginning of Asynchronicity in Python

If you have worked with asynchronous programming in Python, you may have used the async and await keywords before. It turns out that Python Generators are actually the building blocks of these abstractions. This article explain their relationship in a greater detail.

Coroutines

For single-threaded asynchronous programming to work in Python, we need a mechanism to “pause” function calls. For example if a particular function involves fetching something from a database, we would like to “pause” the function’s execution and schedule something else until the response is received. However in traditional Python functions, the return keyword frees up the internal state at the end of invocation…

It turns out that generators in Python can achieve similar purpose! With generators, the yield keyword gives up the control of the thread while the internal state is saved until the next invocation. So we can do some multitasking with a scheduler as shown below.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
def gen_one():
+    print("Gen one doing some work")
+    yield
+    print("Gen one doing more work")
+    yield
+
+def gen_two():
+    print("Gen two doing some work")
+    yield
+    print("Gen two doing more work")
+    yield
+
+def scheduler():
+    g1 = gen_one()
+    g2 = gen_two()
+    next(g1)
+    next(g2)
+    next(g1)
+    next(g2)
+
1
+2
+3
+4
+5
+
>>> scheduler()
+Gen one doing some work
+Gen two doing some work
+Gen one doing more work
+Gen two doing more work
+

Coroutine is the term for suspendable functions. As generators cannot take in values like normal functions, new methods are introduced in PEP 342 including .send() that allows passing of parameters (and also .throw() and .close()).

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
def coroutine_one():
+    print("Coroutine one doing some work")
+    data = (yield)
+    print(f"Received data: {data}")
+    print("Coroutine one doing more work")
+    yield
+
+cor1 = coroutine_one()
+cor1.send(None)
+cor1.send("lorem ipsum")
+
1
+2
+3
+
Coroutine one doing some work
+Received data: lorem ipsum
+Coroutine one doing more work
+

Let’s refer to generators as coroutines from now.

Nested coroutines

Another problem we have is that nested coroutines would not work with current syntax. As shown below, how will coroutine_three() call coroutine_one() and coroutine_two()? It is just a function that has two coroutine objects but has no ability to schedule them!

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
def coroutine_one():
+    print("Coroutine one doing some work")
+    yield
+    print("Coroutine one doing more work")
+    yield
+
+def coroutine_two():
+    print("Coroutine two doing some work")
+    yield
+    print("Coroutine two doing more work")
+    yield
+
+# Will not work as intended
+def coroutine_three():
+    coroutine_one()
+    coroutine_two()
+

To solve this, PEP 380 introduces the yield from operator. This allows a section of code containing yield to be factored out and placed in another generator. So in essence the yield calls are “flattened” so that the same scheduler that handles coroutine_three() can handle the nested coroutines. Furthermore, if the inner coroutines use return, the values can made available to coroutine_three(), just like traditional nested functions!

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
def coroutine_three():
+    yield from coroutine_one()
+    yield from coroutine_two()
+
+# Equivalent code
+# The 'yield' calls in subgenerators are flattened
+def coroutine_three():
+    print("Coroutine one doing some work")
+    yield
+    print("Coroutine one doing more work")
+    yield
+    print("Coroutine two doing some work")
+    yield
+    print("Coroutine two doing more work")
+    yield
+

Better scheduler function

The previous scheduler in the example interleaves the two coroutines manually. A more automatic implementation will be using a queue as shown below.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
from collections import deque
+
+def scheduler(coroutines):
+    q = deque(coroutines)
+    while q:
+        try:
+            coroutine = q.popleft()
+            results = coroutine.send(None)
+            q.append(coroutine)
+        except StopIteration:
+            pass
+
1
+2
+3
+4
+5
+
>>> scheduler([coroutine_one(), coroutine_two()])
+Coroutine one doing some work
+Coroutine two doing some work
+Coroutine one doing more work
+Coroutine two doing more work
+

How coroutines help with asynchronous I/O

During I/O operations, a synchronous function will block the main thread until the I/O is ready. To carry out asychronous work on a single thread, a good way is for the scheduler to check all the coroutines in the queue and only allow those which are “ready” to run.

In the example below, coroutine_four() has to fetch data through I/O operation. While it is suspended as the kernel populates the read buffer, the scheduler allows other coroutines to occupy the thread. The scheduler only allows coroutine_four() to execute again when the I/O is ready.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
def fetch_data():
+    print("Fetching data awaiting IO..")
+    # Suspends coroutine while awaiting IO to be ready
+    yield
+
+    # Let's assume that the scheduler only reschedules
+    # the coroutine again when IO is ready
+    print("Fetching data IO ready..")
+    # Mocked data
+    return 10
+
+def coroutine_four():
+    print("Coroutine four doing some work")
+    data = yield from fetch_data() # I/O related coroutine
+    print("Coroutine four doing more work with data: " + str(data))
+    yield
+
1
+2
+3
+4
+5
+6
+7
+
>>> scheduler([coroutine_one(), coroutine_four()])
+Coroutine one doing some work
+Coroutine four doing some work
+Fetching data awaiting IO..
+Coroutine one doing more work
+Fetching data IO ready..
+Coroutine four doing more work with data: 10
+

How does the scheduler check for I/O completion?

In the previous example, the I/O completion is mocked inside the fetch_data() coroutine. In reality, how does the scheduler know when the I/O is complete?

This is where AsyncIO library comes in. It introduces concepts called Future and the Event Loop. Future objects are just coroutines that track whether the results (such as I/O) are ready or not. The Event Loop is just a for-loop that continuously schedules and runs coroutines, similar to our scheduler() function in the examples. At each iteration, the scheduler also polls for I/O operations to see which file descriptors are ready for I/O.

In essence, this is the pseudocode of the Event Loop in asyncio.

1
+2
+3
+4
+
while the event loop is not stopped:
+    poll for I/O and schedule reads/writes that are ready
+    schedule the coroutines set for a 'later' time
+    run the scheduled coroutines
+

Cleaner code with async-await

Even though coroutines work well with the yield keyword of generators, it was not the original intention of the feature. From Python 3.5 onwards, coroutines were made first-class features with the introduction of async and await keywords.

There are some implementation differences but the main features remain the same. For example, assuming that fetch_data() returns an awaitable object, the coroutine_four() can be rewritten as shown below.

1
+2
+3
+4
+
async def coroutine_four():
+    print("Coroutine four doing some work")
+    data = await fetch_data()
+    print("Coroutine four doing more work with data: " + str(data))
+

The same coroutine methods such as .send() will still work but the purpose now a lot clearer!

Resources

  1. AsyncIO Event Loop by EdgeDB
  2. AsyncIO by Real Python
  3. Yield to Async-await by Mleue
  4. Event Loop by Lei Mao
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/choosing-between-utf-encodings/index.html b/posts/choosing-between-utf-encodings/index.html new file mode 100644 index 0000000..b907669 --- /dev/null +++ b/posts/choosing-between-utf-encodings/index.html @@ -0,0 +1,69 @@ + Choosing Between UTF Encodings | Phyo's Log
Post

Choosing Between UTF Encodings

Have you occasionally chosen a character encoding such as UTF-8 during reading and writing files while wondering its purpose? I have! This post explains various UTF (Unicode Transformation Format) algorithms such as UTF-8, UTF-16, UTF32 and how to choose between them.

Unicode character set

Unicode character set defines a unique number for almost all characters used in modern texts today. The standard ensures that given a number, also known as a code point, different softwares will decode it as the same character.

CharacterDecimal RepresentationCode (Read Hexadecimal)
A65U+41
B66U+42
25105U+6211
😋128523U+1F60B

The Unicode character set ranges from 0x0 to 0x10FFFF (21-bits range).

UTF-32

UTF stands for Unicode Transformation Format. It encodes integer code points into byte representations on a machine. For example, if 4 bytes are allocated to a character at each time, four-byte representations are shown below.

CharacterByte Representation (Read Hexadecimal)
A0x00000041
B0x00000042
0x00006211
😋0x0001F60B

This is exactly what UTF-32 does. It pads every code point with zeros into 32 bits. This is more than sufficient for the 21-bits range of Unicode character set.

However, the approach is space-inefficient. For example, if there are only English letters in a document (U+41 to U+7A), only one byte is necessary to represent each character. However, UTF-32 will still pad with three bytes to form four-byte representations, resulting as 300% increase in storage.

UTF-16

UTF-16 mitigates the problem by representing U+0 to U+FFFF with two bytes and U+10000 to U+10FFFF with four bytes.

Characters from almost all modern languages are found in the first 216 code points (See Basic Multilingual Plane). If a document only contains these code points, UTF-16 will mainly use two-byte representations instead, meaning storage is cut by 50% from using UTF-32.

To represent larger code points, UTF-16 employs a concept called surrogate pairs. High surrogates are code points from U+D800 to U+DBFF and low surrogates are code points from U+DC00 to U+DFFF. There are no character mappings at these ranges and they only have meaningful representations when paired. The example below may present a clearer picture.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
High surrogate --> U+D800 to U+DBFF --> 110110 concat with any 10 bits
+Low surrogate  --> U+DC00 to U+DFFF --> 110111 concat with any 10 bits
+
+Character: 😋
+Unicode  : U+1F60B
+Binary   : 0b11111011000001011
+
+Binary padded 20-bits: 0b00011111011000001011
+                         <--- A --><--- B -->
+                         (10 bits)  (10 bits)
+
+High surrogate: 110110 concat A = 1101100001111101 (16 bits)
+Low surrogate : 110111 concat B = 1101111000001011 (16 bits)
+

If a decoder sees a two-byte representation starting with 110110 or 110111 bits, it can infer that this is part of a surrogate pair and immediately identify the other surrogate. The binary representation of the original character can be reconstructed afterwards.

UTF-8

ASCII characters compose the first 27 code points. Most of the time when coding or writing English articles, you may mostly end up using these characters. As these code points can be represented with one byte, two-byte representations of UTF-16 still results in wasted storage.

Depending on the range of Unicode character set, UTF-8 uses one, two, three or four-byte representations. The encoding pseudocode is shown below.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
if code point < 2^7             # Covers ASCII
+  pad with zeros till 8 bits
+  1st byte = 8 bits
+
+else if code point < 2^11       # Covers other Latin alphabets
+  pad with zeros till 11 bits   # (5 + 6)
+  1st byte = "110" concat 5 bits
+  2nd byte = "10" concat 6 bits
+
+else if code point < 2^16       # Covers Basic Multilingual Plane
+  pad with zeros till 16 bits   # (4 + 6 + 6)
+  1st byte = "1110" concat 4 bits
+  2nd byte = "10" concat 6 bits
+  3rd byte = "10" concat 6 bits
+
+else if code point < 2^21       # Covers 21-bit Unicode range
+  pad with zeros till 21 bits   # (3 + 6 + 6 + 6)
+  1st byte = "11110" concat 3 bits
+  2nd byte = "10" concat 6 bits
+  3rd byte = "10" concat 6 bits
+  4rd byte = "10" concat 6 bits
+

As texts encoded in ASCII never appear as multi-byte sequences, UTF-8 can be used to decode it directly. The backward compatibility is one of the reasons why it has been adopted at a large scale.

How to choose between UTF-8, UTF-16, UTF-32

If backward compatibility to ASCII is preferred and most characters are English text, UTF-8 is a good choice.

If most characters are from non-English languages, UTF-16 is preferred because it uses two-byte representations for Basic Multilingual Plane as compared to UTF-8 which uses three-byte representations.

UTF-32 is rarely used but in theory, the fixed-width encoding without transformations allows faster encoding and decoding of characters.

Resources

  1. UTF-8 vs UTF-16 vs UTF-32 on StackOverflow
  2. How UTF-16 encodes 21-bit unicode
  3. Unicode Encoding! by EmNudge
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/cmu-mcds-course-reviews/index.html b/posts/cmu-mcds-course-reviews/index.html new file mode 100644 index 0000000..f2b5b9f --- /dev/null +++ b/posts/cmu-mcds-course-reviews/index.html @@ -0,0 +1 @@ + CMU MCDS Course Reviews | Phyo's Log
Post

CMU MCDS Course Reviews

Over the last 1.5 years, I studied Master of Computational Data Science (MCDS) at Carnegie Mellon University. Inspired by blogs such as fanpu.io and wanshenl.me, I am going to outline my experiences for each course to hopefully help future students.

Summer 2022

  • 15513 - Introduction to Computer Systems As someone who does not have a systems background, this course was transformational. It started with the foundation of how hardware instructions work and build up to computer memory, processes and threads. I took it with a full time internship but fortunately the class was asynchronous. I watched the lectures on weekday nights and worked on the projects on the weekends. The workload was manageable except for weeks of Malloc Lab which were heavier. For graduate students, this course was only 6 units so the tuition fee was only half the normal cost. Doing well in this course is compulsory for MCDS students who wish to choose the Systems concentration. Notable projects include:
    • Bomb lab: Reading through assembly code to decode its purpose.
    • Malloc lab: Building a memory allocator where throughput and memory fragmentation is graded.
    • Tsh lab: Building a simple shell to manage processes.
    • Proxy lab: Web server to serve and cache static files concurrently.

Fall 2022

  • 10601 - Introduction to Machine Learning This course focused on how each type of machine learning models work from the ground-up, aka all mathematics. There were 3 written exams and homeworks with written and programming components. I spent more effort on the written components and preparing for the exams. The programming contained straightforward instructions and were not too complex. For students with more machine learning background, the course code for the PhD level version is 10701.
  • 11631 - Data Science Seminar A compulsary course for MCDS students. The focus was on reading and writing scientific papers. Every week students critique data science papers and submit paper summaries. There was also a component where students read the papers of the senior MCDS batch and critique them. Personally, I felt that the selected readings were too focused on the human-computer-interaction concentration of MCDS and did not include the other two concentrations (analytics and systems). The workload was the lowest compared to the other courses in the semester.
  • 11637 - Foundation of Computational Data Science There were some overlaps with 10601 in terms of the machine learning content. However, this course included the surrounding infrastructure around a machine learning model such as data processing and deploying the models. The course was asynchronous where students read the lectures on their own time which provided flexibility in terms of scheduling. The projects were time-consuming but not as technically complex as the systems related courses. Personally, I did not learn much more than the basic ML engineering experiences that I already had from internships.
  • 15645 - Database Systems The most exciting course for the semester. I got to build components of the Bustub database system. The professor, Andy Pavlo, was also very engaging which led to a lot of participation during class. The contents ranged from the low level concepts such as how the database interacts with the disk and optimizing query plans to higher level concepts such as how distributed databases work. The projects were in C++ and emphasized on multi-threading support. Optimizations could lead to higher ranks on the leaderboard and extra credits. As someone without any C++ experience, this course had the most difficult projects for the semester but after investing significant amount of time, it was still possible to achieve top 10 leaderboard ranking. My lecture notes can be found here. Notable projects include:
    • Storage Index: B+ tree data structure with concurrent protocols.
    • Query Execution: Iterator query processing model and query plan optimization.
    • Concurrency Control: Lock-based concurrency control with different isolation levels.

Spring 2023

  • 05839 - Interactive Data Science The course was about visualization techniques in data science. The students have to read the lecture slides before attending the lectures afterwards. Contents included drawing charts, interpreting them and using frameworks to deploy them. There was also a final project where students could choose almost any dataset and draw visualizations in Streamlit. In my opinion, this was not sufficiently challenging for a graduate level course that was compulsory for MCDS students. A more appropriate audience would be freshmen students with not a lot of programming background.
  • 11634 - Capstone Planning Seminar MCDS students would have to work on a capstone project spanning two semesters. This course covered how to produce documentations for a data science project and also helped the students to match with project mentors. Generally, the contents provided a good structure to plan out the capstone projects from scratch. However, a lot of documentation was required for submission with tight deadlines and this took some time away from the actual discussions of the projects.
  • 15640 - Distributed Systems It was an introductory course on how to design and implement scalable distributed systems. The Spring version was taught in C and Java unlike the Fall version which was in Golang. I was fortunate to be taught by Professor Satya who implemented Andrew File System. The projects included implementing RPC, a distributed caching system, a scalable web service and the two-phased commit protocol. Some ideas overlap with 15645 such as transactions and logging. The projects were easier too. I spent about half the time on each project compared to 15645. My lecture notes can be found here.
  • 15719 - Advanced Cloud Computing Rather than how to use cloud technologies, the course covered how each type is implemented. I really liked the course as now I have a clearer understanding on the abstractions behind services on the cloud providers. For a better understanding, I really recommend going through the original papers provided as the compulsory reading materials as the lecture notes condenses them too much. It would also be helpful for system design interviews as the papers explains the complexities behind each system very well. In terms of assessment, the exams tested the application of each concept in scenarios. The projects on Spark were challenging but the other projects on Terraform and Kubernetes were easy for a “systems course”. For MCDS students who took 15513, the course can be taken to replace 15619 - Cloud Computing.

Fall 2023

  • 11632 / 11635 - Data Science Capstone The time was meant for implementing the proposals that were submitted during 11634. Weekly standups had to be submitted in the form of videos and google forms. Every few weeks, the teams had to present the updates to Professor Nyberg. One recommendation is to be very particular about planning, especially to have roadmaps with timelines and task assignments. The workload varies extremely widely across each team. The same mentors are usually involved with multiple batches of MCDS students so I would highly recommend talking to the previous teams beforehand.
  • 15641 - Computer Networks One of my favourite courses in CMU. Professor Sherry is a very friendly and engaging person which led to a lot of participation in class. The contents included a deep dive into each layer of TCP-IP model and network security. Even though I took a networking class before during undergraduate, I still found the projects exciting to work on. People could choose to work in pairs, but it was still very manageable to work on them alone. It was more challenging than 15640 but less than 15645. Projects were in C and included:
    • Mixnet: Distributed spanning tree with link state routing to transport frames across nodes.
    • TCP: Implementing features of TCP over UDP sockets. Writing 3-way handshake, flow control and congestion control from scratch really made the concepts stuck in my head.
    • HTTP: Single-threaded HTTP server with Linux epoll to handle multiple clients.
  • 17663 - Programming Language Pragmatics The course spent about 60% on formally proving the properties of programming languages and 40% on the compiler implementation. As someone with little theory background, this was the toughest course for the semester. The proofs had to be written in SASyLF which checked the correctness during compilation. This short feedback loop made the learning process easier. The compiler was to be written in OCaml and transforms a subset of TypeScript into WebAssembly code. As a systems student, this component was a lot more engaging to me but I can now also appreciate the importance of formal reasoning. From my impression, it seemed that the other undergraduate students with more theory background found the course relatively easy.
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/data-structures-behind-git/index.html b/posts/data-structures-behind-git/index.html new file mode 100644 index 0000000..135d32c --- /dev/null +++ b/posts/data-structures-behind-git/index.html @@ -0,0 +1,137 @@ + Data Structures Behind Git | Phyo's Log
Post

Data Structures Behind Git

The underlying data structure of a Git repository is just a directed acylic graph (DAG). Not only the core idea is simple, the implementation can be easily inspected in the .git directory. Let’s break it down.

If Git is a graph, what are the “nodes”?

There are three types of “nodes”, also known as Git Objects - Blobs, Trees and Commits. The article will run through an example usage of Git so that we can observe how each of them is created.

Empty Git repository

After initializing an empty Git repository, the .git directory is shown below. For the rest of the article, our focus will be on the .git/objects directory.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
.
+|____branches
+|____config
+|____description
+|____HEAD
+|____hooks
+| |____applypatch-msg.sample
+| |____commit-msg.sample
+| |...
+|____info
+| |____exclude
+|____objects       <--- Our focus
+| |____info
+| |____pack
+|____refs
+| |____heads
+| |____tags
+

Git Object - Blob

We will create a new file and add it to staging.

1
+2
+
echo "Hello World" > hello.txt
+git add hello.txt
+

Now we can find a new directory and file in .git/objects.

1
+2
+3
+4
+5
+6
+7
+
.
+|____objects
+| |____55
+| | |____7db03de997c86a4a028e1ebd3a1ceb225be238
+| |____info
+| |____pack
+...
+

Note that the concatenation of the directory name and file name is a 20-byte hash digest.

If we inspect the file, we will see that it is not a human-readable format.

1
+
cat .git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
+
1
+
xKOR04bH/IAI
+

This is because Git stores content in a compressed binary format. If we uncompress with the appropriate algorithm, the contents can be seen as below. This Git Object is indicated to be a 12-byte Blob with data being Hello World\n.

1
+
blob 12Hello World
+

Remember the 20-byte hash digest? To produce it, Git has actually run SHA-1 on the uncompressed data shown above. In other words, a Blob is simply contents of one file and is identified by SHA-1 hash of its contents.

Note that our Blob does not contain any information about the file name hello.txt.

Inspecting Git Objects

Convenient to us, Git provides APIs to inspect the compressed data in Git Objects.

1
+2
+3
+4
+5
+
# Inspect the type of Git Object
+git cat-file -t 557db
+
+# Inspect the content of Git Object
+git cat-file -p 557db
+
1
+2
+3
+4
+5
+
# Type of Git Object
+blob
+
+# Content of Git Object
+Hello World
+

Git Object - Tree

Now that we understand what a Blob is, let’s create a new commit.

1
+
git commit -m "First commit"
+

Looking at .git/objects again, there are two new Git Objects created.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
.
+|____objects
+| |____55
+| | |____7db03de997c86a4a028e1ebd3a1ceb225be238
+| |____97
+| | |____b49d4c943e3715fe30f141cc6f27a8548cee0e  <-- New file 1
+| |____c5
+| | |____5df28adf8320cc4d15637b82e8a0b13422d955  <-- New file 2
+...
+

If we inspect 97b49 with cat-file, the Git Object type and its contents are shown below.

1
+2
+3
+4
+5
+
# Type of Git Object
+tree
+
+# Content of Git Object
+100644 blob 557db03de997c86a4a028e1ebd3a1ceb225be238       hello.txt
+

It can be seen that this particular Git Object is a Tree. More specifically, it has a pointer to a Blob with hash digest 557db while naming it hello.txt. It also states that the file has a 644 permission.

In the example, the Tree has one Blob pointer but in reality it can have multiple Blob pointers and even other Tree pointers. In other words, a Tree simply contains pointers to other Git Objects and is identified by the SHA-1 hash of its contents.

Excluding the file names from Blobs is an intentional optimization by Git. If there are two files with duplicate content but different names, Git’s representation will be multiple pointers pointing to the same Blob.

Git Object - Commit

There is still one more Git Object. If we inspect c55df with cat-file, the results are shown below.

1
+2
+3
+4
+5
+6
+7
+
# Type of Git Object
+commit
+
+# Content of Git Object
+tree 97b49d4c943e3715fe30f141cc6f27a8548cee0e
+author yarkhinephyo <yarkhinephyo@gmail.com> 1652402598 +0800
+committer yarkhinephyo <yarkhinephyo@gmail.com> 1652402598 +0800
+

It can be seen that a Commit contains a pointer to a single Tree encompassing the contents of the commit and other bookkeeping details (such as author and timestamp). Similar to other Git Objects, a Commit is also identified by the SHA-1 hash of its contents.

Putting it together - Directed acyclic graph

1
+2
+3
+
echo "Hello World" > hello.txt
+git add hello.txt
+git commit "First commit"
+

Considering all the pointers, the Git Objects resulting from these commands can be represented as a graph.

DAG after first commit - Diagram by author

This is essentially the data structure powering Git repositories, stored right in the .git/objects directory as compressed binary files.

Adding a new commit

Let’s see what happens with a new commit. We will modify hello.txt and add new_file.txt in the second commit.

1
+2
+3
+4
+
echo "Bye" >> hello.txt
+echo "I love git" > new_file.txt
+git add hello.txt new_file.txt
+git commit -m "Second commit"
+

If we look at the .git/objects directory and inspect the new Git Objects with cat-file tool, it is possible to manually update the graph.

DAG after second commit - Diagram by author

There are two interesting observations.

First, the new Commit has a pointer to the parent Commit in its contents. This means that whatever is in the ancestor Commits affects the SHA-1 calculation of the new Commit. Therefore, as long as we have the SHA-1 calculation of the latest commit, the integrity of Git history can be verified.

Second, a new Blob is created after hello.txt has been modified and a new Tree stores a pointer to it. This is because Git Objects are immutable. Whatever changes made in a new commit would not mutate the previous Git Objects and modify the SHA-1 calculations.

Merkle DAG

This DAG where each node has an identifier resulting from hashing its contents is called Merkel DAG. This data structure also plays an important role in Web3 applications.

Resources

  1. Advanced Git: Graphs, Hashes and Compression by InfoQ
  2. Git Merkle DAG by John Williams
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/git-submodules-cheatsheet/index.html b/posts/git-submodules-cheatsheet/index.html new file mode 100644 index 0000000..6974b07 --- /dev/null +++ b/posts/git-submodules-cheatsheet/index.html @@ -0,0 +1,23 @@ + Git Submodules Cheatsheet | Phyo's Log
Post

Git Submodules Cheatsheet

Git Submodules allows one Git repository to be a subdirectory of another. I keep forgetting the commands so I have created a 2-minute refresher for my future reference.

Adding a submodule

To add a submodule to a project, run the command as shown below. Git will clone the submodule to the path provided and create a new .gitmodules file to store the information.

1
+
git submodule add <remote-url> <path-to-module>
+

Note that the <path-to-module> is now tracked by the parent repository as a commit ID instead of a subdirectory of contents. Treat it as a file for all practical purposes.

1
+2
+
git add <path-to-module> .gitmodules
+git commit -m "Added submodule"
+

Pushing an updated submodule

Only the submodule’s commit ID is inspected by the parent repository. When the submodule’s commit is modified, the parent repository will react similarly to how a file has been modified. Add the modified “file” to staging and commit as usual.

1
+2
+
git add <path-to-module>
+git commit -m "Updated submodule"
+

Pulling an updated submodule

After pulling changes from the parent repository, only the submodule’s tracked commit ID will be updated, not its contents. Manually update the contents of the submodule to synchronize with the updated commit ID.

1
+2
+3
+4
+5
+
# This updates the commit IDs of submodules
+git pull origin main
+
+# Update the contents of the submodules
+git submodule update --init --recursive
+

Cloning a repository containing submodules

Add a --recursive flag.

1
+
git clone --recursive <module>
+

Resources

  1. Git Tools Submodules by Git Scm
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/how-the-same-origin-policy-mitigates-csrf/index.html b/posts/how-the-same-origin-policy-mitigates-csrf/index.html new file mode 100644 index 0000000..a6cf527 --- /dev/null +++ b/posts/how-the-same-origin-policy-mitigates-csrf/index.html @@ -0,0 +1,7 @@ + How the Same-Origin Policy Mitigates CSRF | Phyo's Log
Post

How the Same-Origin Policy Mitigates CSRF

What is CSRF?

A cross site request forgery (CSRF) attack occurs when a web browser is tricked into executing an unwanted action in an application that a user is logged in.

For example, User A may be logged onto Bank XYZ in the browser which uses cookies for authentication. Let’s say the a transfer request like this -

1
+
GET http://bank-zyz.com/transfer?from=UserA&to=UserB&amt=100 HTTP/1.1
+

Then a malicious actor can embed a request with similar signatures inside an innocent looking hyperlink.

1
+
<a href="http://bank-xyz.com/transfer?from=UserA&to=MaliciousActor&amt=10000 HTTP/1.1></a>
+

If User A clicks on the hyperlink, the web browser sends the request together with the session cookie. The funds are then unintentionally transferred out of User A’s account.

When the same-origin-policy does not help with CSRF

The same-origin policy prevents a page from accessing results of cross domain requests. It prevents a malicious website from accessing another website’s resources such as static files and cookies.

Even though the policy prevents cross-origin access of resources, it does not prevent the requests from being sent.

In the Bank XYZ example, a GET request with relevant cookies triggers the server to transfer the funds before returning the 200 OK response. As shown in the diagram below, the same-origin policy only prevents the access of resouces, which in this case is reading the HTTP response. Since the request (with cookies) can still be sent, the hyperlink can still trigger the transfer of funds.

GET request sequence Image by Author - The policy would only prevent a cross-origin access of HTTP response (Step 3)

Note: For more complex HTTP requests, a preflight OPTIONS request is sent beforehand to check for relevant CORS headers. In that scenario, an unexpected cross-origin request will not reach Bank XYZ’s website at all.

How the same-origin-policy actually mitigates CSRF

To prevent CSRF, Bank XYZ can generate an unpredictable token for each client which is validated in the subsequent requests. For example, a hidden HTML field can allow the token to be included in subsequent form submissions.

1
+
<input type="hidden" name="csrf-token" value="CIwNZNlR4XbisJF39I8yWnWX9wX4WFoz" />
+

Other websites running in User A’s browser do not have access to the form field due to the same-origin-policy. So malicious scripts from other origins can no longer make the same request to transfer funds.

Resources

  1. StackOverFlow on why SOP is not enough not prevent CSRF
  2. CSRF by Imperva
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/index.html b/posts/index.html new file mode 100644 index 0000000..3a9e350 --- /dev/null +++ b/posts/index.html @@ -0,0 +1,11 @@ + + + + Redirecting… + + + + +

Redirecting…

+ Click here if you are not redirected. + diff --git a/posts/intuition-behind-the-attention-head/index.html b/posts/intuition-behind-the-attention-head/index.html new file mode 100644 index 0000000..7354549 --- /dev/null +++ b/posts/intuition-behind-the-attention-head/index.html @@ -0,0 +1,35 @@ + Intuition Behind the Attention Head of Transformers | Phyo's Log
Post

Intuition Behind the Attention Head of Transformers

Even as I frequently use transformers for NLP projects, I have struggled with the intuition behind the multi-head attention mechanism outlined in the paper - Attention Is All You Need. This post will act as a memo for my future self.

Limitation of only using word embeddings

Consider the sequence of words - pool beats badminton. For the purpose of machine learning tasks, we can use word embeddings to represent each of them. The representation can be a matrix of three word embeddings.

If we take a closer look, the word pool has multiple meanings. It can mean a swimming pool, some cue sports or a collection of things such as money. Humans can easily perceive the correct interpretation because of the word badminton. However, the word embedding of pool includes all the possible interpretations learnt from the training corpus.

Can we add more context to the embedding representing pool? Optimally, we want it to be “aware” of the word badminton more than the word beats.

My intuition behind the self-attention mechanism

Consider that matrix A represents the sequence - pool beats badminton. There are three words (rows) and the word embedding has four dimensions (columns). The first dimension represents the concept of sports. Naturally, we expect the words pool and badminton to have more similarity in this dimension.

Matrix-A Diagram by Author

1
+2
+3
+4
+5
+
A = np.array([
+  [0.5, 0.1, 0.1, 0.2],
+  [0.1, 0.5, 0.2, 0.1],
+  [0.5, 0.1, 0.2, 0.1],
+])
+

If we do a matrix multiplication between A and AT, the resulting matrix will be the dot-product similarities between all possible pairs of words. For example, the word pool is more similar to badminton than the word beats. In other words, this matrix hints that the word badminton should be more important than the word beats when adding more context to the word embedding of pool.

Similarity Diagram by Author

1
+2
+3
+4
+5
+
A_At = np.matmul(A, A.T)
+>>> A_At
+array([[0.31, 0.14, 0.3 ],
+       [0.14, 0.31, 0.15],
+       [0.3 , 0.15, 0.31]])
+

By applying the softmax function across each word, we can ensure that these “similarity scores” add up to 1.0.

The last step is to do another matrix multiplication with matrix A. In a way, this step consolidates the contexts of the entire sequence to each embedding in an “intelligent” manner. In the example below, both embeddings of beats and badminton are added to pool but with different weights depending on their similarities with pool.

Result Diagram by Author

1
+2
+3
+4
+5
+6
+7
+
output = np.round(
+    np.matmul(softmax(A_At, axis=1), A)
+, 2)
+>>> output
+array([[0.38, 0.22, 0.16, 0.14],
+       [0.35, 0.25, 0.17, 0.13],
+       [0.38, 0.22, 0.17, 0.13]])
+

Notice that the output matrix has the same dimensions (3 x 4) as the original input A. The intuition is that each word vector is now enriched with more information. This is the gist of the self-attention mechanism.

Scaled Dot-Product Attention

The picture below shows the Scaled Dot-Product Attention from the paper. The core operations are the same as the example we explored. Notice that scaling is added before softmax to ensure stable gradients, and there is an optional masking operation. Inputs are also termed as Q, K and V.

Result Image taken from Attention Is All You Need paper

The Scaled Dot-Product Attention can be represented as attention(Q, K, V) function.

Scaled-dot-product Diagram by Frank Odom on Medium

Adding trainable weights with linear layers

The initial example that we use can be represented as attention(A, A, A), where matrix A contains the word embeddings of pool, beats and badminton. So far there are no weights involved. We can make a simple adjustment to add trainable parameters.

Imagine we have (m x m) matrices MQ, MK and MV where m matches the dimension of word embeddings in A. Instead of passing matrix A directly to the function, we can calculate Q = A MQ, K = A MK and V = A MV which will be the same sizes as A. Then we apply attention(Q, K, V) afterwards. In neural network, this is akin to adding a linear layer before each input into the Scaled Dot-Product Attention.

To complete the Single-Head Attention mechanism, we just need to add another linear layer after the output from the Scaled Dot-Product Attention. The idea of expanding to the Multi-Head Attention in the paper is relatively simpler to grasp.

Single-head Diagram by Frank Odom on Medium

Resources

  1. Series on Attention by Rasa Algorithm Whiteboard
  2. The Illustrated Transformer by Jay Alammer
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/javascript-module-ecosystem/index.html b/posts/javascript-module-ecosystem/index.html new file mode 100644 index 0000000..538a668 --- /dev/null +++ b/posts/javascript-module-ecosystem/index.html @@ -0,0 +1,163 @@ + Quick History of JavaScript Module Ecosystem | Phyo's Log
Post

Quick History of JavaScript Module Ecosystem

IIFE - Initial Concept of JS Modules

Immediately-invoked Function Expression are anonymous functions that wrap around code blocks to be imported. In the example below, the inner function sayHi() cannot be accessed outside the anonymous function. The anonymous function itself also does not have a name so it does not pollute the global scope.

1
+2
+3
+4
+5
+6
+7
+8
+
// script1.js
+(function () {
+    var userName = "Steve";
+    function sayHi(name) {
+        console.log("Hi " + name);
+    }
+    sayHi(userName);
+})();
+

If this script is included as shown below, no variable name collision can occur with other scripts such as script2.js.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
<!DOCTYPE html>
+<html>
+    <head>
+        <title>JavaScript Demo</title>
+        <script src="script1.js"></script>
+        <script src="script2.js"></script>
+    </head>
+    <body>
+        <h1>IIFE Demo</h1>
+    </body>
+</html>
+

Problems with IIFE

What if script2.js wants to use the sayHi() function defined in script1.js? We can pass a common global variable through the two IIFE modules as shown below.

1
+2
+3
+4
+5
+6
+7
+
// script1.js
+(function (window) {
+    function sayHi(name) {
+        console.log("Hi " + name);
+    }
+    window.script1 = { sayHi };
+})(window);
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
// script2.js
+(function (window) {
+    function sayHiBye(name) {
+        window.script1.sayHi(name);
+        console.log("Bye " + name);
+    }
+    var userName = "Jenny";
+    sayHiBye(userName);
+})(window);
+

This solves the immediate problem, but generates other issues.

If we reorder script1.js and script2.js, the code will break as the window object will not have the script1 object by the time script2.js starts to load.

There is also the problem of what common variable to pass between the two IIFE. One company may use the window object but another may create a new app object in the global scope. No strict standards means incompatiblity issues.

CommonJS - Solving the problems of IIFE

CommonJS is a series of specifications for development of JavaScript applications in non-browser environments. One of the specifications is the API for importing and exporting of modules. This is where require() and module.exports are introduced.

There is no more need for passing around a global variable or wrapping an anonymous function around every code blocks for export.

1
+2
+3
+4
+5
+
// script1.js
+function sayHi(name) {
+    console.log("Hi " + name);
+}
+module.exports.sayHi = sayHi;
+
1
+2
+3
+4
+5
+6
+7
+8
+
// script2.js
+script1 = require("./script1.js");
+function sayHiBye(name) {
+    script1.sayHi(name);
+    console.log("Bye " + name);
+}
+var userName = "Jenny";
+sayHiBye(userName);
+

However, CommonJS was not meant for the browser environment. The specifications also do not support asychronous loading of the modules which is important in the browser environment for the user experience.

Module Bundler - CommonJS style modules in the Browser

Module bundlers such as Webpack solves the incompatibility problem by bundling CommonJS modules for usage in the browser. The modules are loaded into a single bundle.js file such that individual dependencies are satisfied, which can be loaded onto the page with the a single <script> tag.

For the example above, webpack can produce a single bundle.js with script2.js as an entry. The bundle will include script1.js first as it understands the dependency graph. By including the bundle.js into HTML as shown below, the abovementioned problems with CommonJS are fixed.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
<!DOCTYPE html>
+<html>
+    <head>
+        <title>JavaScript Demo</title>
+        <script src="bundle.js"></script>
+    </head>
+    <body>
+        <h1>Webpack Demo</h1>
+    </body>
+</html>
+

ES6 - Module system as part of JavaScript standard

ES6 is a JavaScript standard introduced in 2015 that finally introduced a module system for JavaScript in the browsers. ES6 modules utilize import and export keywords. Unlike CommonJS, webpack is not necessary for browser compatibility. We only need to add a type="module" attribute inside the HTML <script> tag and everything will work out of the box.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
<!DOCTYPE html>
+<html>
+    <head>
+        <title>JavaScript Demo</title>
+        <script type="module" src="script2.js"></script>
+    </head>
+    <body>
+        <h1>ES6 Demo</h1>
+    </body>
+</html>
+
1
+2
+3
+4
+5
+
// script1.js
+function sayHi(name) {
+    console.log("Hi " + name);
+}
+export default { sayHi };
+
1
+2
+3
+4
+5
+6
+7
+8
+
// script2.js
+import script1 from './script1.js';
+function sayHiBye(name) {
+    script1.sayHi(name);
+    console.log("Bye " + name);
+}
+var userName = "Jenny";
+sayHiBye(userName);
+

Why are bundlers still used for browser scripts?

Backward Compatibility: ES6 modules are not recognized in the older versions of the browsers. Bundlers allow developers to work with the more modern ES6 syntax while the code remains compatible with older browsers.

Size Reduction: Minifying code with bundlers reduces file sizes which will lead to faster page loads.

Code Splitting: Bundlers can split code into chunks which can then be loaded on demand or in parallel.

Caching Support: Webpack can be configured to name the bundles with the hash of their contents. Browsers will only fetch scripts from the server if the hashes no longer match.

Resources

  1. JavaScript Modules by uidotdev
  2. ES6 Modules in the Browser by David Gilbertson
  3. JavaScript Module Systems Showdown by Auth0
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/jvm-garbage-collectors/index.html b/posts/jvm-garbage-collectors/index.html new file mode 100644 index 0000000..bc03f55 --- /dev/null +++ b/posts/jvm-garbage-collectors/index.html @@ -0,0 +1,33 @@ + JVM Garbage Collectors - Serial, Parallel, CMS | Phyo's Log
Post

JVM Garbage Collectors - Serial, Parallel, CMS

Depending on the resources available and the performance metric of an application, different Garbage Collectors (GC) should be considered for the underlying Java Virtual Machine. This post explains the main idea behind the garbage collection process in JVM and summarizes the pros and cons of Serial GC, Parallel GC and Concurrent-Mark-Sweep GC.

Garbage-First GC (G1) is out-of-scope for this post as it works very differently from the other algorithms (and I still have not wrap my head around it). This post also assumes familiarity with heap memory.

Garbage Collection Algorithms

These symbols will be used to illustrate the memory allocation in heap.

1
+2
+3
+
o       - unvisited
+x       - visited
+<empty> - free
+

Mark-Sweep: The objects in the heap that can be reached from root nodes (such as stack references) are marked as visited. While sweeping the memory, the regions occupied by the unvisited objects are updated to be free. As there are likely to be less contiguous regions after a collection, external fragmentation is likely to occur.

1
+2
+
Marked     | x |o| x |  o  |x|
+Sweeped    | x | | x |     |x|
+

Mark-Sweep-Compact: After marking, the visited objects are identified and compacted to the beginning of the memory region. This solves the external fragmentation issue, but more time is required as objects have to be moved and references have to be updated accordingly.

1
+2
+3
+
Marked     | x |o| x |  o  |x|
+Sweeped    | x | | x |     |x|
+Compacted  | x | x |x|       |
+

Mark-Copy: After marking, the visited objects are relocated to another region. This accomplishes compaction of allocated memory at the same step. However, the disadvantage is that there is a need to maintain one more memory region.

1
+2
+
Marked     | x |o| x |  o  |x|                 |
+Copied     |                 | x | x |x|       |
+

During parts of a garbage collection, all application threads may be suspended. This is called stop-the-world pause. Long pauses are especially undesirable in interactive applications.

Generational Garbage Collection in JVM

The Weak Generational Hypothesis states that most objects die young.

In JVM, heap memory is divided into two regions - Young Generation and Old Generation. Newly created objects are stored in the Young Generation and the older ones are promoted to the Old Generation. With this separation, GC can work more often in a smaller region where dead objects are more likely to be found.

1
+2
+3
+4
+5
+6
+
<- Young Generation ->
++--------------------+--------------------+
+|    Eden Space      |                    |
++----------+---------+   Old Generation   |
+|    S0    |    S1   |                    |
++----------+---------+--------------------+
+

Young Generation: The region is divided into Eden Space where new objects are created, and S0/S1 Space where the visited objects from each garbage collection can be copied to. Naturally, Mark-Copy algorithm is used.

Old Generation: As there is no delimited region for the visited objects to be copied to. Only Mark-Sweep and Mark-Sweep-Compact algorithms can be used.

Serial GC

JVM option: -XX:+UseSerialGC

This option uses Mark-Copy for the Young Generation and Mark-Sweep-Compact for the Old Generation. Both of the collectors are single-threaded. Without leveraging multiple cores present in modern processors, the stop-the-world pauses are longer. The advantage is that there is less resource overhead compared to other options.

Parallel GC

JVM option: -XX:+UseParallelGC -XX:+UseParallelOldGC

Similary to Serial GC, this option uses Mark-Copy for the Young Generation and Mark-Sweep-Compact for the Old Generation. Unlike Serial GC, multiple threads are run for the respective algorithms. As less time is spent on garbage collection, there is higher throughput for the application.

Concurrent-Mark-Sweep (CMS) GC

JVM option: -XX:+UseParNewGC -XX:+UseConcMarkSweepGC

For the Young Generation, this option is the same as Parallel GC. For the Old Generation, this option runs most of the job in Mark-Sweep concurrently with the application. This means that the application threads continue running during some parts of the garbage collection. Hence this option is less affected by stop-the-world pauses compared to the other two, making it preferred for interactive applications.

As at least one thread is used for garbage collection all the time, the application has lower throughput. Without compaction, external fragmentation may also occur. When this happens, there is a fallback with Serial GC but it is very time-consuming.

Resources

  1. Java Garbage Collection by Plumbr
  2. Garbage Collection in Java by Ranjith
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/kubernetes-networking-intro-and-deep-dive/index.html b/posts/kubernetes-networking-intro-and-deep-dive/index.html new file mode 100644 index 0000000..bd3a62b --- /dev/null +++ b/posts/kubernetes-networking-intro-and-deep-dive/index.html @@ -0,0 +1,39 @@ + Learning Points: Kubernetes Networking Deep Dive | Phyo's Log
Post

Learning Points: Kubernetes Networking Deep Dive

This video is about how networking works in Kubernetes by Bowei Du and Tim Hockin from Google.

Networking APIs Exposed by Kubernetes

  • Service, Endpoint: Service registration and discovery.
  • Ingress: L7 HTTP routing.
  • Gateway: Next-gen HTTP routing and service ingress.
  • NetworkPolicy: Application firewall.

Pod Networking Model

All pods can reach all other pods across nodes. The network drivers on each node and networking between pods are implemented by Kubelet CNI implementation.

One implementation is using hosts as a router device while a routing entry is added for each pod. For example, Flannel host-gw mode and Calico BGP mode. Another implementation is using overlay networks where layer 2 frames are encapsulated into layer 4 UDP packets alongside a VxLAN header. For example, Flannel and Calico VxLAN mode.

Service API Implementation

Pod IP addresses are ephemeral. Service API exposes a group of pods via one IP (ClusterIP). This is how the API works:

  • A client pod sends a DNS query of to KubeDNS service.
  • The KubeProxy sends the query to a KubeDNS pod which returns the ClusterIP.
  • The client sends a packet to the ClusterIP.
  • The KubeProxy intercepts the packet and sends it to one of the server pods.
  • If the server pod goes down, the client can retry with the same ClusterIP.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
kind: Service
+apiVersion: v1
+metadata:
+  name: my-service
+  namespace: default
+spec:
+  selector:
+    app: my-app
+  ports:
+    - port: 80 # for clients
+    - targetPort: 9376 # for backend pods
+

KubeProxy runs on every node in the cluster. It uses iptables, IPVS or userspace options to proxy traffic from pods.

KubeProxy control plane accumulate changes to Endpoints and Services, then updates rules in the node. In the data plane, the sending KubeProxy recognizes ClusterIP/port and rewrites packets to the new destination (DNAT). The recipient KubeProxy un-DNAT the packets.

To disambiguate, CNI ensures the Pod IPs work. KubeProxy redirects ClusterIP to Pod IP before sending over the network.

Endpoint API Implementation

Endpoint objects are a list of IPs behind a Service. An Endpoint Controller manages them automatically.

When a Service object is created, an Endpoint object is created that has a mapping of service name to pod addresses and ports. This object is fed into the rest of the system such as KubeDNS and KubeProxy.

Ingress API Implementation

HTTP proxy and L7 routing rules that targets a service for each rule. Kubernetes define the API but implementations are all third party.

Unlike the Ingress API, Service-type load balancers only work at L4 level.

1
+2
+3
+4
+5
+6
+7
+8
+
Ingress {
+  hostname: foo.com
+  paths:
+  - path: /foo
+    service: foo-svc
+  - path: /bar
+    service: bar-svc
+}
+

Deep Dive: NodeLocal DNS

DNS resource cost is high. There are more microservice addressed by names and more application libraries tending to use DNS names. The solution is to run a DNS cache on every node.

The NodeLocal DNS implementation is deployed on each node as a Daemonset.

Dummy network interface is created that binds to ClusterIP address of KubeDNS. Linux NOTRACK target is added with KubeDNS ClusterIP before any KubeProxy rules. This ensures that NodeLocal DNS can process the packets without them reaching KubeProxy.

A watcher process removes the NOTRACK entries in the event that NodeLocalDNS fails. This defaults back to the original KubeDNS infrastructure.

Deep Dive: EndpointSlice

Endpoint objects are stored in the Etcd database. When one pod IP changes, the entire object has to be redistributed to all the KubeProxy. If Endpoint objects are large, it may also hit the maximum storage limit in Etcd.

The solution is to represent one original Endpoint object with a set of EndpointSlice objects. A single update to pod IP will only require redistribution of one EndpointSlice object.

The EndpointSlice controller slices from a Service object to create EndpointSlice objects.

Interesting optimization problem:

  • Keep number of slices low.
  • Minimize changes to slices per update.
  • Keep the amount of data sent low.
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/primer-to-sift/index.html b/posts/primer-to-sift/index.html new file mode 100644 index 0000000..1f58e5a --- /dev/null +++ b/posts/primer-to-sift/index.html @@ -0,0 +1 @@ + Primer to Scale-invariant Feature Transform | Phyo's Log
Post

Primer to Scale-invariant Feature Transform

Scale-invariant Feature Transform, also known as SIFT, is a method to consistently represent features in an image even under different scales, rotations and lighting conditions. Since the video series by First Principles of Computer Vision covers the details very well, the post covers mainly my intuition. The topic requires prior knowledge on using Laplacian of Gaussian for edge detection in images.

Why extract features?

Image by First Principles of Computer Vision

Consider the two images. How can the computer recognize that the object in the left is included inside the image on the right? One way is to use template-based matching where the left image is overlapped onto the right. Then some form of similarity measure can be calculated as it is shifted across the right image.

Problem: To ensure different scales are accounted for, we would need templates of different sizes. To check for different orientations, we would need a template for every unit of angle. To overcome occlusion, we may even need to split the left image into multiple pieces and check if each of them matches.

For the example above, our brains recognize the eye and the faces to locate the book. Our eyes do not scan every pixel, and we are not affected by the differences in scale and rotation. Similarly it will be great if we can 1) extract only interesting features from an image and 2) transform them into representations that are consistent across different scenes.

Good requirements for feature representation

Points of Interest: Blob-like features with rich details are preferred over simple corners or edges.

Insensitive to Scale: The feature representation should be normalized to its size.

Insensitive to Rotation: The feature representation should be able to undo the effects of rotation.

Insensitive to Lighting: The feature representation should be consistent under different lighting conditions.

Blob detection - Scale-normalized points of interest

Image from Princeton CS429 - 1D edge detection

In traditional edge detection, a Laplacian operator can be applied to an image through convolution. Edges can be identified from the ripples in the response.

Image from Princeton CS429 - 1D blob detection

If multiple edges are at the right distance, there will be a single strong ripple caused by constructive interference. If this response is sufficiently strong, the location is identified as a blob representing a feature. Intuitively, complex features will be chosen compared to simple edges as constructive interferences cannot be produced by single edges.

From the same diagram, we can also see that not all collection of edges result in singular ripples with a particular Laplacian operator. By increasing the sigma (σ) of the Laplacian (making the kernel “fatter”), the constructive interference will occur when edges are further apart. If we apply the Laplacian operators many times with varying σ’s, blobs of different scales can be identified each time.

Image from Princeton CS429 - Increasing σ to identify larger blobs

Wait but if the σ is larger, the Laplacian response will be weaker (shown above). Intuitively, if the responses by larger blobs are penalized for their sizes. Does that means the selected features will be mostly tiny?

Image from Princeton CS429 - Normalized Laplacian of the Gaussian (NLoG)

We solve this by multiplying the Laplacian response with σ2 for normalization. (This works out because the Laplacian is the 2nd Gaussian derivative) Intuitively, this means that the response now only indicates the complexity of the features without any effect from their sizes.

3 x 3 x 3 kernels to find local extremas

Imagine the Laplacian response represented as a matrix with x-y plane for image dimensions and z axis for various σ. We can slide an n x n x n kernel to find the local extremas. The resulting x-y coordinates would represent the centers of the blobs and σ would correspond to their sizes.

With this technique, blobs can be extracted to represent complex features with the sizes normalized.

Countering the effects of rotation

Image from Princeton CS429

To assign an orientation to each feature, it can be divided into smaller windows as shown above. Then the pixel gradient for each window can be computed to produce a histogram of gradient directions. The most prominent direction can be assigned as the principle orientation of the feature.

Image by Author

In the example above, blobs are identified in both images representing the same feature. The black arrows are the principle orientations. After rescaling the blob sizes with the corresponding σ’s, the effect of rotation is eliminated by aligning with respect to the principle orientations.

Countering the effects of lighting conditions

Image from Princeton CS429 - Pixels to SIFT descriptors

Instead of comparing each blob directly (pixel-by-pixel), we can produce a unique representation that is invariant to lighting conditions. As shown above, the image can be broken into smaller windows (4 x 4) where each histogram of the gradients is computed. If each histogram only consider 8 directions, there will be 8 dimensions per window. Even with only 16 windows per blob, each feature representation will be of 128 dimensions (16 x 8) which can be robust.

These feature representations are known more formally as SIFT descriptors.

Conclusion

Image from OpenCV documentation

For matching images, SIFT descriptors in two images can be directly compared against one another through similarity measurements. If a large number of them matches, it is likely that the same objects are observed in both images. In practice, nearest neighbor algorithms such as FLANN are used to match the features between images.

Resources

  1. SIFT Detector by First Principles of Computer Vision
  2. Feature Detectors and Descriptors - Princeton CS429
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/scaling-monitoring-with-m3/index.html b/posts/scaling-monitoring-with-m3/index.html new file mode 100644 index 0000000..88721cb --- /dev/null +++ b/posts/scaling-monitoring-with-m3/index.html @@ -0,0 +1,11 @@ + Learning Points: Scaling Monitoring from Prometheus to M3 | Phyo's Log
Post

Learning Points: Scaling Monitoring from Prometheus to M3

I enjoy watching 45 minutes to 1 hour long technical talks at conferences. Unfortunately, I am not retaining the knowledge as long as I would like to. From now on, I am going to try summarizing my takeaways for each videos to improve my own retention.

This video is about scaling monitoring from Prometheus to M3 at Databricks presented by YY Wan and Nick Lanham.

Context

Prometheus based monitoring system is used in Databricks since 2016. Most internal services run on Kubernetes and Spark workloads run in VMs in customer environments. PromQL is widely used by engineers.

In each region, there are two Prometheus servers, Prom-Normal and Prom-Proxied. Prom-Normal scrapes metrics from internal services in k8s pods. Metrics from external services are pushed by Kafka to the Metrics Proxy Service (on k8s). Prom-Proxied scrapes metrics from the Metrics Proxy Service. Having two servers also means metrics can be sharded logically (internal/external) as all the metrics would not fit on one. Disks are attached to each Prometheus server to store metrics.

Globally, there is a Prometheus server that contain a subset of metrics federated from all the regions.

Users interact with the monitoring system in two ways: alerting and querying. Regional Prometheus servers issue alerts to the Alert Manager Service which notifies engineers via PagerDuty. Users also query regional or global servers for insights.

Problem

50 regions across multiple cloud providers with 4 million VMs of Databricks services and Spark workers.

  • The global Prometheus server was huge. Disk usage of 4TB.
  • Big queries not completing due to frequent OOMs.
  • Short retention period, only 15 days.
  • Users have sharded view of metrics (Prom-Normal, Prom-Proxied).
  • Strict metrics whitelist to keep the servers running.
  • Metrics are lost during restarts as it takes a while to replay the large volume of logs per server.

Requirements

Must:

  • High metrics volume.
  • 90 days retention.
  • PromQL compatible.
  • Global view of metrics.
  • High availability setup.

Nice to have:

  • Good maintenance story, no metrics gap.
  • Open source.
  • Battle tested.

Why M3?

Why M3 solves the problem for Databricks:

  • Horizontally scalable.
  • High availability with multi-replica setup.
  • Exposes Prometheus API query endpoint.
  • Global querying feature.
  • Battle-tested at Uber production environment.
  • Exists k8s operator for automated deployments.

M3 Architecture

1
+2
+3
+4
+5
+
Application --- M3 collector
+                    |
+                M3 aggregator
+                    |
+                  M3DB --- M3 query --- Grafana
+
  • M3DB is a distributed time-series database, optimizations include time series compression. Sharding is also in-built.
  • M3 Query is a stateless query server that accepts M3QL or PromQL. Coordinates with servers in other regions to achieve global queries.
  • M3 Coordinator provides APIs to read and writing to M3DB. It also acts as a bridge with Prometheus.
  • M3 Aggregator provides streaming aggregation of time series data. It reduces cardinality and/or datapoint resolution to reduce the volume of data stored.

Initial Plan

Prom-Normal and Prom-Proxied remote-write data in M3DB instead of local disks.

  • For regional query, users only need to interact with regional M3DB instead of thinking about sharded view of Prom-Normal and Prom-Proxied metrics.
  • M3 Query server would also provide global view without requiring another global Prometheus server.
  • Least amount of work.

However, remote-writes by only two Prometheus servers could not achieved at a scale that Databricks required.

Small Components to Replace Prom-Normal and Prom-Proxied

More servers would achieve higher write throughput into M3DB.

To replace Prom-Normal, multiple Grafana Scrape Agents scrape metrics from internal services and write to M3DB.

To replace Prom-Proxied, Metrics Proxy Service directly writes to M3DB. Note that this service is already made up of multiple servers. This reduces end-to-end latency of external metrics too.

Update Alerting

Originally, the alerting rule configurations are used in Prometheus servers to issue alerts to Alert Manager Service.

Databricks built its own rule engine that takes the same configurations and interacts with M3DB and Alert Manager Service.

Noisy Neighbor Issues

M3 Coordinators were having noisy neighbor issues. If users submit heavy queries, the coordinators would not be able to serve the write paths from Metrics Proxy Service and Grafana Scrape Agents.

To solve this, M3 Coordinators were separately deployed for read and writes. CPU-heavy machines for write-coordinators and Memory-heavy machines for read-coordinators.

Monitoring the M3 Monitoring System

Vanilla Prometheus servers that scrape M3 related components. Metrics retention period is short but it is sufficient for the use case.

Global Prometheus server to federate metrics for all the Prometheus server.

Final Architecture

This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/snowflake-new-streams-talk/index.html b/posts/snowflake-new-streams-talk/index.html new file mode 100644 index 0000000..9e78e12 --- /dev/null +++ b/posts/snowflake-new-streams-talk/index.html @@ -0,0 +1,51 @@ + Learning Points: Snowflake Iceberg, Streaming, Unistore | Phyo's Log
Post

Learning Points: Snowflake Iceberg, Streaming, Unistore

This video is about Snowflake Iceberg Tables, Streaming Ingest and Unistore. The presenters are N.Single, T.Jones and A.Motivala as part of the Database Seminar Series by CMU Database Group.

Problems with Traditional Data Lakes

Traditional data lakes use file systems as the metadata layer. For example, data for each table is organized in a directory. Partitioning is implemented through nested directories. Using directories as database tables cause problems.

  • Not easy to provide ACID guarantees. Multiple partition inserts were not atomic.
  • Tools may directly access the file systems without consistent metadata updates.
  • Schema evolution was very error prone.
  • No clear way for access control.

Apache Iceberg

  • Describes how to perform updates to the table.
  • Specification to achieve snapshot isolation. File memberships are defined to a snapshot.
  • Easier schema evolution with Iceberg metadata.

Snowflake Architecture

Table metadata and data are stored as Parquet files on customers’ bucket.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
                 +-------------------------------------------------+
+  Cloud services | Authentication and Authorization                |
+                 +-------------------------------------------------+
+                 | Infra Manager | Optimizer | Transaction Manager |
+                 +-------------------------------------------------+
+                 | Metadata Storage (Customer's Bucket)            |
+                 +-------------------------------------------------+
+
+                 +-------------------+ +-------------------+
+  Compute        | Warehouse         | | Warehouse         |
+                 +-------------------+ +-------------------+
+
+                 +-------------------------------------------------+
+  Storage        | Data (Customer's Bucket)                        |
+                 +-------------------------------------------------+
+

Customers will have to provide Snowflake External Volumes on any the cloud providers with access credentials. Data and metadata files are written to the External Volume.

Metadata Generation

Snowflake has its own files to store snapshot metadata originally. To support Iceberg format, each table commit requires generation of both Iceberg metadata and internal Snowflake metadata.

The generation of additional metadata files (Iceberg) increases query latency significantly. Thus Iceberg metadata files are generated on the background at the same time.

When Snowflake metadata files are generated, the transaction is considered commited. If the server crashes before Iceberg metadata is generated, the request would come to the new Snowflake server and the Iceberg metadata will be generated on the fly.

How Spark Accesses Iceberg

The Iceberg SDK accesses a catalog which returns the location of metadata files in customers’ buckets. Then the SDK interprets the metadata files and returns the locations of data files in an API to Spark.

1
+2
+3
+4
+5
+
Spark ---> Iceberg SDK ---> 1. Catalog (Hive, Glue)
+  |            |
+  |            -----------> 2. Storage (Snapshot Metadata)
+  |            
+  ------------> 3. Data Files
+

Snowpipe Streaming

Before this feature, the original Snowpipe did continuous copying from a bucket to a table behind the scenes, in batches. However, there was no low latency, high throughput, in-order processing feature. Snowpipe Streaming provides:

  • Ingestion to tables over HTTPs.
  • Exactly once (?) and per-channel ordering guarantees.
  • Low latency, queryable after seconds.
  • High throughput, GB/s supported.
  • Low overhead for configuration.

New concepts include:

  • Channel - Logical partition that represents a connection from a single client to a destination table.
  • Client SDK - Accepts bytes from application, writes data to cloud storage as blobs and registers them to Snowflake.
  • Mixed table - Contains both BDEC (Chunks of Arrow format) that is written by the client SDK and Snowflake’s propriatory FDN format. In the background, the BDEC files are rewritten into FDN format. The rewriting process is transparent to the users as queries can be done on the mixture of BDEC and FDN files. However, the rewriting process implies additional compute which will be charged to the customer.

The implementation details:

  • User code uses the Snowpipe Streaming Client SDK to open a Channel and write rows in the Channel.
  • Client SDK writes BDEC files to the Streaming Ingest’s internal storage (Blobstore). Note that FDN files exist in the same Blobstore.
  • Client SDK registers the blob via REST API to Snowflake’s Frontend node.
  • Frontend node fans out per-table registration requests to the Snowflake’s Commit Service and provides a progress update to the client SDK.
  • The Commit Service validates and deduplicates chunks per-table in memory. Then it commits by changing table version references to the new Arrow chunks (BDEC).
  • Snowpipe creates regular FDN files from BDEC files. At this point, queries would reflect the newly added data in BDEC files.

Unistore

Snowflake’s product for combining transactional and analytical workload on one platform.

A new table type that works with existing snowflake tables, supports transactional features such as unique keys, referential integrity constraints and cross domain transactions.

1
+2
+3
+4
+5
+
CREATE HYBRID TABLE CustomerTable {
+  customer_id int primary key,
+  full_name varchar(256),
+  ...
+}
+
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/solving-http1-problems-with-http2/index.html b/posts/solving-http1-problems-with-http2/index.html new file mode 100644 index 0000000..d518bf5 --- /dev/null +++ b/posts/solving-http1-problems-with-http2/index.html @@ -0,0 +1,37 @@ + Solving HTTP/1 Problems With HTTP/2 | Phyo's Log
Post

Solving HTTP/1 Problems With HTTP/2

HTTP/2 has made our applications faster and more robust by providing protocol enhancements over HTTP/1. This post only focuses on the major pain points of HTTP/1 and how the new protocol has been engineered to overcome them. It is assumed that the reader is familiar with how HTTP/1 works.

Problems with HTTP/1

Head-of-line blocking: Browsers typically only allow 6 parallel TCP connections per domain. If the initial requests are not complete, the subsequent requests will be blocked.

Unnecessary resource utilization: With HTTP/1, a single connection is created for every request, even if multiple requests are directed to the same server. As the server has to maintain states for each connection, there is an inefficient utilization of resources.

Overhead of headers: Headers in HTTP/1 are in the human-readable text format instead of being encoded to be more space-efficient. As there are numerous headers in complex HTTP requests and responses, it can become a significant overhead.

Binary Framing “Layer”

In HTTP/2, there is a binary framing layer that exists between the original HTTP API exposed to the applications and the transport layer. The rest of TCP/IP stack is unaffected. As long as both client and server implements HTTP/2, the applications will also continue to function as usual.

Request and response multiplexing

In HTTP/1, each HTTP request creates a separate TCP connection as shown below.

1
+2
+3
+4
+
| - Application - | - Transport - |
+
+     request_1  -->  connection_1
+     request_2  -->  connection_2
+

In HTTP/2, the binary framing layer breaks down each request into units called frames. These frames are interleaved and sent to the transport layer as application data. The transport layer is oblivious to the process and carries on with its own responsibilities. At the server’s end, the binary framing layer reconstruct the requests from the frames.

1
+2
+3
+4
+5
+
| - Application ---------------- | - Transport - |
+                | Binary Framing |
+
+     request_1  --->  frames   --->  connection_1
+     request_2  -/  
+

HTTP/2 Frames

To be specific, each HTTP request is broken down into HEADERS frame and DATA frame/s. The names are self-explanatory. HEADERS frame include HTTP headers and DATA frame/s include the body.

The diagram below shows the structure of a frame.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
+-----------------------------------------------+
+|                 Length (24)                   |
++---------------+---------------+---------------+
+|   Type (8)    |   Flags (8)   |
++-+-------------+---------------+-------------------------------+
+|R|                 Stream Identifier (31)                      |
++=+=============================================================+
+|                   Frame Payload (0...)                      ...
++---------------------------------------------------------------+
+

HTTP/2 Streams

Notice that each HTTP/2 frame has an associated stream identifier which identifies each bidirectional flow of bytes. For example, all the frames in a single request-response exchange will have the same stream identifier.

This means that when frames from different requests are interleaved with one another, the receiving binary framing layer can reconstruct them back into independent streams.

Interleaving frames with different stream identifiers

In other words, the hierarchical relationship between connection, stream and frame can be represented as shown below.

Logical relationship between frames and streams

Header compression

Aside from multiplexing HTTP requests over a single TCP connection, HTTP/2 also provides a mechanism for header compression. Instead of transporting textual data, both server and client maintain identical lookup tables to remember the headers that have been used. In the subsequent communication, only the pointers into the lookup table are sent over the network. Tests have show that on average, the header size is reduced around 85%-88%.

Limitation

HTTP/2 solves the head-of-line blocking problem from parallel TCP connections. However, this creates another problem at the TCP level. Due to the nature of TCP implementation, one lost packet can make all the streams wait until the packet is re-transmitted and received.

HTTP/3 addresses this issue by communicating over QUIC (TCP-like protocol over UDP) instead of TCP.

Resources

  1. A Brief History of SPDY and HTTP/2 by Ilya and Surma
  2. HTTP2 by High Performance Browser Networking
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/speed-up-python-with-ctypes/index.html b/posts/speed-up-python-with-ctypes/index.html new file mode 100644 index 0000000..7765ad8 --- /dev/null +++ b/posts/speed-up-python-with-ctypes/index.html @@ -0,0 +1,287 @@ + Speed up Python applications With Ctypes Library | Phyo's Log
Post

Speed up Python applications With Ctypes Library

There are multiple ways to speed up computations in Python. The cython language compiles Python-like syntax into CPython extensions. Libraries such as numpy provides methods to manipulate large arrays and matrices efficiently with underlying C data structures.

In this post, I will be discussing the ctypes module. It provides C-compatible data types to so that Python functions can use C-compiled shared libraries. Therefore, we can offload computationally intensive modules of a Python application into C where the developers will have more fine-grained control. To my surprise, this comes as part of the Python standard library, so no external dependencies are required!

Code to optimize - prime number checker

I have created a sample program that we can speed up afterwards using the ctypes module. The num_primes() calculates the total number of primes in a list by looping through each item.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
# prime.py
+def is_prime(num: int):
+    for i in range(2, int(num**(0.5))):
+        if num % i == 0:
+            return 1
+    return 0
+
+def num_primes(num_list: List[int]):
+    count = 0
+    for num in num_list:
+        count += is_prime(num)
+    return count
+

Let’s see the number of primes in a list of 1 million integers. Note that we use consecutive numbers for the example but it does not have to be.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
from prime import num_primes
+
+MAX_NUM = 1000000
+num_list = list(range(MAX_NUM))
+
+def timeit_function():
+    return num_primes(num_list)
+
+print(timeit_function())
+

It takes around 3.4 seconds to run. How can we speed this up?

1
+2
+3
+
>>> python -m timeit -n 5 -s 'import test_python as t' 't.timeit_function()'
+Primes: 921295
+5 loops, best of 5: 3.4 sec per loop
+

Why Python threading module does not work

As Python has a threading module, one idea is to parallalize calculations across the entire list by using multiple threads. However, this is not possible due to Python’s Global Interpreter Lock (GIL), which prevents multiple threads in a process from executing Python bytecode at the same time. Hence for non-I/O operations, there will not be any speed up.

Rewriting prime checker in C

The prime checker is reimplemented in C as shown below, then compiled it into a shared library prime.so. Note that the program logic is exactly the same.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
// prime.c
+#include <stdio.h>
+#include <math.h>
+
+int is_prime(int num) {
+    for (int i=2; i<(int)sqrt(num); i++) {
+        if (num % i == 0)
+            return 1;
+    }
+    return 0;
+} 
+
+int num_primes(int arrSize, int *numArr) {
+    int count = 0;
+    for (int i=0; i<arrSize; i++)
+        count += is_prime(numArr[i]);
+    return count;
+}
+

Calling the C-compiled prime checker with ctypes

ctypes

The ctypes library provides C-compatible data types in Python. All we need to do is load the shared library with CDLL() API and then declare the parameters/ return types accordingly with argtypes and restype attributes.

1
+2
+3
+4
+5
+6
+7
+8
+
from ctypes import *
+
+# Load the shared library
+lib = CDLL("./libprime.so")
+# Declare the return data as 32-bit int
+lib.num_primes.restype = c_int32
+# Declare the arguments as a 32-bit int and a pointer for 32-bit int (for list)
+lib.num_primes.argtypes = [c_int32, POINTER(c_int32)]
+

Afterwards, the num_primes() in the shared library can be called! Note that the num_list has to be converted from Python list into a contiguous array of C with a method provided by ctypes.

1
+2
+3
+4
+5
+6
+7
+8
+
MAX_NUM = 1000000
+num_list = list(range(MAX_NUM))
+
+def timeit_function():
+    # num_list is converted into an integer array of size MAX_NUM
+    return lib.num_primes(MAX_NUM, (c_int32 * MAX_NUM)(*num_list))
+
+print(f"Primes: {timeit_function()}")
+

For the same input of 1 million integers, the speed up is significant just by offloading the same program logic to C code. It makes sense because contiguous arrays in C can leverage caching mechanisms better than lists in Python.

1
+2
+3
+
>>> python -m timeit -n 5 -s 'import test_ctypes as t' 't.timeit_function()'
+Primes: 921295
+5 loops, best of 5: 482 msec per loop
+

Multithreading in the C shared library with POSIX pthreads

There is one more benefit of offloading the work to C. Since the shared library is not under Python’s GIL, we can now use multithreading in C to parallelize the computations!

In the code below, the integer array is split evenly into 4 subarrays and 4 threads are spawned with POSIX pthreads to do parallel work. Each thread runs thread_function() to check the numbers in the array without any overlap between threads. The counts of prime numbers are added into countByThreads array which are summed up after the child threads have terminated.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+
#define NUM_THREADS 4                       // 4 threads used
+
+// Global variables for spawn threads to access
+int *gArrSize = 0;                          // Ptr for array size
+int *gNumArr = 0;                           // Ptr for input array
+int countByThreads[NUM_THREADS] = { 0 };    // Prime counts of each thread
+pthread_t tids[NUM_THREADS] = { 0 };        // IDs of each thread
+
+// Function run by each thread
+void *thread_function(void *vargp) {
+    // Each thread has a different offset
+    int offset = (*(int*) vargp);
+    int count = 0;
+    // Split the array items evenly across threads
+    for (int i=offset; i < *gArrSize; i+=NUM_THREADS)
+        count += is_prime(gNumArr[i]);
+    countByThreads[offset] += count;
+    free(vargp);
+}
+
+int num_primes(int arrSize, int *numArr) {
+    gArrSize = &arrSize;
+    gNumArr = numArr;
+    for(int i=0; i < NUM_THREADS; i++) {
+        int *offset = (int*) malloc(sizeof(int));
+        *offset = i;
+        if(pthread_create(&tids[i], NULL, thread_function, (void *) offset) == -1)
+            exit(1);
+    }
+    int count = 0;
+    for(int i=0; i < NUM_THREADS; i++) {
+        if(pthread_join(tids[i], NULL) == -1)
+            exit(1);
+        // Combine counts from each thread after termination
+        count += countByThreads[i];
+        countByThreads[i] = 0;
+    }
+    return count;
+}
+

We have further sped up the code execution although there is an additional overhead of managing threads.

1
+2
+3
+
>>> python -m timeit -n 5 -s 'import test_ctypes_pthread as t' 't.timeit_function()'
+Primes: 921295
+5 loops, best of 5: 322 msec per loop
+

Calling the C-compiled prime checker with Python threading

Remember the threading module in Python just now? Another neat thing about ctypes is that the program releases the GIL as long as the execution is inside the C-compiled shared library. So instead of POSIX pthreads in C, we can generate the threads with threading instead!

1
+2
+3
+4
+5
+6
+7
+8
+
from ctypes import *
+
+# Load the shared library
+lib = CDLL("./libprime.so")
+# Declare the return data as 32-bit integer
+lib.num_primes.restype = c_int32
+# Declare the arguments as a 32-bit integer & a pointer for 32-bit integer (for list)
+lib.num_primes.argtypes = [c_int32, POINTER(c_int32)]
+

Afterwards, the num_primes() in the shared library can be called! Note that the num_list has to be converted from Python list into a contiguous array with a method provided by ctypes.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
MAX_NUM = 1000000
+NUM_THREADS = 4
+
+# Prime counts per thread
+count_list = [0 for _ in range(NUM_THREADS)]
+# One list of numbers for each thread
+num_list_list = []
+
+# Split the list for multiple threads
+for i in range(NUM_THREADS):
+    num_list = list(range(i, MAX_NUM, NUM_THREADS))
+    num_list_list.append(num_list)
+
+# Function run by each thread
+def thread_function(i, num_list, count_list):
+    len_num_list = len(num_list)
+    count_list[i] = lib.num_primes(len_num_list, (c_int32 * len_num_list)(*num_list))
+
+def timeit_function():
+    threads = []
+    for i in range(NUM_THREADS):
+        t = threading.Thread(target=thread_function, args=(i, num_list_list[i], count_list))
+        t.start()
+        threads.append(t)
+    for thread in threads:
+        thread.join()
+    return sum(count_list) # Combine counts from each thread
+    
+print(f"Primes: {timeit_function()}")
+

For this example, the speed up is comparable to using pthreads.

1
+2
+3
+
>>> python -m timeit -n 5 -s 'import test_ctypes_threading as t' 't.timeit_function()'
+Primes: 921295
+5 loops, best of 5: 313 msec per loop
+

The code demonstrations can be found here.

Resources

  1. Ctypes Made Easy by Dublin User Group
  2. Bypassing GIL with ctypes by Christopher Swenson
  3. Python ctypes documentation
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/streamlining-fedramp-compliance/index.html b/posts/streamlining-fedramp-compliance/index.html new file mode 100644 index 0000000..49e5338 --- /dev/null +++ b/posts/streamlining-fedramp-compliance/index.html @@ -0,0 +1 @@ + Learning Points: Streamlining FedRAMP Compliance with CNCF | Phyo's Log
Post

Learning Points: Streamlining FedRAMP Compliance with CNCF

This video is about streamlining FedRAMP compliance with CNCF technologies. The presenters are Ali Monfre and Vlad Ungureanu from Palantir Technologies.

FedRAMP Overview

FedRAMP is the accreditation required for companies to sell SaaS solutions to the government instead of on-prem solutions. General steps include:

  • Identify a sponsor to join the authorization board.
  • Document security controls and validate them with third-party assessments.
  • Final review with the sponsor and FedRAMP management office.

Challenges Pre-Kubernetes

  • Scanning requirements included vulnerability scans, virus scans, configuration scans. They had to be done weekly or monthly basis. Before Kubernetes adoption, there was no uniform infrastructure or standardized immutable AMIs and container images. Palantir had to scan every single piece of live infrastructure.
  • Security patching requirements required constant host reboots. Orchestrating reboot cycles without downtime was a lot of engineering effort.
  • FIPS encryption is the government approved standards for cypher suites and cryptographic libraries. It takes a while for newly introduced cypher suites to be FIPS validated. As Palantir relies on open source technology, maintaining FIPS validated traffic between diverse services was a very difficult problem.

How Palantir Solved

For operating systems, major vendors have STIGs published. Palantir started running immutable machine images which were scanned during the CI process. This provided a faster feedback loop for the developers. Every host was also terminated every 72 hours. One side effect was that the vulnerabilities would be patched within three days.

For container images, an internal “golden image” was used by all products. The downstream images that used this were built automatically. Trivy (a scanning tool) was also embedded into CI.

Regarding FIPS, there is a long processing time for NIST (government agency) to validate new kernels and cryptographic libraries. Thus, Palantir cannot used features offered by new versions of the kernel.

Regarding service-to-service communication, Cilium CNI is used for k8s clusters. IPSec encryption in Cilium ensures FIPS validated encryption between pods. Cilium also has powerful network policy primitives which made it easier to adhere to FedRAMP standards.

Regarding Ingress/ Egress traffic, NGINX+ provides FIPS validation but there were performance problems encountered by Palantir. The decision was made to switched to Envoy, which is an open sourced service proxy designed for cloud native applications. BoringSSL with FIPS configured was used as the TLS provider.

Regarding Host Intrusion Detection System, originally OSQUERY tool was used. However, it did not integrate well with k8s so all the pods showed up as similar processes. The decision was made to switch to Isovalent Tetragon which was an eBPF tool that integrated well with k8s.

Continuing Challenges

There are more challenges not solved by CNCF technologies out of the box. To solve this, Palantir created Apollo and FedStart which helps companies to deploy software for the federal environment.

  • Change Management: Security relevant changes must be approved by authorized US person. Combining compliance checks with other automation tools is challenging. Policy-based rollouts allow CICD while enforcing approvals from the US persons.
  • Process Controls: A lot of FedRAMP controls are not technical, but more of procedure-related. Standardized infrastructure provided by Palantir means that the companies can just follow the provided templates for the documentation rather than creating from scratch.
  • Vulnerability Management: Palantir manages minimized images to reduce exposures to CVEs.
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/weaviate-vector-database/index.html b/posts/weaviate-vector-database/index.html new file mode 100644 index 0000000..3b92102 --- /dev/null +++ b/posts/weaviate-vector-database/index.html @@ -0,0 +1,47 @@ + Learning Points: Weaviate Vector Database | Phyo's Log
Post

Learning Points: Weaviate Vector Database

This video is a deep dive on the vector database Weaviate as part of the weekly database seminars by CMU Database Group. The presenter is Etienne Dilocker from Weaviate.

Why a Vector Database

Instead of indexing literal keywords from paragraphs, meanings (embeddings) can be indexed for search purposes.

LLMs tend to hallucinate less when some context around the question is given.

Retrieval Augmented Generation (RAG) retrieves the top few relevant documents from a vector database before providing as a context to LLMs. There is no need to retrain LLMs to keep updated with the latest information.

Weaviate Architecture

Collections are logical groups by the user. Shards distribute data across multiple nodes.

In each shard, the HNSW index is used most of the time. The object store can keep any binary files that are related to the embeddings. There is no need to have secondary storage for non-key-value data. The inverted index allows searching by properties and BM25 (bag-of-words retrieval) queries.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
      +-----------------------+
+      | Weaviate Setup        |
+      +-----------------------+
+  +-- | Collection "Articles" |
+  |   | Collection "Authors"  |
+  |   | ...                   |
+  |   +-----------------------+
+  |
+  |   +-----------------------+
+  +-> | Collection "Articles" |
+      +-----------------------+
+  +-- | Shard A               |
+  |   | Shard B               |
+  |   | ...                   |
+  |   +-----------------------+
+  |
+  |   +-----------------------+
+  +-> | Shard A               |
+      +-----------------------+
+      | HNSW Index            |
+      | Object Store (LSM)    |
+      | Inverted Index (LSM)  |
+      +-----------------------+
+

Consistent hashing on a specific key is used for sharding. On each node (physical shard), there can be multiple logical shards.

If the number of shards are changed on the fly, there are measures to ensure that minimal amount of data is moved around the nodes.

Hierarchical Navigable Small World (HNSW)

The index approximates nearest neighbor proximity graph with multiple layers. Compared to other indexes, it is slower to build but faster to query with.

Algorithm for querying a Navigable Small World (NSW) graph:

  • Pick a random entry point.
  • Follow all the edges and evaluate if the newly discovered points are closer.
  • Recenter on the best known point.
  • Don’t score any points twice.
  • Stop when there is no more improvement.

Considerations when building a NSW graph:

  • A real life dataset with natural clusters would be more efficient as compared to randomly generated points.
  • The number of connections between each point.
  • During the build phase, there is a search on the graph to decide where to place nodes. The more time spent on the build phase, the more efficient the search phase is.

Layers of NSW is HNSW. There are fewer connections per point on higher layers of HNSW. Few connections also means the connections “travel” longer distances. The search starts from the higher layers then move to lower layers.

Rebuilding NSW

Adding new data points do not degrade the graph. When one point has too many connections, pruning is done by reducing first-grade connections (direct) to second-grade connections (indirect).

Deleting points degrades the query time. When a point is marked as tombstone, it can still be used for traversing the graph but not be included in the result set. When the proportion of tombstones is large, the graph is rebuilt. On the fly, there are also reassignments of the tombstone’s connections to other points to make sure clusters remain connected. This operation is expensive but works well if there are not too many deletes.

This post is licensed under CC BY 4.0 by the author.
diff --git a/redirects.json b/redirects.json new file mode 100644 index 0000000..146c92d --- /dev/null +++ b/redirects.json @@ -0,0 +1 @@ +{"/norobots/":"https://yarkhinephyo.github.io/404.html","/assets/":"https://yarkhinephyo.github.io/404.html","/posts/":"https://yarkhinephyo.github.io/404.html"} \ No newline at end of file diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..2ff56db --- /dev/null +++ b/robots.txt @@ -0,0 +1,5 @@ +User-agent: * + +Disallow: /norobots/ + +Sitemap: https://yarkhinephyo.github.io/sitemap.xml diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..7bf7cb6 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,144 @@ + + + +https://yarkhinephyo.github.io/posts/async-io-event-loop/ +2024-01-21T02:30:54+00:00 + + +https://yarkhinephyo.github.io/posts/speed-up-python-with-ctypes/ +2024-01-21T02:30:54+00:00 + + +https://yarkhinephyo.github.io/posts/javascript-module-ecosystem/ +2024-01-21T02:30:54+00:00 + + +https://yarkhinephyo.github.io/posts/intuition-behind-the-attention-head/ +2024-01-21T02:17:58+00:00 + + +https://yarkhinephyo.github.io/posts/primer-to-sift/ +2024-01-21T02:17:58+00:00 + + +https://yarkhinephyo.github.io/posts/git-submodules-cheatsheet/ +2024-01-21T02:30:54+00:00 + + +https://yarkhinephyo.github.io/posts/how-the-same-origin-policy-mitigates-csrf/ +2024-01-21T02:30:54+00:00 + + +https://yarkhinephyo.github.io/posts/choosing-between-utf-encodings/ +2024-01-21T02:30:54+00:00 + + +https://yarkhinephyo.github.io/posts/solving-http1-problems-with-http2/ +2024-01-21T02:30:54+00:00 + + +https://yarkhinephyo.github.io/posts/jvm-garbage-collectors/ +2024-01-21T02:30:54+00:00 + + +https://yarkhinephyo.github.io/posts/data-structures-behind-git/ +2024-01-21T02:30:54+00:00 + + +https://yarkhinephyo.github.io/posts/cmu-mcds-course-reviews/ +2024-01-21T02:30:54+00:00 + + +https://yarkhinephyo.github.io/posts/kubernetes-networking-intro-and-deep-dive/ +2024-01-20T03:30:00+00:00 + + +https://yarkhinephyo.github.io/posts/scaling-monitoring-with-m3/ +2024-01-21T02:30:54+00:00 + + +https://yarkhinephyo.github.io/posts/snowflake-new-streams-talk/ +2024-01-21T02:20:00+00:00 + + +https://yarkhinephyo.github.io/posts/streamlining-fedramp-compliance/ +2024-01-21T03:55:00+00:00 + + +https://yarkhinephyo.github.io/posts/weaviate-vector-database/ +2024-01-24T04:20:00+00:00 + + +https://yarkhinephyo.github.io/categories/ +2024-01-24T04:32:36+00:00 + + +https://yarkhinephyo.github.io/tags/ +2024-01-24T04:32:36+00:00 + + +https://yarkhinephyo.github.io/archives/ +2024-01-24T04:32:36+00:00 + + +https://yarkhinephyo.github.io/about/ +2024-01-24T04:32:36+00:00 + + +https://yarkhinephyo.github.io/ + + +https://yarkhinephyo.github.io/tags/python/ + + +https://yarkhinephyo.github.io/tags/concurrency/ + + +https://yarkhinephyo.github.io/tags/c/ + + +https://yarkhinephyo.github.io/tags/javascript/ + + +https://yarkhinephyo.github.io/tags/data-science/ + + +https://yarkhinephyo.github.io/tags/git/ + + +https://yarkhinephyo.github.io/tags/networking/ + + +https://yarkhinephyo.github.io/tags/data-engineering/ + + +https://yarkhinephyo.github.io/tags/java/ + + +https://yarkhinephyo.github.io/tags/cmu/ + + +https://yarkhinephyo.github.io/tags/system-design/ + + +https://yarkhinephyo.github.io/tags/learning-points/ + + +https://yarkhinephyo.github.io/tags/database/ + + +https://yarkhinephyo.github.io/categories/tech/ + + +https://yarkhinephyo.github.io/categories/school/ + + +https://yarkhinephyo.github.io/page2/ + + +https://yarkhinephyo.github.io/page3/ + + +https://yarkhinephyo.github.io/page4/ + + diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..ded331a --- /dev/null +++ b/sw.js @@ -0,0 +1 @@ +self.importScripts('/assets/js/data/swcache.js'); const cacheName = 'chirpy-1706070757'; function verifyDomain(url) { for (const domain of allowedDomains) { const regex = RegExp(`^http(s)?:\/\/${domain}\/`); if (regex.test(url)) { return true; } } return false; } function isExcluded(url) { for (const item of denyUrls) { if (url === item) { return true; } } return false; } self.addEventListener('install', (event) => { event.waitUntil( caches.open(cacheName).then((cache) => { return cache.addAll(resource); }) ); }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keyList) => { return Promise.all( keyList.map((key) => { if (key !== cacheName) { return caches.delete(key); } }) ); }) ); }); self.addEventListener('message', (event) => { if (event.data === 'SKIP_WAITING') { self.skipWaiting(); } }); self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((response) => { if (response) { return response; } return fetch(event.request).then((response) => { const url = event.request.url; if ( event.request.method !== 'GET' || !verifyDomain(url) || isExcluded(url) ) { return response; } /* see: */ let responseToCache = response.clone(); caches.open(cacheName).then((cache) => { /* console.log('[sw] Caching new resource: ' + event.request.url); */ cache.put(event.request, responseToCache); }); return response; }); }) ); }); diff --git a/tags/c/index.html b/tags/c/index.html new file mode 100644 index 0000000..b3ef16c --- /dev/null +++ b/tags/c/index.html @@ -0,0 +1 @@ + C | Phyo's Log
diff --git a/tags/cmu/index.html b/tags/cmu/index.html new file mode 100644 index 0000000..a5a69a1 --- /dev/null +++ b/tags/cmu/index.html @@ -0,0 +1 @@ + CMU | Phyo's Log
diff --git a/tags/concurrency/index.html b/tags/concurrency/index.html new file mode 100644 index 0000000..8bac68c --- /dev/null +++ b/tags/concurrency/index.html @@ -0,0 +1 @@ + Concurrency | Phyo's Log
diff --git a/tags/data-engineering/index.html b/tags/data-engineering/index.html new file mode 100644 index 0000000..4a5bd4e --- /dev/null +++ b/tags/data-engineering/index.html @@ -0,0 +1 @@ + Data-Engineering | Phyo's Log
diff --git a/tags/data-science/index.html b/tags/data-science/index.html new file mode 100644 index 0000000..9f37b53 --- /dev/null +++ b/tags/data-science/index.html @@ -0,0 +1 @@ + Data-Science | Phyo's Log
diff --git a/tags/database/index.html b/tags/database/index.html new file mode 100644 index 0000000..6b7af09 --- /dev/null +++ b/tags/database/index.html @@ -0,0 +1 @@ + Database | Phyo's Log
diff --git a/tags/git/index.html b/tags/git/index.html new file mode 100644 index 0000000..a577816 --- /dev/null +++ b/tags/git/index.html @@ -0,0 +1 @@ + Git | Phyo's Log
diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 0000000..3d45085 --- /dev/null +++ b/tags/index.html @@ -0,0 +1 @@ + Tags | Phyo's Log
diff --git a/tags/java/index.html b/tags/java/index.html new file mode 100644 index 0000000..a4cbecd --- /dev/null +++ b/tags/java/index.html @@ -0,0 +1 @@ + Java | Phyo's Log
diff --git a/tags/javascript/index.html b/tags/javascript/index.html new file mode 100644 index 0000000..4b27e3e --- /dev/null +++ b/tags/javascript/index.html @@ -0,0 +1 @@ + JavaScript | Phyo's Log
diff --git a/tags/learning-points/index.html b/tags/learning-points/index.html new file mode 100644 index 0000000..3c0f09f --- /dev/null +++ b/tags/learning-points/index.html @@ -0,0 +1 @@ + Learning-Points | Phyo's Log
diff --git a/tags/networking/index.html b/tags/networking/index.html new file mode 100644 index 0000000..0493edf --- /dev/null +++ b/tags/networking/index.html @@ -0,0 +1 @@ + Networking | Phyo's Log
diff --git a/tags/python/index.html b/tags/python/index.html new file mode 100644 index 0000000..1098f5d --- /dev/null +++ b/tags/python/index.html @@ -0,0 +1 @@ + Python | Phyo's Log
diff --git a/tags/system-design/index.html b/tags/system-design/index.html new file mode 100644 index 0000000..ca1e91d --- /dev/null +++ b/tags/system-design/index.html @@ -0,0 +1 @@ + System-Design | Phyo's Log
diff --git a/unregister.js b/unregister.js new file mode 100644 index 0000000..20cef0d --- /dev/null +++ b/unregister.js @@ -0,0 +1 @@ +if ('serviceWorker' in navigator) { navigator.serviceWorker.getRegistrations().then((registrations) => { for (let reg of registrations) { reg.unregister(); } }); }