├── .gitignore ├── Jiena Gu McLellan’s resume.pdf ├── README.md ├── Jiena_McLellan_CV_files ├── font-awesome-5.1.0 │ ├── webfonts │ │ ├── fa-solid-900.eot │ │ ├── fa-solid-900.ttf │ │ ├── fa-brands-400.eot │ │ ├── fa-brands-400.ttf │ │ ├── fa-brands-400.woff │ │ ├── fa-brands-400.woff2 │ │ ├── fa-regular-400.eot │ │ ├── fa-regular-400.ttf │ │ ├── fa-regular-400.woff │ │ ├── fa-solid-900.woff │ │ ├── fa-solid-900.woff2 │ │ └── fa-regular-400.woff2 │ └── css │ │ └── all.css ├── header-attrs-2.16 │ └── header-attrs.js ├── paged-0.10 │ ├── js │ │ ├── config.js │ │ └── hooks.js │ └── css │ │ └── resume.css ├── paged-0.18.1 │ ├── js │ │ ├── config.js │ │ └── hooks.js │ └── css │ │ └── resume.css └── paged-0.18 │ ├── js │ ├── config.js │ └── hooks.js │ └── css │ └── resume.css ├── Jiena_McLellan_CV.Rproj ├── index.md ├── Jiena_McLellan_CV.Rmd └── Jiena_McLellan_CV.html /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | -------------------------------------------------------------------------------- /Jiena Gu McLellan’s resume.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena Gu McLellan’s resume.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jiena McLellan CV 2 | 3 | This CV is generated from pagedown R package. 4 | 5 | View: https://jienagu.github.io/Jiena_McLellan_CV/Jiena_McLellan_CV 6 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jienagu/Jiena_McLellan_CV/HEAD/Jiena_McLellan_CV_files/font-awesome-5.1.0/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /Jiena_McLellan_CV.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | ## Hello github page 2 | 3 | ### Link 4 | 5 | Note the relative link starting with a slash before the html file. 6 | 7 | The index.md is to customize the github page's home page, 8 | The following link directs to the slide. 9 | 10 | Code: `[cv link](/Jiena_McLellan_CV.html)` 11 | 12 | Result: 13 | 14 | [cv link](/Jiena_McLellan_CV.html) 15 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/header-attrs-2.16/header-attrs.js: -------------------------------------------------------------------------------- 1 | // Pandoc 2.9 adds attributes on both header and div. We remove the former (to 2 | // be compatible with the behavior of Pandoc < 2.8). 3 | document.addEventListener('DOMContentLoaded', function(e) { 4 | var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); 5 | var i, h, a; 6 | for (i = 0; i < hs.length; i++) { 7 | h = hs[i]; 8 | if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 9 | a = h.attributes; 10 | while (a.length > 0) h.removeAttribute(a[0].name); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/paged-0.10/js/config.js: -------------------------------------------------------------------------------- 1 | // Configuration script for paged.js 2 | 3 | (function() { 4 | // Retrieve previous config object if defined 5 | window.PagedConfig = window.PagedConfig || {}; 6 | const {before: beforePaged, after: afterPaged} = window.PagedConfig; 7 | 8 | // utils 9 | const insertCSS = text => { 10 | let style = document.createElement('style'); 11 | style.type = 'text/css'; 12 | style.appendChild(document.createTextNode(text)); 13 | document.head.appendChild(style); 14 | }; 15 | 16 | // Util function for front and back covers images 17 | const insertCSSForCover = type => { 18 | const links = document.querySelectorAll('link[id^=' + type + ']'); 19 | if (!links.length) return; 20 | const re = new RegExp(type + '-\\d+'); 21 | let text = ':root {--' + type + ': var(--' + type + '-1);'; 22 | for (const link of links) { 23 | text += '--' + re.exec(link.id)[0] + ': url("' + link.href + '");'; 24 | } 25 | text += '}'; 26 | insertCSS(text); 27 | }; 28 | 29 | const insertPageBreaksCSS = () => { 30 | insertCSS(` 31 | .page-break-after {break-after: page;} 32 | .page-break-before {break-before: page;} 33 | `); 34 | }; 35 | 36 | window.PagedConfig.before = async () => { 37 | // Front and back covers support 38 | let frontCover = document.querySelector('.front-cover'); 39 | let backCover = document.querySelector('.back-cover'); 40 | if (frontCover) document.body.prepend(frontCover); 41 | if (backCover) document.body.append(backCover); 42 | insertCSSForCover('front-cover'); 43 | insertCSSForCover('back-cover'); 44 | insertPageBreaksCSS(); 45 | 46 | if (beforePaged) await beforePaged(); 47 | }; 48 | 49 | window.PagedConfig.after = (flow) => { 50 | // force redraw, see https://github.com/rstudio/pagedown/issues/35#issuecomment-475905361 51 | // and https://stackoverflow.com/a/24753578/6500804 52 | document.body.style.display = 'none'; 53 | document.body.offsetHeight; 54 | document.body.style.display = ''; 55 | 56 | // run previous PagedConfig.after function if defined 57 | if (afterPaged) afterPaged(flow); 58 | 59 | // pagedownListener is a binding added by the chrome_print function 60 | // this binding exists only when chrome_print opens the html file 61 | if (window.pagedownListener) { 62 | // the html file is opened for printing 63 | // call the binding to signal to the R session that Paged.js has finished 64 | pagedownListener(JSON.stringify({ 65 | pagedjs: true, 66 | pages: flow.total, 67 | elapsedtime: flow.performance 68 | })); 69 | return; 70 | } 71 | if (sessionStorage.getItem('pagedown-scroll')) { 72 | // scroll to the last position before the page is reloaded 73 | window.scrollTo(0, sessionStorage.getItem('pagedown-scroll')); 74 | return; 75 | } 76 | if (window.location.hash) { 77 | const id = window.location.hash.replace(/^#/, ''); 78 | document.getElementById(id).scrollIntoView({behavior: 'smooth'}); 79 | } 80 | }; 81 | })(); 82 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/paged-0.18.1/js/config.js: -------------------------------------------------------------------------------- 1 | // Configuration script for paged.js 2 | 3 | (function() { 4 | // Retrieve previous config object if defined 5 | window.PagedConfig = window.PagedConfig || {}; 6 | const {before: beforePaged, after: afterPaged} = window.PagedConfig; 7 | 8 | // utils 9 | const insertCSS = text => { 10 | let style = document.createElement('style'); 11 | style.type = 'text/css'; 12 | style.appendChild(document.createTextNode(text)); 13 | document.head.appendChild(style); 14 | }; 15 | 16 | // Util function for front and back covers images 17 | const insertCSSForCover = type => { 18 | const links = document.querySelectorAll('link[id^=' + type + ']'); 19 | if (!links.length) return; 20 | const re = new RegExp(type + '-\\d+'); 21 | let text = ':root {--' + type + ': var(--' + type + '-1);'; 22 | for (const link of links) { 23 | text += '--' + re.exec(link.id)[0] + ': url("' + link.href + '");'; 24 | } 25 | text += '}'; 26 | insertCSS(text); 27 | }; 28 | 29 | const insertPageBreaksCSS = () => { 30 | insertCSS(` 31 | .page-break-after {break-after: page;} 32 | .page-break-before {break-before: page;} 33 | `); 34 | }; 35 | 36 | window.PagedConfig.before = async () => { 37 | // Front and back covers support 38 | let frontCover = document.querySelector('.front-cover'); 39 | let backCover = document.querySelector('.back-cover'); 40 | if (frontCover) document.body.prepend(frontCover); 41 | if (backCover) document.body.append(backCover); 42 | insertCSSForCover('front-cover'); 43 | insertCSSForCover('back-cover'); 44 | insertPageBreaksCSS(); 45 | 46 | if (beforePaged) await beforePaged(); 47 | }; 48 | 49 | // from https://stackoverflow.com/q/21647928 50 | const toUTF16BE = x => { 51 | let res = ''; 52 | for (i=0; i < x.length; i++) { 53 | let hex = x.charCodeAt(i).toString(16); 54 | hex = ('000' + hex).slice(-4); 55 | res += hex 56 | } 57 | res = 'feff' + res ; 58 | return res; 59 | } 60 | 61 | const findPage = el => { 62 | while (el.parentElement) { 63 | el = el.parentElement; 64 | if (el.getAttribute('data-page-number')) { 65 | return parseInt(el.getAttribute('data-page-number')); 66 | } 67 | } 68 | return null; 69 | }; 70 | 71 | const tocEntriesInfos = ul => { 72 | let result = []; // where we store the results 73 | // if there is no element, return an empty array 74 | if (!ul) { 75 | return result; 76 | } 77 | const tocEntries = ul.children; // tocEntries are 'li' elements 78 | 79 | for (const li of tocEntries) { 80 | // Since parts entries in TOC have no anchor, 81 | // do not use them in the PDF outline. 82 | if (li.classList.contains('part')) { 83 | continue; 84 | } 85 | 86 | // get the title and encode it in UTF16BE (pdfmark is encoded in UTF16BE with BOM) 87 | const title = toUTF16BE(li.querySelector('a').textContent); 88 | 89 | // get the page number 90 | const href = li.querySelector('a').getAttribute('href'); 91 | const el = document.getElementById(href.substring(1)); 92 | const page = findPage(el); 93 | 94 | // get the children 95 | children = tocEntriesInfos(li.querySelector('ul')); 96 | 97 | result.push({ 98 | title: title, 99 | page: page, 100 | children: children 101 | }); 102 | } 103 | 104 | return result; 105 | }; 106 | window.PagedConfig.after = (flow) => { 107 | // force redraw, see https://github.com/rstudio/pagedown/issues/35#issuecomment-475905361 108 | // and https://stackoverflow.com/a/24753578/6500804 109 | document.body.style.display = 'none'; 110 | document.body.offsetHeight; 111 | document.body.style.display = ''; 112 | 113 | // run previous PagedConfig.after function if defined 114 | if (afterPaged) afterPaged(flow); 115 | 116 | // pagedownListener is a binding added by the chrome_print function 117 | // this binding exists only when chrome_print opens the html file 118 | if (window.pagedownListener) { 119 | // the html file is opened for printing 120 | // call the binding to signal to the R session that Paged.js has finished 121 | const tocList = flow.source.querySelector('.toc > ul'); 122 | const tocInfos = tocEntriesInfos(tocList); 123 | pagedownListener(JSON.stringify({ 124 | pagedjs: true, 125 | pages: flow.total, 126 | elapsedtime: flow.performance, 127 | tocInfos: tocInfos 128 | })); 129 | return; 130 | } 131 | if (sessionStorage.getItem('pagedown-scroll')) { 132 | // scroll to the last position before the page is reloaded 133 | window.scrollTo(0, sessionStorage.getItem('pagedown-scroll')); 134 | return; 135 | } 136 | if (window.location.hash) { 137 | const id = decodeURIComponent(window.location.hash).replace(/^#/, ''); 138 | document.getElementById(id).scrollIntoView({behavior: 'smooth'}); 139 | } 140 | }; 141 | })(); 142 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/paged-0.18/js/config.js: -------------------------------------------------------------------------------- 1 | // Configuration script for paged.js 2 | 3 | (function() { 4 | // Retrieve previous config object if defined 5 | window.PagedConfig = window.PagedConfig || {}; 6 | const {before: beforePaged, after: afterPaged} = window.PagedConfig; 7 | 8 | // utils 9 | const insertCSS = text => { 10 | let style = document.createElement('style'); 11 | style.type = 'text/css'; 12 | style.appendChild(document.createTextNode(text)); 13 | document.head.appendChild(style); 14 | }; 15 | 16 | // Util function for front and back covers images 17 | const insertCSSForCover = type => { 18 | const links = document.querySelectorAll('link[id^=' + type + ']'); 19 | if (!links.length) return; 20 | const re = new RegExp(type + '-\\d+'); 21 | let text = ':root {--' + type + ': var(--' + type + '-1);'; 22 | for (const link of links) { 23 | text += '--' + re.exec(link.id)[0] + ': url("' + link.href + '");'; 24 | } 25 | text += '}'; 26 | insertCSS(text); 27 | }; 28 | 29 | const insertPageBreaksCSS = () => { 30 | insertCSS(` 31 | .page-break-after {break-after: page;} 32 | .page-break-before {break-before: page;} 33 | `); 34 | }; 35 | 36 | window.PagedConfig.before = async () => { 37 | // Front and back covers support 38 | let frontCover = document.querySelector('.front-cover'); 39 | let backCover = document.querySelector('.back-cover'); 40 | if (frontCover) document.body.prepend(frontCover); 41 | if (backCover) document.body.append(backCover); 42 | insertCSSForCover('front-cover'); 43 | insertCSSForCover('back-cover'); 44 | insertPageBreaksCSS(); 45 | 46 | if (beforePaged) await beforePaged(); 47 | }; 48 | 49 | // from https://stackoverflow.com/q/21647928 50 | const toUTF16BE = x => { 51 | let res = ''; 52 | for (i=0; i < x.length; i++) { 53 | let hex = x.charCodeAt(i).toString(16); 54 | hex = ('000' + hex).slice(-4); 55 | res += hex 56 | } 57 | res = 'feff' + res ; 58 | return res; 59 | } 60 | 61 | const findPage = el => { 62 | while (el.parentElement) { 63 | el = el.parentElement; 64 | if (el.getAttribute('data-page-number')) { 65 | return parseInt(el.getAttribute('data-page-number')); 66 | } 67 | } 68 | return null; 69 | }; 70 | 71 | const tocEntriesInfos = ul => { 72 | let result = []; // where we store the results 73 | // if there is no element, return an empty array 74 | if (!ul) { 75 | return result; 76 | } 77 | const tocEntries = ul.children; // tocEntries are 'li' elements 78 | 79 | for (const li of tocEntries) { 80 | // Since parts entries in TOC have no anchor, 81 | // do not use them in the PDF outline. 82 | if (li.classList.contains('part')) { 83 | continue; 84 | } 85 | 86 | // get the title and encode it in UTF16BE (pdfmark is encoded in UTF16BE with BOM) 87 | const title = toUTF16BE(li.querySelector('a').textContent); 88 | 89 | // get the page number 90 | const href = li.querySelector('a').getAttribute('href'); 91 | const el = document.getElementById(href.substring(1)); 92 | const page = findPage(el); 93 | 94 | // get the children 95 | children = tocEntriesInfos(li.querySelector('ul')); 96 | 97 | result.push({ 98 | title: title, 99 | page: page, 100 | children: children 101 | }); 102 | } 103 | 104 | return result; 105 | }; 106 | window.PagedConfig.after = (flow) => { 107 | // force redraw, see https://github.com/rstudio/pagedown/issues/35#issuecomment-475905361 108 | // and https://stackoverflow.com/a/24753578/6500804 109 | document.body.style.display = 'none'; 110 | document.body.offsetHeight; 111 | document.body.style.display = ''; 112 | 113 | // run previous PagedConfig.after function if defined 114 | if (afterPaged) afterPaged(flow); 115 | 116 | // pagedownListener is a binding added by the chrome_print function 117 | // this binding exists only when chrome_print opens the html file 118 | if (window.pagedownListener) { 119 | // the html file is opened for printing 120 | // call the binding to signal to the R session that Paged.js has finished 121 | const tocList = flow.source.querySelector('.toc > ul'); 122 | const tocInfos = tocEntriesInfos(tocList); 123 | pagedownListener(JSON.stringify({ 124 | pagedjs: true, 125 | pages: flow.total, 126 | elapsedtime: flow.performance, 127 | tocInfos: tocInfos 128 | })); 129 | return; 130 | } 131 | if (sessionStorage.getItem('pagedown-scroll')) { 132 | // scroll to the last position before the page is reloaded 133 | window.scrollTo(0, sessionStorage.getItem('pagedown-scroll')); 134 | return; 135 | } 136 | if (window.location.hash) { 137 | const id = decodeURIComponent(window.location.hash).replace(/^#/, ''); 138 | document.getElementById(id).scrollIntoView({behavior: 'smooth'}); 139 | } 140 | }; 141 | })(); 142 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Jiena Gu McLellan's resume" 3 | author: Jiena Gu McLellan 4 | date: "`r Sys.Date()`" 5 | output: 6 | pagedown::html_resume: 7 | # set it to true for a self-contained HTML page but it'll take longer to render 8 | self_contained: false 9 | --- 10 | 11 | Aside 12 | ================================================================================ 13 | 14 | Contact Info {#contact} 15 | -------------------------------------------------------------------------------- 16 | 17 | - jienagu90@gmail.com 18 | - [github.com/jienagu](https://github.com/jienagu) 19 | - www.jienamclellan.com 20 | 21 | 22 | R packages {#skills} 23 | -------------------------------------------------------------------------------- 24 | 25 | - #### noteMD: 26 | 27 | ![noteMD](https://raw.githubusercontent.com/jienagu/noteMD/master/noteMD_logo.png){width=70%} 28 | 29 | - Print text from shiny ui (support markdown syntax) to pdf or word report 30 | - Repo: github.com/jienagu/noteMD 31 | 32 | 33 | - #### forestry 34 | 35 | ![forestry](https://raw.githubusercontent.com/jienagu/forestry/master/forestry_hex2.png){width=70%} 36 | 37 | - help with reshaping hierarchy of data tree 38 | - Repo: github.com/jienagu/forestry 39 | 40 | 41 | 42 | Main 43 | ================================================================================ 44 | 45 | Jiena Gu McLellan {#title} 46 | -------------------------------------------------------------------------------- 47 | 48 | Professional Experience {data-icon=suitcase} 49 | -------------------------------------------------------------------------------- 50 | 51 | ### Software Architect 52 | 53 | McKinsey & Company 54 | 55 | Tampa, FL 56 | 57 | 2021 - Present 58 | 59 | 60 | ### Senior Software Developer 61 | 62 | McKinsey & Company 63 | 64 | Atlanta, GA 65 | 66 | 2019 - 2021 67 | 68 | - Develop tools and web applications for data analytics 69 | - Develop and mantain R and Python packages 70 | 71 | 72 | ### Shiny Developer/ R Programmer Analyst 73 | 74 | Beef Cattle Institute at Kansas State University 75 | 76 | Manhattan, KS 77 | 78 | 2016 - 2019 79 | 80 | - Build big data interactive dashboard (shiny) for clients generating dynamic report. 81 | - Develop R program/ packages for research projects. 82 | - Build shiny dashboard (including user authentication) that linked to a mobile app, mobile app users can visualize their own data and generate reports with the same log in as the mobile side. 83 | - Statistical consulting. 84 | 85 | 86 | 87 | ### Graduate Teaching Assistant 88 | 89 | Department of Statistics at Kansas State University 90 | 91 | Manhattan, KS 92 | 93 | 2014 - 2016 94 | 95 | - Teach Stat 325 (Introduction to Statistics) course as the instructor. 96 | - Grade Assignments and exam. 97 | 98 | 99 | ### Statistics Specialist 100 | 101 | Internatioanl Admission and Recruiting office at Kansas State University 102 | 103 | Manhattan, KS 104 | 105 | 2015 - 2016 106 | 107 | - Build interactive dashboard (shiny) to visualize the admission data. 108 | 109 | 110 | 111 | Education {data-icon=graduation-cap data-concise=true} 112 | -------------------------------------------------------------------------------- 113 | 114 | ### Kansas State University 115 | 116 | Graduate Certificate in Data Analytics 117 | 118 | Manhattan, KS, USA 119 | 120 | 2017 121 | 122 | ### Kansas State University 123 | 124 | M.S. in Statistics 125 | 126 | Manhattan, KS, USA 127 | 128 | 2016 129 | 130 | ### South China Normal University 131 | 132 | B.S. in Bioengineering 133 | 134 | Guangzhou, China 135 | 136 | 2013 137 | 138 | 139 | R/ Shiny Publications {data-icon=file} 140 | -------------------------------------------------------------------------------- 141 | 142 | ### Building an Interactive Tutorial Website for R and Statistics 143 | 144 | C2C Digital Magazine: Vol. 1 : Iss. 10 , Article 7. 145 | 146 | N/A 147 | 148 | 2019 149 | 150 | Jiena Gu McLellan 151 | 152 | 153 | 154 | ::: aside 155 | 156 | - #### faq 157 | 158 | ![faq](https://raw.githubusercontent.com/jienagu/faq/master/faq_logo.png){width=70%} 159 | 160 | - create a FAQ (Frequently Asked Questions) page 161 | - Repo: github.com/jienagu/faq 162 | 163 | - #### flashCard 164 | 165 | ![flashCard](https://raw.githubusercontent.com/jienagu/flashCard/master/man/figures/flashCard_hex.png){width=70%} 166 | 167 | - create a flash card 168 | - github.com/jienagu/flashCard 169 | 170 | - #### Skills: 171 | 172 | R, Python, javascript, Shiny, html, Rcpp (C++), CSS 173 | 174 | ::: 175 | 176 | 177 | ### Geographic Information Visualization by R 178 | 179 | C2C Digital Magazine: Vol. 1 : Iss. 8 , Article 4. 180 | 181 | N/A 182 | 183 | 2018 184 | 185 | Jiena Gu McLellan 186 | 187 | ### Sentiment Analysis of Real-time Twitter Data 188 | 189 | C2C Digital Magazine: Vol. 1 : Iss. 7 , Article 8. 190 | 191 | N/A 192 | 193 | 2017 194 | 195 | Jiena Gu McLellan 196 | 197 | ### Shiny App: An Efficient and Creative Communicator of Big Data 198 | 199 | C2C Digital Magazine: Vol. 1 : Iss. 6 , Article 6. 200 | 201 | N/A 202 | 203 | 2016 204 | 205 | Jiena Gu 206 | 207 | Peer-Reviewed Publications and Talks {data-icon=file} 208 | -------------------------------------------------------------------------------- 209 | ### Effect of Duration and Onset of Clinical Signs on Short-Term Outcome of Dogs with Hansen Type I Thoracolumbar Intervertebral Disc Extrusion 210 | 211 | Veterinary and Comparative Orthopaedics and Traumatology 212 | 213 | N/A 214 | 215 | 2020 216 | 217 | DA Upchurch, WC Renberg, HS Turner, **JG McLellan** 218 | 219 | ### learnr: Interactive R tutorials 220 | 221 | TA for Yihui Xie (Instructor) at Advanced Rmarkdown Workshop at RStudio Conference 2019 222 | 223 | Austin, TX 224 | 225 | 2019 226 | 227 | Jiena Gu McLellan 228 | 229 | 230 | ### Evaluation of three classification models to predict risk class of cattle cohorts developing bovine respiratory disease within the first 14 days on feed using on-arrival and/or prearrival information. 231 | 232 | Computers and Electronics in Agriculture. 156 (2019) 439-446. 233 | 234 | N/A 235 | 236 | 2019 237 | 238 | David E. Amrine, **Jiena G. McLellan**, Brad J. White, Robert L. Larson, David G. Renter, 239 | Mike Sanderson. 240 | 241 | ### Evaluation of animal-to-animal and community contact structures determined by a real-time location system for correlation with and prediction of new bovine respiratory disease diagnoses in beef cattle during the first 28 days after feedlot entry. 242 | 243 | American Journal of Veterinary Research [01 Dec 2018, 79(12):1277-1286] 244 | 245 | Kansas, USA 246 | 247 | 2018 248 | 249 | Shane DD, **McLellan JG**, White BJ, Larson RL, Amrine DE, Sanderson MW, Apley MD. 250 | 251 | 252 | ### R for Data Analytics & Interactive Visualization 253 | 254 | Manhattan Data Analytics Meetup 255 | 256 | Manhattan, KS 257 | 258 | 2018 259 | 260 | Jiena Gu McLellan 261 | 262 | 263 | 264 | Personal Shiny Projects {data-icon=file} 265 | -------------------------------------------------------------------------------- 266 | 267 | ### DT Editor/ DT Editor Shiny Module 268 | 269 | - Intro: DT Editor Shiny App that has (almost) all features of data table editor. 270 | - Shiny Module: https://github.com/jienagu/DT_editor_shiny_module 271 | 272 | N/A 273 | 274 | Repo: https://github.com/jienagu/DT-Editor 275 | 276 | 2019 277 | 278 | ### rpivotTableMD 279 | 280 | - Intro: a shiny app demonstrates `rpivotTable` dynamically interacting with Rmarkdown reports (pdf or word) from a shiny app platform 281 | 282 | N/A 283 | 284 | Repo: https://github.com/jienagu/rpivotTableMD 285 | 286 | 2019 287 | 288 | ### D3 folded charts 289 | 290 | - Hierarchies Design of Data Visualization for shiny: Drilling down from graphs to table/graphs similar with Power BI; generate dynamic reports. 291 | 292 | N/A 293 | 294 | Repo: https://github.com/jienagu/D3_folded_charts 295 | 296 | 2019 297 | 298 | ### Shiny Template 299 | 300 | - Intro: a shiny template is to mimic questions/survey type of UI design to bring the best user-experience. 301 | 302 | N/A 303 | 304 | Repo: https://github.com/jienagu/Shiny_Template 305 | 306 | 2019 307 | 308 | ### PlayR: Interacitve Tutorial Website 309 | 310 | - Intro: an interactive tutorial website (PlayR) built by learnr package. 311 | 312 | N/A 313 | 314 | Repo: https://github.com/jienagu/Interactive_tutorial_website 315 | 316 | 2019 317 | 318 | ### Shiny Interactive Presentation 319 | 320 | - Intro: Generating interactive presentation from shiny side 321 | 322 | N/A 323 | 324 | Repo: https://github.com/jienagu/Shiny_Interactive_Presentation 325 | 326 | 2019 327 | 328 | 329 | Grant & Awards {data-icon=file} 330 | -------------------------------------------------------------------------------- 331 | 332 | ### The Development Grant for MIS665 through K-State global campus 333 | 334 | Intro: Develop MIS 665 Business Analytics and Data Mining course 335 | 336 | Manhattan 337 | KS 338 | 339 | 2019 340 | 341 | 342 | ::: aside 343 | 344 | This resume was made with the R package [**pagedown**](https://github.com/rstudio/pagedown). 345 | 346 | Last updated on `r Sys.Date()`. 347 | 348 | ::: 349 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/paged-0.10/css/resume.css: -------------------------------------------------------------------------------- 1 | @page{ 2 | size: letter portrait; 3 | margin: 1in 0.5in 1in 0.25in; 4 | } 5 | 6 | *{ 7 | box-sizing: border-box; 8 | } 9 | 10 | :root{ 11 | --page-width: 8.5in; 12 | --margin-right: 0.5in; 13 | --margin-left: 0.25in; 14 | --content-width: calc(var(--page-width) - var(--margin-right) - var(--margin-left)); 15 | --root-font-size: 12pt; 16 | --sidebar-width: 15rem; 17 | --sidebar-background-color: #f2f2f2; 18 | --main-width: calc(var(--content-width) - var(--sidebar-width)); 19 | --decorator-horizontal-margin: 0.2in; 20 | 21 | --sidebar-horizontal-padding: 0.2in; 22 | 23 | /* XXX: using px for very good precision control */ 24 | --decorator-outer-offset-top: 10px; 25 | --decorator-outer-offset-left: -5.5px; 26 | --decorator-border-width: 1px; 27 | --decorator-outer-dim: 9px; 28 | --decorator-border: 1px solid #ccc; 29 | 30 | --row-blocks-padding-top: 0.5rem; 31 | --date-block-width: 0.7in; 32 | 33 | --main-blocks-title-icon-offset-left: calc(-17pt - 0.25 * var(--root-font-size)); 34 | 35 | --viewer-background-color: #dcdcdc; 36 | --viewer-pages-spacing: 12px; 37 | --viewer-shadow-color: #313131; 38 | } 39 | 40 | .pagedjs_page { 41 | --content-area-height: calc(var(--pagedjs-height) - var(--pagedjs-margin-top) - var(--pagedjs-margin-bottom)); 42 | --sidebar-background-width: calc(var(--pagedjs-margin-right) + var(--sidebar-width)); 43 | } 44 | 45 | @page{ 46 | background: linear-gradient(to left, var(--sidebar-background-color), var(--sidebar-background-color) var(--sidebar-background-width), white var(--sidebar-background-width)); 47 | } 48 | 49 | html { 50 | font-size: var(--root-font-size); 51 | } 52 | 53 | body{ 54 | width: var(--content-width); 55 | font-family: "Open Sans", sans-serif; 56 | font-weight: 300; 57 | line-height: 1.3; 58 | color: #444; 59 | hyphens: auto; 60 | } 61 | 62 | h1, h2, h3{ 63 | margin: 0; 64 | color: #000; 65 | } 66 | 67 | #main > h1, #aside > h1, #disclaimer > h2 { 68 | display: none; 69 | } 70 | 71 | li{ 72 | list-style-type: none; 73 | } 74 | 75 | a{ 76 | text-decoration: none; 77 | } 78 | 79 | img{ 80 | max-width: 100%; 81 | } 82 | 83 | #main{ 84 | width: var(--main-width); 85 | padding: 0 0.25in 0 0.25in; 86 | font-size: 0.7rem; 87 | float: left; 88 | } 89 | 90 | #aside{ 91 | position: relative; /* for disclaimer */ 92 | height: var(--content-area-height); 93 | } 94 | 95 | .aside{ 96 | width: var(--sidebar-width); 97 | padding: 0.6in var(--sidebar-horizontal-padding); 98 | font-size: 0.8rem; 99 | float: right; 100 | position: absolute; 101 | right: 0; 102 | } 103 | 104 | /* main */ 105 | 106 | /** big title **/ 107 | h1, h2{ 108 | text-transform: uppercase; 109 | } 110 | 111 | #title{ 112 | position: relative; 113 | left: 0.55in; 114 | margin: auto 0.55in 0.3in auto; 115 | line-height: 1.2; 116 | } 117 | 118 | #title h1{ 119 | font-weight: 300; 120 | font-size: 1.8rem; 121 | line-height: 1.5; 122 | } 123 | 124 | #title h3{ 125 | font-size: 0.8rem; 126 | } 127 | 128 | 129 | /*** categorial blocks ***/ 130 | 131 | .main-block{ 132 | margin-top: 0.1in; 133 | } 134 | 135 | #main h2{ 136 | position: relative; 137 | top: var(--row-blocks-padding-top); 138 | /* XXX: 0.5px for aligning fx printing */ 139 | left: calc((var(--date-block-width) + var(--decorator-horizontal-margin))); 140 | font-weight: 400; 141 | font-size: 1.1rem; 142 | color: #555; 143 | } 144 | 145 | #main h2 > i{ 146 | /* use absolute position to prevent icon's width from misaligning title */ 147 | /* assigning a fixed width here is no better due to needing to align decorator 148 | line too */ 149 | position: absolute; 150 | left: var(--main-blocks-title-icon-offset-left); 151 | z-index: 1; /* over decorator line */ 152 | color: #444; 153 | } 154 | 155 | #main h2::after{ /* extends the decorator line */ 156 | height: calc(var(--row-blocks-padding-top) * 2); 157 | position: relative; 158 | top: calc(-1 * var(--row-blocks-padding-top)); 159 | /* XXX: 0.5px for aligning fx printing */ 160 | left: calc(-1 * var(--decorator-horizontal-margin)); 161 | display: block; 162 | border-left: var(--decorator-border); 163 | z-index: 0; 164 | line-height: 0; 165 | font-size: 0; 166 | content: ' '; 167 | } 168 | 169 | /**** minor tweaks on the icon fonts ****/ 170 | #main h2 > .fa-graduation-cap{ 171 | left: calc(var(--main-blocks-title-icon-offset-left) - 2pt); 172 | top: 2pt; 173 | } 174 | 175 | #main h2 > .fa-suitcase{ 176 | top: 1pt; 177 | } 178 | 179 | #main h2 > .fa-folder-open{ 180 | top: 1.5pt; 181 | } 182 | 183 | /**** individual row blocks (date - decorator - details) ****/ 184 | 185 | .blocks{ 186 | display: flex; 187 | flex-flow: row nowrap; 188 | } 189 | 190 | .blocks > div{ 191 | padding-top: var(--row-blocks-padding-top); 192 | } 193 | 194 | .date{ 195 | flex: 0 0 var(--date-block-width); 196 | padding-top: calc(var(--row-blocks-padding-top) + 0.25rem) !important; 197 | padding-right: var(--decorator-horizontal-margin); 198 | font-size: 0.7rem; 199 | text-align: right; 200 | line-height: 1; 201 | max-width: var(--date-block-width); 202 | } 203 | 204 | .date span{ 205 | display: block; 206 | text-align: center; 207 | } 208 | 209 | .date span:nth-child(2)::before{ 210 | position: relative; 211 | top: 0.1rem; 212 | right: 0; 213 | display: block; 214 | height: 1rem; 215 | content: '|'; 216 | } 217 | 218 | .decorator{ 219 | flex: 0 0 0; 220 | position: relative; 221 | width: 2pt; 222 | min-height: 100%; 223 | border-left: var(--decorator-border); 224 | } 225 | 226 | /* 227 | * XXX: Use two filled circles to achieve the circle-with-white-border effect. 228 | * The normal technique of only using one pseudo element and 229 | * border: 1px solid white; style makes firefox erroneously either: 230 | * 1) overflows the grayshade background (if no background-clip is set), or 231 | * 2) shows decorator line which should've been masked by the white border 232 | * 233 | */ 234 | 235 | .decorator::before{ 236 | position: absolute; 237 | top: var(--decorator-outer-offset-top); 238 | left: var(--decorator-outer-offset-left); 239 | content: ' '; 240 | display: block; 241 | width: var(--decorator-outer-dim); 242 | height: var(--decorator-outer-dim); 243 | border-radius: calc(var(--decorator-outer-dim) / 2); 244 | background-color: #fff; 245 | } 246 | 247 | .decorator::after{ 248 | position: absolute; 249 | top: calc(var(--decorator-outer-offset-top) + var(--decorator-border-width)); 250 | left: calc(var(--decorator-outer-offset-left) + var(--decorator-border-width)); 251 | content: ' '; 252 | display: block; 253 | width: calc(var(--decorator-outer-dim) - (var(--decorator-border-width) * 2)); 254 | height: calc(var(--decorator-outer-dim) - (var(--decorator-border-width) * 2)); 255 | border-radius: calc((var(--decorator-outer-dim) - (var(--decorator-border-width) * 2)) / 2); 256 | background-color: #555; 257 | } 258 | 259 | .blocks:last-child .decorator{ /* slightly shortens it */ 260 | margin-bottom: 0.25in; 261 | } 262 | 263 | /***** fine-tunes on the details block where the real juice is *****/ 264 | 265 | .details{ 266 | flex: 1 0 0; 267 | padding-left: var(--decorator-horizontal-margin); 268 | padding-top: calc(var(--row-blocks-padding-top) - 0.05rem) !important; /* not sure why but this is needed for better alignment */ 269 | } 270 | 271 | .details header{ 272 | color: #000; 273 | } 274 | 275 | .details h3{ 276 | font-size: 0.8rem; 277 | } 278 | 279 | .main-block:not(.concise) .details div{ 280 | margin: 0.18in 0 0.1in 0; 281 | } 282 | 283 | .main-block:not(.concise) .details div:empty { 284 | margin: 0; 285 | } 286 | 287 | .main-block:not(.concise) .blocks:last-child .details div{ 288 | margin-bottom: 0; 289 | } 290 | 291 | .main-block.concise .details div:not(.concise){ 292 | /* use padding to work around the fact that margin doesn't affect floated 293 | neighboring elements */ 294 | padding: 0.05in 0 0.07in 0; 295 | } 296 | 297 | .details .place{ 298 | float: left; 299 | font-size: 0.75rem; 300 | } 301 | 302 | .details .location{ 303 | float: right; 304 | } 305 | 306 | .details div{ 307 | clear: both; 308 | } 309 | 310 | /***** fine-tunes on the lists... *****/ 311 | 312 | #main ul{ 313 | padding-left: 0.07in; 314 | margin: 0.08in 0; 315 | } 316 | 317 | #main li{ 318 | margin: 0 0 0.025in 0; 319 | } 320 | 321 | /****** customize list symbol style ******/ 322 | #main li::before{ 323 | position: relative; 324 | margin-left: -4.25pt; 325 | content: '• '; 326 | } 327 | 328 | .aside li::before { 329 | content: none; 330 | } 331 | 332 | .details .concise ul{ 333 | margin: 0 !important; 334 | -webkit-columns: 2; 335 | -moz-columns: 2; 336 | columns: 2; 337 | } 338 | 339 | .details .concise li{ 340 | margin: 0 !important; 341 | } 342 | 343 | .details .concise li{ 344 | margin-left: 0 !important; 345 | } 346 | 347 | 348 | 349 | /* sidebar */ 350 | 351 | .aside h2{ 352 | font-weight: 400; 353 | font-size: 1.1rem; 354 | } 355 | 356 | .aside .level2{ 357 | margin-top: 0.5in; 358 | } 359 | 360 | #contact ul{ 361 | margin-top: 0.05in; 362 | padding-left: 0; 363 | font-weight: 400; 364 | line-height: 1.75; 365 | } 366 | 367 | #contact li > i{ 368 | width: 0.9rem; /* for text alignment */ 369 | text-align: right; 370 | } 371 | 372 | #skills{ 373 | line-height: 1.5; 374 | } 375 | 376 | #skills ul{ 377 | margin: 0.05in 0 0.15in; 378 | padding: 0; 379 | } 380 | 381 | #disclaimer{ 382 | position: absolute; 383 | bottom: 0; 384 | right: var(--sidebar-horizontal-padding); 385 | font-size: 0.75rem; 386 | font-style: italic; 387 | line-height: 1.1; 388 | text-align: right; 389 | color: #777; 390 | } 391 | 392 | #disclaimer code{ 393 | color: #666; 394 | font-family: "Source Code Pro"; 395 | font-weight: 400; 396 | font-style: normal; 397 | } 398 | 399 | /* Page breaks */ 400 | 401 | h2 { 402 | break-after: avoid; 403 | } 404 | 405 | .blocks { 406 | break-inside: avoid; 407 | } 408 | 409 | /* Paged.js viewer */ 410 | 411 | @media screen { 412 | body { 413 | background-color: var(--viewer-background-color); 414 | margin: 0; /* for mobile */ 415 | width: calc(var(--pagedjs-width) + 2 * var(--viewer-pages-spacing)); /* for mobile */ 416 | } 417 | .pagedjs_pages { 418 | max-width: var(--pagedjs-width); 419 | margin: 0 auto; 420 | display: flex; 421 | flex-direction: column; 422 | } 423 | .pagedjs_page { 424 | box-shadow: 0 0 calc(0.66667 * var(--viewer-pages-spacing)) var(--viewer-shadow-color); 425 | margin: var(--viewer-pages-spacing) 0; 426 | } 427 | } 428 | @media screen and (min-width: 8.5in) { 429 | /* not a mobile */ 430 | body { 431 | margin: auto; 432 | width: unset; 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/paged-0.18/css/resume.css: -------------------------------------------------------------------------------- 1 | @page{ 2 | size: letter portrait; 3 | margin: 1in 0.5in 1in 0.25in; 4 | } 5 | 6 | *{ 7 | box-sizing: border-box; 8 | } 9 | 10 | :root{ 11 | --page-width: 8.5in; 12 | --margin-right: 0.5in; 13 | --margin-left: 0.25in; 14 | --content-width: calc(var(--page-width) - var(--margin-right) - var(--margin-left)); 15 | --root-font-size: 12pt; 16 | --sidebar-width: 15rem; 17 | --sidebar-background-color: #f2f2f2; 18 | --main-width: calc(var(--content-width) - var(--sidebar-width)); 19 | --decorator-horizontal-margin: 0.2in; 20 | 21 | --sidebar-horizontal-padding: 0.2in; 22 | 23 | /* XXX: using px for very good precision control */ 24 | --decorator-outer-offset-top: 10px; 25 | --decorator-outer-offset-left: -5.5px; 26 | --decorator-border-width: 1px; 27 | --decorator-outer-dim: 9px; 28 | --decorator-border: 1px solid #ccc; 29 | 30 | --row-blocks-padding-top: 0.5rem; 31 | --date-block-width: 0.7in; 32 | 33 | --main-blocks-title-icon-offset-left: calc(-17pt - 0.25 * var(--root-font-size)); 34 | 35 | --viewer-background-color: #dcdcdc; 36 | --viewer-pages-spacing: 12px; 37 | --viewer-shadow-color: #313131; 38 | } 39 | 40 | .pagedjs_page { 41 | --content-area-height: calc(var(--pagedjs-height) - var(--pagedjs-margin-top) - var(--pagedjs-margin-bottom)); 42 | --sidebar-background-width: calc(var(--pagedjs-margin-right) + var(--sidebar-width)); 43 | } 44 | 45 | @page{ 46 | background: linear-gradient(to left, var(--sidebar-background-color), var(--sidebar-background-color) var(--sidebar-background-width), white var(--sidebar-background-width)); 47 | } 48 | 49 | html { 50 | font-size: var(--root-font-size); 51 | } 52 | 53 | body{ 54 | width: var(--content-width); 55 | font-family: "Open Sans", sans-serif; 56 | font-weight: 300; 57 | line-height: 1.3; 58 | color: #444; 59 | hyphens: auto; 60 | } 61 | 62 | h1, h2, h3{ 63 | margin: 0; 64 | color: #000; 65 | } 66 | 67 | #main > h1, #aside > h1, #disclaimer > h2 { 68 | display: none; 69 | } 70 | 71 | li{ 72 | list-style-type: none; 73 | } 74 | 75 | a{ 76 | text-decoration: none; 77 | } 78 | 79 | img{ 80 | max-width: 100%; 81 | } 82 | 83 | #main{ 84 | width: var(--main-width); 85 | padding: 0 0.25in 0 0.25in; 86 | font-size: 0.7rem; 87 | float: left; 88 | } 89 | 90 | #aside{ 91 | position: relative; /* for disclaimer */ 92 | height: var(--content-area-height); 93 | } 94 | 95 | .aside{ 96 | width: var(--sidebar-width); 97 | padding: 0.6in var(--sidebar-horizontal-padding); 98 | font-size: 0.8rem; 99 | float: right; 100 | position: absolute; 101 | right: 0; 102 | } 103 | 104 | /* main */ 105 | 106 | /** big title **/ 107 | h1, h2{ 108 | text-transform: uppercase; 109 | } 110 | 111 | #title{ 112 | position: relative; 113 | left: 0.55in; 114 | margin: auto 0.55in 0.3in auto; 115 | line-height: 1.2; 116 | } 117 | 118 | #title h1{ 119 | font-weight: 300; 120 | font-size: 1.8rem; 121 | line-height: 1.5; 122 | } 123 | 124 | #title h3{ 125 | font-size: 0.8rem; 126 | } 127 | 128 | 129 | /*** categorial blocks ***/ 130 | 131 | .main-block{ 132 | margin-top: 0.1in; 133 | } 134 | 135 | #main h2{ 136 | position: relative; 137 | top: var(--row-blocks-padding-top); 138 | /* XXX: 0.5px for aligning fx printing */ 139 | left: calc((var(--date-block-width) + var(--decorator-horizontal-margin))); 140 | font-weight: 400; 141 | font-size: 1.1rem; 142 | color: #555; 143 | } 144 | 145 | #main h2 > i{ 146 | /* use absolute position to prevent icon's width from misaligning title */ 147 | /* assigning a fixed width here is no better due to needing to align decorator 148 | line too */ 149 | position: absolute; 150 | left: var(--main-blocks-title-icon-offset-left); 151 | z-index: 1; /* over decorator line */ 152 | color: #444; 153 | } 154 | 155 | #main h2::after{ /* extends the decorator line */ 156 | height: calc(var(--row-blocks-padding-top) * 2); 157 | position: relative; 158 | top: calc(-1 * var(--row-blocks-padding-top)); 159 | /* XXX: 0.5px for aligning fx printing */ 160 | left: calc(-1 * var(--decorator-horizontal-margin)); 161 | display: block; 162 | border-left: var(--decorator-border); 163 | z-index: 0; 164 | line-height: 0; 165 | font-size: 0; 166 | content: ' '; 167 | } 168 | 169 | /**** minor tweaks on the icon fonts ****/ 170 | #main h2 > .fa-graduation-cap{ 171 | left: calc(var(--main-blocks-title-icon-offset-left) - 2pt); 172 | top: 2pt; 173 | } 174 | 175 | #main h2 > .fa-suitcase{ 176 | top: 1pt; 177 | } 178 | 179 | #main h2 > .fa-folder-open{ 180 | top: 1.5pt; 181 | } 182 | 183 | /**** individual row blocks (date - decorator - details) ****/ 184 | 185 | .blocks{ 186 | display: flex; 187 | flex-flow: row nowrap; 188 | } 189 | 190 | .blocks > div{ 191 | padding-top: var(--row-blocks-padding-top); 192 | } 193 | 194 | .date{ 195 | flex: 0 0 var(--date-block-width); 196 | padding-top: calc(var(--row-blocks-padding-top) + 0.25rem) !important; 197 | padding-right: var(--decorator-horizontal-margin); 198 | font-size: 0.7rem; 199 | text-align: right; 200 | line-height: 1; 201 | max-width: var(--date-block-width); 202 | } 203 | 204 | .date span{ 205 | display: block; 206 | text-align: center; 207 | } 208 | 209 | .date span:nth-child(2)::before{ 210 | position: relative; 211 | top: 0.1rem; 212 | right: 0; 213 | display: block; 214 | height: 1rem; 215 | content: '|'; 216 | } 217 | 218 | .decorator{ 219 | flex: 0 0 0; 220 | position: relative; 221 | width: 2pt; 222 | min-height: 100%; 223 | border-left: var(--decorator-border); 224 | } 225 | 226 | /* 227 | * XXX: Use two filled circles to achieve the circle-with-white-border effect. 228 | * The normal technique of only using one pseudo element and 229 | * border: 1px solid white; style makes firefox erroneously either: 230 | * 1) overflows the grayshade background (if no background-clip is set), or 231 | * 2) shows decorator line which should've been masked by the white border 232 | * 233 | */ 234 | 235 | .decorator::before{ 236 | position: absolute; 237 | top: var(--decorator-outer-offset-top); 238 | left: var(--decorator-outer-offset-left); 239 | content: ' '; 240 | display: block; 241 | width: var(--decorator-outer-dim); 242 | height: var(--decorator-outer-dim); 243 | border-radius: calc(var(--decorator-outer-dim) / 2); 244 | background-color: #fff; 245 | } 246 | 247 | .decorator::after{ 248 | position: absolute; 249 | top: calc(var(--decorator-outer-offset-top) + var(--decorator-border-width)); 250 | left: calc(var(--decorator-outer-offset-left) + var(--decorator-border-width)); 251 | content: ' '; 252 | display: block; 253 | width: calc(var(--decorator-outer-dim) - (var(--decorator-border-width) * 2)); 254 | height: calc(var(--decorator-outer-dim) - (var(--decorator-border-width) * 2)); 255 | border-radius: calc((var(--decorator-outer-dim) - (var(--decorator-border-width) * 2)) / 2); 256 | background-color: #555; 257 | } 258 | 259 | .blocks:last-child .decorator{ /* slightly shortens it */ 260 | margin-bottom: 0.25in; 261 | } 262 | 263 | /***** fine-tunes on the details block where the real juice is *****/ 264 | 265 | .details{ 266 | flex: 1 0 0; 267 | padding-left: var(--decorator-horizontal-margin); 268 | padding-top: calc(var(--row-blocks-padding-top) - 0.05rem) !important; /* not sure why but this is needed for better alignment */ 269 | } 270 | 271 | .details header{ 272 | color: #000; 273 | } 274 | 275 | .details h3{ 276 | font-size: 0.8rem; 277 | } 278 | 279 | .main-block:not(.concise) .details div{ 280 | margin: 0.18in 0 0.1in 0; 281 | } 282 | 283 | .main-block:not(.concise) .details div:empty { 284 | margin: 0; 285 | } 286 | 287 | .main-block:not(.concise) .blocks:last-child .details div{ 288 | margin-bottom: 0; 289 | } 290 | 291 | .main-block.concise .details div:not(.concise){ 292 | /* use padding to work around the fact that margin doesn't affect floated 293 | neighboring elements */ 294 | padding: 0.05in 0 0.07in 0; 295 | } 296 | 297 | .details .place{ 298 | float: left; 299 | font-size: 0.75rem; 300 | } 301 | 302 | .details .location{ 303 | float: right; 304 | } 305 | 306 | .details div{ 307 | clear: both; 308 | } 309 | 310 | /***** fine-tunes on the lists... *****/ 311 | 312 | #main ul{ 313 | padding-left: 0.07in; 314 | margin: 0.08in 0; 315 | } 316 | 317 | #main li{ 318 | margin: 0 0 0.025in 0; 319 | } 320 | 321 | /****** customize list symbol style ******/ 322 | #main li::before{ 323 | position: relative; 324 | margin-left: -4.25pt; 325 | content: '• '; 326 | } 327 | 328 | .aside li::before { 329 | content: none; 330 | } 331 | 332 | .details .concise ul{ 333 | margin: 0 !important; 334 | -webkit-columns: 2; 335 | -moz-columns: 2; 336 | columns: 2; 337 | } 338 | 339 | .details .concise li{ 340 | margin: 0 !important; 341 | } 342 | 343 | .details .concise li{ 344 | margin-left: 0 !important; 345 | } 346 | 347 | 348 | 349 | /* sidebar */ 350 | 351 | .aside h2{ 352 | font-weight: 400; 353 | font-size: 1.1rem; 354 | } 355 | 356 | .aside .level2{ 357 | margin-top: 0.5in; 358 | } 359 | 360 | #contact ul{ 361 | margin-top: 0.05in; 362 | padding-left: 0; 363 | font-weight: 400; 364 | line-height: 1.75; 365 | } 366 | 367 | #contact li > i{ 368 | width: 0.9rem; /* for text alignment */ 369 | text-align: right; 370 | } 371 | 372 | #skills{ 373 | line-height: 1.5; 374 | } 375 | 376 | #skills ul{ 377 | margin: 0.05in 0 0.15in; 378 | padding: 0; 379 | } 380 | 381 | #disclaimer{ 382 | position: absolute; 383 | bottom: 0; 384 | right: var(--sidebar-horizontal-padding); 385 | font-size: 0.75rem; 386 | font-style: italic; 387 | line-height: 1.1; 388 | text-align: right; 389 | color: #777; 390 | } 391 | 392 | #disclaimer code{ 393 | color: #666; 394 | font-family: "Source Code Pro"; 395 | font-weight: 400; 396 | font-style: normal; 397 | } 398 | 399 | /* Page breaks */ 400 | 401 | h2 { 402 | break-after: avoid; 403 | } 404 | 405 | .blocks { 406 | break-inside: avoid; 407 | } 408 | 409 | /* Paged.js viewer */ 410 | 411 | @media screen { 412 | body { 413 | background-color: var(--viewer-background-color); 414 | margin: 0; /* for mobile */ 415 | width: calc(var(--pagedjs-width) + 2 * var(--viewer-pages-spacing)); /* for mobile */ 416 | } 417 | .pagedjs_pages { 418 | max-width: var(--pagedjs-width); 419 | margin: 0 auto; 420 | display: flex; 421 | flex-direction: column; 422 | } 423 | .pagedjs_page { 424 | box-shadow: 0 0 calc(0.66667 * var(--viewer-pages-spacing)) var(--viewer-shadow-color); 425 | margin: var(--viewer-pages-spacing) 0; 426 | } 427 | } 428 | @media screen and (min-width: 8.5in) { 429 | /* not a mobile */ 430 | body { 431 | margin: auto; 432 | width: unset; 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/paged-0.18.1/css/resume.css: -------------------------------------------------------------------------------- 1 | @page{ 2 | size: letter portrait; 3 | margin: 1in 0.5in 1in 0.25in; 4 | } 5 | 6 | *{ 7 | box-sizing: border-box; 8 | } 9 | 10 | :root{ 11 | --page-width: 8.5in; 12 | --margin-right: 0.5in; 13 | --margin-left: 0.25in; 14 | --content-width: calc(var(--page-width) - var(--margin-right) - var(--margin-left)); 15 | --root-font-size: 12pt; 16 | --sidebar-width: 15rem; 17 | --sidebar-background-color: #f2f2f2; 18 | --main-width: calc(var(--content-width) - var(--sidebar-width)); 19 | --decorator-horizontal-margin: 0.2in; 20 | 21 | --sidebar-horizontal-padding: 0.2in; 22 | 23 | /* XXX: using px for very good precision control */ 24 | --decorator-outer-offset-top: 10px; 25 | --decorator-outer-offset-left: -5.5px; 26 | --decorator-border-width: 1px; 27 | --decorator-outer-dim: 9px; 28 | --decorator-border: 1px solid #ccc; 29 | 30 | --row-blocks-padding-top: 0.5rem; 31 | --date-block-width: 0.7in; 32 | 33 | --main-blocks-title-icon-offset-left: calc(-17pt - 0.25 * var(--root-font-size)); 34 | 35 | --viewer-background-color: #dcdcdc; 36 | --viewer-pages-spacing: 12px; 37 | --viewer-shadow-color: #313131; 38 | } 39 | 40 | .pagedjs_page { 41 | --content-area-height: calc(var(--pagedjs-height) - var(--pagedjs-margin-top) - var(--pagedjs-margin-bottom)); 42 | --sidebar-background-width: calc(var(--pagedjs-margin-right) + var(--sidebar-width)); 43 | } 44 | 45 | @page{ 46 | background: linear-gradient(to left, var(--sidebar-background-color), var(--sidebar-background-color) var(--sidebar-background-width), white var(--sidebar-background-width)); 47 | } 48 | 49 | html { 50 | font-size: var(--root-font-size); 51 | } 52 | 53 | body{ 54 | width: var(--content-width); 55 | font-family: "Open Sans", sans-serif; 56 | font-weight: 300; 57 | line-height: 1.3; 58 | color: #444; 59 | hyphens: auto; 60 | } 61 | 62 | h1, h2, h3{ 63 | margin: 0; 64 | color: #000; 65 | } 66 | 67 | #main > h1, #aside > h1, #disclaimer > h2 { 68 | display: none; 69 | } 70 | 71 | li{ 72 | list-style-type: none; 73 | } 74 | 75 | a{ 76 | text-decoration: none; 77 | } 78 | 79 | img{ 80 | max-width: 100%; 81 | } 82 | 83 | #main{ 84 | width: var(--main-width); 85 | padding: 0 0.25in 0 0.25in; 86 | font-size: 0.7rem; 87 | float: left; 88 | } 89 | 90 | #aside{ 91 | position: relative; /* for disclaimer */ 92 | height: var(--content-area-height); 93 | } 94 | 95 | .aside{ 96 | width: var(--sidebar-width); 97 | padding: 0.6in var(--sidebar-horizontal-padding); 98 | font-size: 0.8rem; 99 | float: right; 100 | position: absolute; 101 | right: 0; 102 | } 103 | 104 | /* main */ 105 | 106 | /** big title **/ 107 | h1, h2{ 108 | text-transform: uppercase; 109 | } 110 | 111 | #title{ 112 | position: relative; 113 | left: 0.55in; 114 | margin: auto 0.55in 0.3in auto; 115 | line-height: 1.2; 116 | } 117 | 118 | #title h1{ 119 | font-weight: 300; 120 | font-size: 1.8rem; 121 | line-height: 1.5; 122 | } 123 | 124 | #title h3{ 125 | font-size: 0.8rem; 126 | } 127 | 128 | 129 | /*** categorial blocks ***/ 130 | 131 | .main-block{ 132 | margin-top: 0.1in; 133 | } 134 | 135 | #main h2{ 136 | position: relative; 137 | top: var(--row-blocks-padding-top); 138 | /* XXX: 0.5px for aligning fx printing */ 139 | left: calc((var(--date-block-width) + var(--decorator-horizontal-margin))); 140 | font-weight: 400; 141 | font-size: 1.1rem; 142 | color: #555; 143 | } 144 | 145 | #main h2 > i{ 146 | /* use absolute position to prevent icon's width from misaligning title */ 147 | /* assigning a fixed width here is no better due to needing to align decorator 148 | line too */ 149 | position: absolute; 150 | left: var(--main-blocks-title-icon-offset-left); 151 | z-index: 1; /* over decorator line */ 152 | color: #444; 153 | } 154 | 155 | #main h2::after{ /* extends the decorator line */ 156 | height: calc(var(--row-blocks-padding-top) * 2); 157 | position: relative; 158 | top: calc(-1 * var(--row-blocks-padding-top)); 159 | /* XXX: 0.5px for aligning fx printing */ 160 | left: calc(-1 * var(--decorator-horizontal-margin)); 161 | display: block; 162 | border-left: var(--decorator-border); 163 | z-index: 0; 164 | line-height: 0; 165 | font-size: 0; 166 | content: ' '; 167 | } 168 | 169 | /**** minor tweaks on the icon fonts ****/ 170 | #main h2 > .fa-graduation-cap{ 171 | left: calc(var(--main-blocks-title-icon-offset-left) - 2pt); 172 | top: 2pt; 173 | } 174 | 175 | #main h2 > .fa-suitcase{ 176 | top: 1pt; 177 | } 178 | 179 | #main h2 > .fa-folder-open{ 180 | top: 1.5pt; 181 | } 182 | 183 | /**** individual row blocks (date - decorator - details) ****/ 184 | 185 | .blocks{ 186 | display: flex; 187 | flex-flow: row nowrap; 188 | } 189 | 190 | .blocks > div{ 191 | padding-top: var(--row-blocks-padding-top); 192 | } 193 | 194 | .date{ 195 | flex: 0 0 var(--date-block-width); 196 | padding-top: calc(var(--row-blocks-padding-top) + 0.25rem) !important; 197 | padding-right: var(--decorator-horizontal-margin); 198 | font-size: 0.7rem; 199 | text-align: right; 200 | line-height: 1; 201 | max-width: var(--date-block-width); 202 | } 203 | 204 | .date span{ 205 | display: block; 206 | text-align: center; 207 | } 208 | 209 | .date span:nth-child(2)::before{ 210 | position: relative; 211 | top: 0.1rem; 212 | right: 0; 213 | display: block; 214 | height: 1rem; 215 | content: '|'; 216 | } 217 | 218 | .decorator{ 219 | flex: 0 0 0; 220 | position: relative; 221 | width: 2pt; 222 | min-height: 100%; 223 | border-left: var(--decorator-border); 224 | } 225 | 226 | /* 227 | * XXX: Use two filled circles to achieve the circle-with-white-border effect. 228 | * The normal technique of only using one pseudo element and 229 | * border: 1px solid white; style makes firefox erroneously either: 230 | * 1) overflows the grayshade background (if no background-clip is set), or 231 | * 2) shows decorator line which should've been masked by the white border 232 | * 233 | */ 234 | 235 | .decorator::before{ 236 | position: absolute; 237 | top: var(--decorator-outer-offset-top); 238 | left: var(--decorator-outer-offset-left); 239 | content: ' '; 240 | display: block; 241 | width: var(--decorator-outer-dim); 242 | height: var(--decorator-outer-dim); 243 | border-radius: calc(var(--decorator-outer-dim) / 2); 244 | background-color: #fff; 245 | } 246 | 247 | .decorator::after{ 248 | position: absolute; 249 | top: calc(var(--decorator-outer-offset-top) + var(--decorator-border-width)); 250 | left: calc(var(--decorator-outer-offset-left) + var(--decorator-border-width)); 251 | content: ' '; 252 | display: block; 253 | width: calc(var(--decorator-outer-dim) - (var(--decorator-border-width) * 2)); 254 | height: calc(var(--decorator-outer-dim) - (var(--decorator-border-width) * 2)); 255 | border-radius: calc((var(--decorator-outer-dim) - (var(--decorator-border-width) * 2)) / 2); 256 | background-color: #555; 257 | } 258 | 259 | .blocks:last-child .decorator{ /* slightly shortens it */ 260 | margin-bottom: 0.25in; 261 | } 262 | 263 | /***** fine-tunes on the details block where the real juice is *****/ 264 | 265 | .details{ 266 | flex: 1 0 0; 267 | padding-left: var(--decorator-horizontal-margin); 268 | padding-top: calc(var(--row-blocks-padding-top) - 0.05rem) !important; /* not sure why but this is needed for better alignment */ 269 | } 270 | 271 | .details header{ 272 | color: #000; 273 | } 274 | 275 | .details h3{ 276 | font-size: 0.8rem; 277 | } 278 | 279 | .main-block:not(.concise) .details div{ 280 | margin: 0.18in 0 0.1in 0; 281 | } 282 | 283 | .main-block:not(.concise) .details div:empty { 284 | margin: 0; 285 | } 286 | 287 | .main-block:not(.concise) .blocks:last-child .details div{ 288 | margin-bottom: 0; 289 | } 290 | 291 | .main-block.concise .details div:not(.concise){ 292 | /* use padding to work around the fact that margin doesn't affect floated 293 | neighboring elements */ 294 | padding: 0.05in 0 0.07in 0; 295 | } 296 | 297 | .details .place{ 298 | float: left; 299 | font-size: 0.75rem; 300 | } 301 | 302 | .details .location{ 303 | float: right; 304 | } 305 | 306 | .details div{ 307 | clear: both; 308 | } 309 | 310 | /***** fine-tunes on the lists... *****/ 311 | 312 | #main ul{ 313 | padding-left: 0.07in; 314 | margin: 0.08in 0; 315 | } 316 | 317 | #main li{ 318 | margin: 0 0 0.025in 0; 319 | } 320 | 321 | /****** customize list symbol style ******/ 322 | #main li::before{ 323 | position: relative; 324 | margin-left: -4.25pt; 325 | content: '• '; 326 | } 327 | 328 | .aside li::before { 329 | content: none; 330 | } 331 | 332 | .details .concise ul{ 333 | margin: 0 !important; 334 | -webkit-columns: 2; 335 | -moz-columns: 2; 336 | columns: 2; 337 | } 338 | 339 | .details .concise li{ 340 | margin: 0 !important; 341 | } 342 | 343 | .details .concise li{ 344 | margin-left: 0 !important; 345 | } 346 | 347 | 348 | 349 | /* sidebar */ 350 | 351 | .aside h2{ 352 | font-weight: 400; 353 | font-size: 1.1rem; 354 | } 355 | 356 | .aside .level2{ 357 | margin-top: 0.5in; 358 | } 359 | 360 | #contact ul{ 361 | margin-top: 0.05in; 362 | padding-left: 0; 363 | font-weight: 400; 364 | line-height: 1.75; 365 | } 366 | 367 | #contact li > i{ 368 | width: 0.9rem; /* for text alignment */ 369 | text-align: right; 370 | } 371 | 372 | #skills{ 373 | line-height: 1.5; 374 | } 375 | 376 | #skills ul{ 377 | margin: 0.05in 0 0.15in; 378 | padding: 0; 379 | } 380 | 381 | #disclaimer{ 382 | position: absolute; 383 | bottom: 0; 384 | right: var(--sidebar-horizontal-padding); 385 | font-size: 0.75rem; 386 | font-style: italic; 387 | line-height: 1.1; 388 | text-align: right; 389 | color: #777; 390 | } 391 | 392 | #disclaimer code{ 393 | color: #666; 394 | font-family: "Source Code Pro"; 395 | font-weight: 400; 396 | font-style: normal; 397 | } 398 | 399 | /* Page breaks */ 400 | 401 | h2 { 402 | break-after: avoid; 403 | } 404 | 405 | .blocks { 406 | break-inside: avoid; 407 | } 408 | 409 | /* Paged.js viewer */ 410 | 411 | @media screen { 412 | body { 413 | background-color: var(--viewer-background-color); 414 | margin: 0; /* for mobile */ 415 | width: calc(var(--pagedjs-width) + 2 * var(--viewer-pages-spacing)); /* for mobile */ 416 | } 417 | .pagedjs_pages { 418 | max-width: var(--pagedjs-width); 419 | margin: 0 auto; 420 | display: flex; 421 | flex-direction: column; 422 | } 423 | .pagedjs_page { 424 | box-shadow: 0 0 calc(0.66667 * var(--viewer-pages-spacing)) var(--viewer-shadow-color); 425 | margin: var(--viewer-pages-spacing) 0; 426 | } 427 | } 428 | @media screen and (min-width: 8.5in) { 429 | /* not a mobile */ 430 | body { 431 | margin: auto; 432 | width: unset; 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Jiena Gu McLellan’s resume 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |

Aside

27 |
28 |

Contact Info

29 | 34 |
35 |
36 |

R packages

37 | 40 |

noteMD

41 | 46 |

forestry

47 | 51 |
52 |
53 |
54 |

Main

55 |
56 |

Jiena Gu McLellan

57 |
58 |
59 |

Professional Experience

60 |
61 |

Software Architect

62 |

McKinsey & Company

63 |

Tampa, FL

64 |

2021 - Present

65 |
66 |
67 |

Senior Software Developer

68 |

McKinsey & Company

69 |

Atlanta, GA

70 |

2019 - 2021

71 |
    72 |
  • Develop tools and web applications for data analytics
  • 73 |
  • Develop and mantain R and Python packages
  • 74 |
75 |
76 |
77 |

Shiny Developer/ R Programmer Analyst

78 |

Beef Cattle Institute at Kansas State University

79 |

Manhattan, KS

80 |

2016 - 2019

81 |
    82 |
  • Build big data interactive dashboard (shiny) for clients generating dynamic report.
  • 83 |
  • Develop R program/ packages for research projects.
  • 84 |
  • Build shiny dashboard (including user authentication) that linked to a mobile app, mobile app users can visualize their own data and generate reports with the same log in as the mobile side.
  • 85 |
  • Statistical consulting.
  • 86 |
87 |
88 |
89 |

Graduate Teaching Assistant

90 |

Department of Statistics at Kansas State University

91 |

Manhattan, KS

92 |

2014 - 2016

93 |
    94 |
  • Teach Stat 325 (Introduction to Statistics) course as the instructor.
  • 95 |
  • Grade Assignments and exam.
  • 96 |
97 |
98 |
99 |

Statistics Specialist

100 |

Internatioanl Admission and Recruiting office at Kansas State University

101 |

Manhattan, KS

102 |

2015 - 2016

103 |
    104 |
  • Build interactive dashboard (shiny) to visualize the admission data.
  • 105 |
106 |
107 |
108 |
109 |

Education

110 |
111 |

Kansas State University

112 |

Graduate Certificate in Data Analytics

113 |

Manhattan, KS, USA

114 |

2017

115 |
116 |
117 |

Kansas State University

118 |

M.S. in Statistics

119 |

Manhattan, KS, USA

120 |

2016

121 |
122 |
123 |

South China Normal University

124 |

B.S. in Bioengineering

125 |

Guangzhou, China

126 |

2013

127 |
128 |
129 |
130 |

R/ Shiny Publications

131 |
132 |

Building an Interactive Tutorial Website for R and Statistics

133 |

C2C Digital Magazine: Vol. 1 : Iss. 10 , Article 7.

134 |

N/A

135 |

2019

136 |

Jiena Gu McLellan

137 |
138 |
    139 |
  • faq

  • 140 |
141 |

faq

142 |
    143 |
  • create a FAQ (Frequently Asked Questions) page

  • 144 |
  • Repo: github.com/jienagu/faq

  • 145 |
  • flashCard

  • 146 |
147 |

flashCard

148 |
    149 |
  • create a flash card

  • 150 |
  • github.com/jienagu/flashCard

  • 151 |
  • Skills:

  • 152 |
153 |

R, Python, javascript, Shiny, html, Rcpp (C++), CSS

154 |
155 |
156 |
157 |

Geographic Information Visualization by R

158 |

C2C Digital Magazine: Vol. 1 : Iss. 8 , Article 4.

159 |

N/A

160 |

2018

161 |

Jiena Gu McLellan

162 |
163 |
164 |

Sentiment Analysis of Real-time Twitter Data

165 |

C2C Digital Magazine: Vol. 1 : Iss. 7 , Article 8.

166 |

N/A

167 |

2017

168 |

Jiena Gu McLellan

169 |
170 |
171 |

Shiny App: An Efficient and Creative Communicator of Big Data

172 |

C2C Digital Magazine: Vol. 1 : Iss. 6 , Article 6.

173 |

N/A

174 |

2016

175 |

Jiena Gu

176 |
177 |
178 |
179 |

Peer-Reviewed Publications and Talks

180 |
181 |

Effect of Duration and Onset of Clinical Signs on Short-Term Outcome of Dogs with Hansen Type I Thoracolumbar Intervertebral Disc Extrusion

182 |

Veterinary and Comparative Orthopaedics and Traumatology

183 |

N/A

184 |

2020

185 |

DA Upchurch, WC Renberg, HS Turner, JG McLellan

186 |
187 |
188 |

learnr: Interactive R tutorials

189 |

TA for Yihui Xie (Instructor) at Advanced Rmarkdown Workshop at RStudio Conference 2019

190 |

Austin, TX

191 |

2019

192 |

Jiena Gu McLellan

193 |
194 |
195 |

Evaluation of three classification models to predict risk class of cattle cohorts developing bovine respiratory disease within the first 14 days on feed using on-arrival and/or prearrival information.

196 |

Computers and Electronics in Agriculture. 156 (2019) 439-446.

197 |

N/A

198 |

2019

199 |

David E. Amrine, Jiena G. McLellan, Brad J. White, Robert L. Larson, David G. Renter, 200 | Mike Sanderson.

201 |
202 |
203 |

Evaluation of animal-to-animal and community contact structures determined by a real-time location system for correlation with and prediction of new bovine respiratory disease diagnoses in beef cattle during the first 28 days after feedlot entry.

204 |

American Journal of Veterinary Research [01 Dec 2018, 79(12):1277-1286]

205 |

Kansas, USA

206 |

2018

207 |

Shane DD, McLellan JG, White BJ, Larson RL, Amrine DE, Sanderson MW, Apley MD.

208 |
209 |
210 |

R for Data Analytics & Interactive Visualization

211 |

Manhattan Data Analytics Meetup

212 |

Manhattan, KS

213 |

2018

214 |

Jiena Gu McLellan

215 |
216 |
217 |
218 |

Personal Shiny Projects

219 |
220 |

DT Editor/ DT Editor Shiny Module

221 | 225 |

N/A

226 |

Repo: https://github.com/jienagu/DT-Editor

227 |

2019

228 |
229 |
230 |

rpivotTableMD

231 |
    232 |
  • Intro: a shiny app demonstrates rpivotTable dynamically interacting with Rmarkdown reports (pdf or word) from a shiny app platform
  • 233 |
234 |

N/A

235 |

Repo: https://github.com/jienagu/rpivotTableMD

236 |

2019

237 |
238 |
239 |

D3 folded charts

240 |
    241 |
  • Hierarchies Design of Data Visualization for shiny: Drilling down from graphs to table/graphs similar with Power BI; generate dynamic reports.
  • 242 |
243 |

N/A

244 |

Repo: https://github.com/jienagu/D3_folded_charts

245 |

2019

246 |
247 |
248 |

Shiny Template

249 |
    250 |
  • Intro: a shiny template is to mimic questions/survey type of UI design to bring the best user-experience.
  • 251 |
252 |

N/A

253 |

Repo: https://github.com/jienagu/Shiny_Template

254 |

2019

255 |
256 |
257 |

PlayR: Interacitve Tutorial Website

258 |
    259 |
  • Intro: an interactive tutorial website (PlayR) built by learnr package.
  • 260 |
261 |

N/A

262 |

Repo: https://github.com/jienagu/Interactive_tutorial_website

263 |

2019

264 |
265 |
266 |

Shiny Interactive Presentation

267 |
    268 |
  • Intro: Generating interactive presentation from shiny side
  • 269 |
270 |

N/A

271 |

Repo: https://github.com/jienagu/Shiny_Interactive_Presentation

272 |

2019

273 |
274 |
275 |
276 |

Grant & Awards

277 |
278 |

The Development Grant for MIS665 through K-State global campus

279 |

Intro: Develop MIS 665 Business Analytics and Data Mining course

280 |

Manhattan 281 | KS

282 |

2019

283 |
284 |

This resume was made with the R package pagedown.

285 |

Last updated on 2022-08-29.

286 |
287 |
288 |
289 |
290 | 291 | 368 | 369 | 370 | 371 | 372 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/paged-0.10/js/hooks.js: -------------------------------------------------------------------------------- 1 | // Hooks for paged.js 2 | { 3 | // Utils 4 | let pandocMeta, pandocMetaToString; 5 | { 6 | let el = document.getElementById('pandoc-meta'); 7 | pandocMeta = el ? JSON.parse(el.firstChild.data) : {}; 8 | } 9 | 10 | pandocMetaToString = meta => { 11 | let el = document.createElement('div'); 12 | el.innerHTML = meta; 13 | return el.innerText; 14 | }; 15 | 16 | let isString = value => { 17 | return typeof value === 'string' || value instanceof String; 18 | }; 19 | 20 | let isArray = value => { 21 | return value && typeof value === 'object' && value.constructor === Array; 22 | }; 23 | 24 | // This hook is an attempt to fix https://github.com/rstudio/pagedown/issues/131 25 | // Sometimes, the {break-after: avoid;} declaration applied on headers 26 | // lead to duplicated headers. I hate this bug. 27 | // This is linked to the way the HTML source is written 28 | // When we have the \n character like this:
\n

...

29 | // the header may be duplicated. 30 | // But, if we have

...

without any \n, the problem disappear 31 | // I think this handler can fix most of cases 32 | // Obviously, we cannot suppress all the \n in the HTML document 33 | // because carriage returns are important in
 elements.
 34 |   // Tested with Chrome 76.0.3809.100/Windows
 35 |   Paged.registerHandlers(class extends Paged.Handler {
 36 |     constructor(chunker, polisher, caller) {
 37 |       super(chunker, polisher, caller);
 38 |       this.carriageReturn = String.fromCharCode(10);
 39 |     }
 40 | 
 41 |     checkNode(node) {
 42 |       if (!node) return;
 43 |       if (node.nodeType !== 3) return;
 44 |       if (node.textContent === this.carriageReturn) {
 45 |         node.remove();
 46 |       }
 47 |     }
 48 | 
 49 |     afterParsed(parsed) {
 50 |       let template = document.querySelector('template').content;
 51 |       const breakAfterAvoidElements = template.querySelectorAll('[data-break-after="avoid"], [data-break-before="avoid"]');
 52 |       for (let el of breakAfterAvoidElements) {
 53 |         this.checkNode(el.previousSibling);
 54 |         this.checkNode(el.nextSibling);
 55 |       }
 56 |     }
 57 |   });
 58 | 
 59 |   // This hook creates a list of abbreviations
 60 |   // Note: we also could implement this feature using a Pandoc filter
 61 |   Paged.registerHandlers(class extends Paged.Handler {
 62 |     constructor(chunker, polisher, caller) {
 63 |       super(chunker, polisher, caller);
 64 |     }
 65 |     beforeParsed(content) {
 66 |       const abbreviations = content.querySelectorAll('abbr');
 67 |       if(abbreviations.length === 0) return;
 68 |       const loaTitle = 'List of Abbreviations';
 69 |       const loaId = 'LOA';
 70 |       const tocList = content.querySelector('.toc ul');
 71 |       let listOfAbbreviations = document.createElement('div');
 72 |       let descriptionList = document.createElement('dl');
 73 |       content.appendChild(listOfAbbreviations);
 74 |       listOfAbbreviations.id = loaId;
 75 |       listOfAbbreviations.classList.add('section', 'front-matter', 'level1', 'loa');
 76 |       listOfAbbreviations.innerHTML = '

' + loaTitle + '

'; 77 | listOfAbbreviations.appendChild(descriptionList); 78 | for(let abbr of abbreviations) { 79 | if(!abbr.title) continue; 80 | let term = document.createElement('dt'); 81 | let definition = document.createElement('dd'); 82 | descriptionList.appendChild(term); 83 | descriptionList.appendChild(definition); 84 | term.innerHTML = abbr.innerHTML; 85 | definition.innerText = abbr.title; 86 | } 87 | if (tocList) { 88 | const loaTOCItem = document.createElement('li'); 89 | loaTOCItem.innerHTML = '' + loaTitle + ''; 90 | tocList.appendChild(loaTOCItem); 91 | } 92 | } 93 | }); 94 | 95 | // This hook moves the sections of class front-matter in the div.front-matter-container 96 | Paged.registerHandlers(class extends Paged.Handler { 97 | constructor(chunker, polisher, caller) { 98 | super(chunker, polisher, caller); 99 | } 100 | 101 | beforeParsed(content) { 102 | const frontMatter = content.querySelector('.front-matter-container'); 103 | if (!frontMatter) return; 104 | 105 | // move front matter sections in the front matter container 106 | const frontMatterSections = content.querySelectorAll('.level1.front-matter'); 107 | for (const section of frontMatterSections) { 108 | frontMatter.appendChild(section); 109 | } 110 | 111 | // add the class front-matter-ref to any element 112 | // referring to an entry in the front matter 113 | const anchors = content.querySelectorAll('a[href^="#"]:not([href*=":"])'); 114 | for (const a of anchors) { 115 | const ref = a.getAttribute('href').replace(/^#/, ''); 116 | const element = content.getElementById(ref); 117 | if (frontMatter.contains(element)) a.classList.add('front-matter-ref'); 118 | } 119 | 120 | // update the toc, lof and lot for front matter sections 121 | const frontMatterSectionsLinks = content.querySelectorAll('.toc .front-matter-ref, .lof .front-matter-ref, .lot .front-matter-ref'); 122 | for (let i = frontMatterSectionsLinks.length - 1; i >= 0; i--) { 123 | const listItem = frontMatterSectionsLinks[i].parentNode; 124 | const list = listItem.parentNode; 125 | list.insertBefore(listItem, list.firstChild); 126 | } 127 | } 128 | }); 129 | 130 | // This hook expands the links in the lists of figures and tables 131 | Paged.registerHandlers(class extends Paged.Handler { 132 | constructor(chunker, polisher, caller) { 133 | super(chunker, polisher, caller); 134 | } 135 | 136 | beforeParsed(content) { 137 | const items = content.querySelectorAll('.lof li, .lot li'); 138 | for (const item of items) { 139 | const anchor = item.firstChild; 140 | anchor.innerText = item.innerText; 141 | item.innerText = ''; 142 | item.append(anchor); 143 | } 144 | } 145 | }); 146 | 147 | // This hook adds spans for leading symbols 148 | Paged.registerHandlers(class extends Paged.Handler { 149 | constructor(chunker, polisher, caller) { 150 | super(chunker, polisher, caller); 151 | } 152 | 153 | beforeParsed(content) { 154 | const anchors = content.querySelectorAll('.toc a, .lof a, .lot a'); 155 | for (const a of anchors) { 156 | a.innerHTML = a.innerHTML + ''; 157 | } 158 | } 159 | }); 160 | 161 | // This hook appends short titles spans 162 | Paged.registerHandlers(class extends Paged.Handler { 163 | constructor(chunker, polisher, caller) { 164 | super(chunker, polisher, caller); 165 | } 166 | 167 | beforeParsed(content) { 168 | /* A factory returning a function that appends short titles spans. 169 | The text content of these spans are reused for running titles (see default.css). 170 | Argument: level - An integer between 1 and 6. 171 | */ 172 | function appendShortTitleSpans(level) { 173 | return () => { 174 | const divs = Array.from(content.querySelectorAll('.level' + level)); 175 | 176 | function addSpan(div) { 177 | const mainHeader = div.getElementsByTagName('h' + level)[0]; 178 | if (!mainHeader) return; 179 | const mainTitle = mainHeader.textContent; 180 | const spanSectionNumber = mainHeader.getElementsByClassName('header-section-number')[0]; 181 | const mainNumber = !!spanSectionNumber ? spanSectionNumber.textContent : ''; 182 | const runningTitle = 'shortTitle' in div.dataset ? mainNumber + ' ' + div.dataset.shortTitle : mainTitle; 183 | const span = document.createElement('span'); 184 | span.className = 'shorttitle' + level; 185 | span.innerText = runningTitle; 186 | span.style.display = "none"; 187 | mainHeader.appendChild(span); 188 | if (level == 1 && div.querySelector('.level2') === null) { 189 | let span2 = document.createElement('span'); 190 | span2.className = 'shorttitle2'; 191 | span2.innerText = ' '; 192 | span2.style.display = "none"; 193 | span.insertAdjacentElement('afterend', span2); 194 | } 195 | } 196 | 197 | for (const div of divs) { 198 | addSpan(div); 199 | } 200 | }; 201 | } 202 | 203 | appendShortTitleSpans(1)(); 204 | appendShortTitleSpans(2)(); 205 | } 206 | }); 207 | 208 | // Footnotes support 209 | Paged.registerHandlers(class extends Paged.Handler { 210 | constructor(chunker, polisher, caller) { 211 | super(chunker, polisher, caller); 212 | 213 | this.splittedParagraphRefs = []; 214 | } 215 | 216 | beforeParsed(content) { 217 | // remove footnotes in toc, lof, lot 218 | // see https://github.com/rstudio/pagedown/issues/54 219 | let removeThese = content.querySelectorAll('.toc .footnote, .lof .footnote, .lot .footnote'); 220 | for (const el of removeThese) { 221 | el.remove(); 222 | } 223 | 224 | let footnotes = content.querySelectorAll('.footnote'); 225 | 226 | for (let footnote of footnotes) { 227 | let parentElement = footnote.parentElement; 228 | let footnoteCall = document.createElement('a'); 229 | let footnoteNumber = footnote.dataset.pagedownFootnoteNumber; 230 | 231 | footnoteCall.className = 'footnote-ref'; // same class as Pandoc 232 | footnoteCall.setAttribute('id', 'fnref' + footnoteNumber); // same notation as Pandoc 233 | footnoteCall.setAttribute('href', '#' + footnote.id); 234 | footnoteCall.innerHTML = '' + footnoteNumber +''; 235 | parentElement.insertBefore(footnoteCall, footnote); 236 | 237 | // Here comes a hack. Fortunately, it works with Chrome and FF. 238 | let handler = document.createElement('p'); 239 | handler.className = 'footnoteHandler'; 240 | parentElement.insertBefore(handler, footnote); 241 | handler.appendChild(footnote); 242 | handler.style.display = 'inline-block'; 243 | handler.style.width = '100%'; 244 | handler.style.float = 'right'; 245 | handler.style.pageBreakInside = 'avoid'; 246 | } 247 | } 248 | 249 | afterPageLayout(pageFragment, page, breakToken) { 250 | function hasItemParent(node) { 251 | if (node.parentElement === null) { 252 | return false; 253 | } else { 254 | if (node.parentElement.tagName === 'LI') { 255 | return true; 256 | } else { 257 | return hasItemParent(node.parentElement); 258 | } 259 | } 260 | } 261 | // If a li item is broken, we store the reference of the p child element 262 | // see https://github.com/rstudio/pagedown/issues/23#issue-376548000 263 | if (breakToken !== undefined) { 264 | if (breakToken.node.nodeName === "#text" && hasItemParent(breakToken.node)) { 265 | this.splittedParagraphRefs.push(breakToken.node.parentElement.dataset.ref); 266 | } 267 | } 268 | } 269 | 270 | afterRendered(pages) { 271 | for (let page of pages) { 272 | const footnotes = page.element.querySelectorAll('.footnote'); 273 | if (footnotes.length === 0) { 274 | continue; 275 | } 276 | 277 | const pageContent = page.element.querySelector('.pagedjs_page_content'); 278 | let hr = document.createElement('hr'); 279 | let footnoteArea = document.createElement('div'); 280 | 281 | pageContent.style.display = 'flex'; 282 | pageContent.style.flexDirection = 'column'; 283 | 284 | hr.className = 'footnote-break'; 285 | hr.style.marginTop = 'auto'; 286 | hr.style.marginBottom = 0; 287 | hr.style.marginLeft = 0; 288 | hr.style.marginRight = 'auto'; 289 | pageContent.appendChild(hr); 290 | 291 | footnoteArea.className = 'footnote-area'; 292 | pageContent.appendChild(footnoteArea); 293 | 294 | for (let footnote of footnotes) { 295 | let handler = footnote.parentElement; 296 | 297 | footnoteArea.appendChild(footnote); 298 | handler.parentNode.removeChild(handler); 299 | 300 | footnote.innerHTML = '' + footnote.dataset.pagedownFootnoteNumber + '' + footnote.innerHTML; 301 | footnote.style.fontSize = 'x-small'; 302 | footnote.style.marginTop = 0; 303 | footnote.style.marginBottom = 0; 304 | footnote.style.paddingTop = 0; 305 | footnote.style.paddingBottom = 0; 306 | footnote.style.display = 'block'; 307 | } 308 | } 309 | 310 | for (let ref of this.splittedParagraphRefs) { 311 | let paragraphFirstPage = document.querySelector('[data-split-to="' + ref + '"]'); 312 | // We test whether the paragraph is empty 313 | // see https://github.com/rstudio/pagedown/issues/23#issue-376548000 314 | if (paragraphFirstPage.innerText === "") { 315 | paragraphFirstPage.parentElement.style.display = "none"; 316 | let paragraphSecondPage = document.querySelector('[data-split-from="' + ref + '"]'); 317 | paragraphSecondPage.parentElement.style.setProperty('list-style', 'inherit', 'important'); 318 | } 319 | } 320 | } 321 | }); 322 | 323 | // Support for "Chapter " label on section with class `.chapter` 324 | Paged.registerHandlers(class extends Paged.Handler { 325 | constructor(chunker, polisher, caller) { 326 | super(chunker, polisher, caller); 327 | 328 | this.options = pandocMeta['chapter_name']; 329 | 330 | let styles; 331 | if (isString(this.options)) { 332 | this.options = pandocMetaToString(this.options); 333 | styles = ` 334 | :root { 335 | --chapter-name-before: "${this.options}"; 336 | } 337 | `; 338 | } 339 | if (isArray(this.options)) { 340 | this.options = this.options.map(pandocMetaToString); 341 | styles = ` 342 | :root { 343 | --chapter-name-before: "${this.options[0]}"; 344 | --chapter-name-after: "${this.options[1]}"; 345 | } 346 | `; 347 | } 348 | if (styles) polisher.insert(styles); 349 | } 350 | 351 | beforeParsed(content) { 352 | const tocAnchors = content.querySelectorAll('.toc a[href^="#"]:not([href*=":"]'); 353 | for (const anchor of tocAnchors) { 354 | const ref = anchor.getAttribute('href').replace(/^#/, ''); 355 | const element = content.getElementById(ref); 356 | if (element.classList.contains('chapter')) { 357 | anchor.classList.add('chapter-ref'); 358 | } 359 | } 360 | } 361 | }); 362 | 363 | // Main text line numbering, 364 | // see https://github.com/rstudio/pagedown/issues/115 365 | // Original idea: Julien Taquet, thanks! 366 | Paged.registerHandlers(class extends Paged.Handler { 367 | constructor(chunker, polisher, caller) { 368 | super(chunker, polisher, caller); 369 | 370 | // get the number-lines option from Pandoc metavariables 371 | this.options = pandocMeta['number-lines']; 372 | // quit early if the "number-lines" option is false or missing 373 | if (!this.options) return; 374 | // retrieve the selector if provided, otherwise use the default selector 375 | this.selector = this.options.selector ? pandocMetaToString(this.options.selector) : '.level1:not(.front-matter) h1, .level1 h2, .level1 h3, .level1 p'; 376 | 377 | const styles = ` 378 | :root { 379 | --line-numbers-padding-right: 10px; 380 | --line-numbers-font-size: 8pt; 381 | } 382 | .pagedown-linenumbers-container { 383 | position: absolute; 384 | margin-top: var(--pagedjs-margin-top); 385 | right: calc(var(--pagedjs-width) - var(--pagedjs-margin-left)); 386 | } 387 | .maintextlinenumbers { 388 | position: absolute; 389 | right: 0; 390 | text-align: right; 391 | padding-right: var(--line-numbers-padding-right); 392 | font-size: var(--line-numbers-font-size); 393 | } 394 | `; 395 | polisher.insert(styles); 396 | 397 | this.resetLinesCounter(); 398 | } 399 | 400 | appendLineNumbersContainer(page) { 401 | const pagebox = page.element.querySelector('.pagedjs_pagebox'); 402 | const lineNumbersContainer = document.createElement('div'); 403 | lineNumbersContainer.classList.add('pagedown-linenumbers-container'); 404 | 405 | return pagebox.appendChild(lineNumbersContainer); 406 | } 407 | 408 | lineHeight(element) { 409 | // If the document stylesheet does not define a value for line-height, 410 | // Blink returns "normal". Therefore, parseInt may return NaN. 411 | return parseInt(getComputedStyle(element).lineHeight); 412 | } 413 | 414 | innerHeight(element) { 415 | let outerHeight = element.getBoundingClientRect().height; 416 | let {borderTopWidth, 417 | borderBottomWidth, 418 | paddingTop, 419 | paddingBottom} = getComputedStyle(element); 420 | 421 | borderTopWidth = parseFloat(borderTopWidth); 422 | borderBottomWidth = parseFloat(borderBottomWidth); 423 | paddingTop = parseFloat(paddingTop); 424 | paddingBottom = parseFloat(paddingBottom); 425 | 426 | return Math.round(outerHeight - borderTopWidth - borderBottomWidth - paddingTop - paddingBottom); 427 | } 428 | 429 | arrayOfInt(from, length) { 430 | // adapted from https://stackoverflow.com/a/50234108/6500804 431 | return Array.from(Array(length).keys(), n => n + from); 432 | } 433 | 434 | incrementLinesCounter(value) { 435 | this.linesCounter = this.linesCounter + value; 436 | } 437 | 438 | resetLinesCounter() { 439 | this.linesCounter = 0; 440 | } 441 | 442 | isDisplayMath(element) { 443 | const nodes = element.childNodes; 444 | if (nodes.length != 1) return false; 445 | return (nodes[0].nodeName === 'SPAN') && (nodes[0].classList.value === 'math display'); 446 | } 447 | 448 | afterRendered(pages) { 449 | if (!this.options) return; 450 | 451 | for (let page of pages) { 452 | const lineNumbersContainer = this.appendLineNumbersContainer(page); 453 | const pageAreaY = page.area.getBoundingClientRect().y; 454 | let elementsToNumber = page.area.querySelectorAll(this.selector); 455 | 456 | for (let element of elementsToNumber) { 457 | // Do not add line numbers for display math environment 458 | if (this.isDisplayMath(element)) continue; 459 | 460 | // Try to retrieve line height 461 | const lineHeight = this.lineHeight(element); 462 | // Test against lineHeight method returns NaN 463 | if (!lineHeight) { 464 | console.warn('Failed to compute line height value on "' + page.id + '".'); 465 | continue; 466 | } 467 | 468 | const innerHeight = this.innerHeight(element); 469 | 470 | // Number of lines estimation 471 | // There is no built-in method to detect the number of lines in a block. 472 | // The main caveat is that an actual line height can differ from 473 | // the line-height CSS property. 474 | // Mixed fonts, subscripts, superscripts, inline math... can increase 475 | // the actual line height. 476 | // Here, we divide the inner height of the block by the line-height 477 | // computed property and round to the floor to take into account that 478 | // sometimes the actual line height is greater than its property value. 479 | // This is far from perfect and can be easily broken especially by 480 | // inline math. 481 | const nLines = Math.floor(innerHeight / lineHeight); 482 | 483 | // do not add line numbers for void paragraphs 484 | if (nLines <= 0) continue; 485 | 486 | const linenumbers = document.createElement('div'); 487 | lineNumbersContainer.appendChild(linenumbers); 488 | linenumbers.classList.add('maintextlinenumbers'); 489 | 490 | const elementY = element.getBoundingClientRect().y; 491 | linenumbers.style.top = (elementY - pageAreaY) + 'px'; 492 | 493 | const cs = getComputedStyle(element); 494 | const paddingTop = parseFloat(cs.paddingTop) + parseFloat(cs.borderTopWidth); 495 | linenumbers.style.paddingTop = paddingTop + 'px'; 496 | 497 | linenumbers.style.lineHeight = cs.lineHeight; 498 | 499 | linenumbers.innerHTML = this.arrayOfInt(this.linesCounter + 1, nLines) 500 | .reduce((t, v) => t + '
' + v); 501 | this.incrementLinesCounter(nLines); 502 | } 503 | 504 | if (this.options['reset-page']) { 505 | this.resetLinesCounter(); 506 | } 507 | } 508 | } 509 | }); 510 | } 511 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/paged-0.18/js/hooks.js: -------------------------------------------------------------------------------- 1 | // Hooks for paged.js 2 | { 3 | // Utils 4 | let pandocMeta, pandocMetaToString; 5 | { 6 | let el = document.getElementById('pandoc-meta'); 7 | pandocMeta = el ? JSON.parse(el.firstChild.data) : {}; 8 | } 9 | 10 | pandocMetaToString = meta => { 11 | let el = document.createElement('div'); 12 | el.innerHTML = meta; 13 | return el.innerText; 14 | }; 15 | 16 | let isString = value => { 17 | return typeof value === 'string' || value instanceof String; 18 | }; 19 | 20 | let isArray = value => { 21 | return value && typeof value === 'object' && value.constructor === Array; 22 | }; 23 | 24 | // This hook is an attempt to fix https://github.com/rstudio/pagedown/issues/131 25 | // Sometimes, the {break-after: avoid;} declaration applied on headers 26 | // lead to duplicated headers. I hate this bug. 27 | // This is linked to the way the HTML source is written 28 | // When we have the \n character like this:
\n

...

29 | // the header may be duplicated. 30 | // But, if we have

...

without any \n, the problem disappear 31 | // I think this handler can fix most of cases 32 | // Obviously, we cannot suppress all the \n in the HTML document 33 | // because carriage returns are important in
 elements.
 34 |   // Tested with Chrome 76.0.3809.100/Windows
 35 |   Paged.registerHandlers(class extends Paged.Handler {
 36 |     constructor(chunker, polisher, caller) {
 37 |       super(chunker, polisher, caller);
 38 |       this.carriageReturn = String.fromCharCode(10);
 39 |     }
 40 | 
 41 |     checkNode(node) {
 42 |       if (!node) return;
 43 |       if (node.nodeType !== 3) return;
 44 |       if (node.textContent === this.carriageReturn) {
 45 |         node.remove();
 46 |       }
 47 |     }
 48 | 
 49 |     afterParsed(parsed) {
 50 |       let template = document.querySelector('template').content;
 51 |       const breakAfterAvoidElements = template.querySelectorAll('[data-break-after="avoid"], [data-break-before="avoid"]');
 52 |       for (let el of breakAfterAvoidElements) {
 53 |         this.checkNode(el.previousSibling);
 54 |         this.checkNode(el.nextSibling);
 55 |       }
 56 |     }
 57 |   });
 58 | 
 59 |   // This hook creates a list of abbreviations
 60 |   // Note: we also could implement this feature using a Pandoc filter
 61 |   Paged.registerHandlers(class extends Paged.Handler {
 62 |     constructor(chunker, polisher, caller) {
 63 |       super(chunker, polisher, caller);
 64 |     }
 65 |     beforeParsed(content) {
 66 |       // Find the abbreviation nodes
 67 |       const abbrNodeList = content.querySelectorAll('abbr');
 68 | 
 69 |       // Return early if there is no abbreviation
 70 |       if (abbrNodeList.length === 0) return;
 71 | 
 72 |       // Store unique values of abbreviations, see https://github.com/rstudio/pagedown/issues/218
 73 |       let abbreviations = [];
 74 |       for (const {title, innerHTML} of abbrNodeList.values()) {
 75 |         if (abbreviations.find(el => el.title === title && el.innerHTML === innerHTML)) {
 76 |           continue;
 77 |         }
 78 |         abbreviations.push({title: title, innerHTML: innerHTML});
 79 |       }
 80 | 
 81 |       const loaTitle = pandocMeta['loa-title'] ? pandocMetaToString(pandocMeta['loa-title']) : 'List of Abbreviations';
 82 |       const loaId = 'LOA';
 83 |       const tocList = content.querySelector('.toc ul');
 84 |       let listOfAbbreviations = document.createElement('div');
 85 |       let descriptionList = document.createElement('dl');
 86 |       content.appendChild(listOfAbbreviations);
 87 |       listOfAbbreviations.id = loaId;
 88 |       listOfAbbreviations.classList.add('section', 'front-matter', 'level1', 'loa');
 89 |       listOfAbbreviations.innerHTML = '

' + loaTitle + '

'; 90 | listOfAbbreviations.appendChild(descriptionList); 91 | for(let abbr of abbreviations) { 92 | if(!abbr.title) continue; 93 | let term = document.createElement('dt'); 94 | let definition = document.createElement('dd'); 95 | descriptionList.appendChild(term); 96 | descriptionList.appendChild(definition); 97 | term.innerHTML = abbr.innerHTML; 98 | definition.innerText = abbr.title; 99 | } 100 | if (tocList) { 101 | const loaTOCItem = document.createElement('li'); 102 | loaTOCItem.innerHTML = '' + loaTitle + ''; 103 | tocList.appendChild(loaTOCItem); 104 | } 105 | } 106 | }); 107 | 108 | // This hook moves the sections of class front-matter in the div.front-matter-container 109 | Paged.registerHandlers(class extends Paged.Handler { 110 | constructor(chunker, polisher, caller) { 111 | super(chunker, polisher, caller); 112 | } 113 | 114 | beforeParsed(content) { 115 | const frontMatter = content.querySelector('.front-matter-container'); 116 | if (!frontMatter) return; 117 | 118 | // move front matter sections in the front matter container 119 | const frontMatterSections = content.querySelectorAll('.level1.front-matter'); 120 | for (const section of frontMatterSections) { 121 | frontMatter.appendChild(section); 122 | } 123 | 124 | // add the class front-matter-ref to any element 125 | // referring to an entry in the front matter 126 | const anchors = content.querySelectorAll('a[href^="#"]:not([href*=":"])'); 127 | for (const a of anchors) { 128 | const ref = a.getAttribute('href').replace(/^#/, ''); 129 | const element = content.getElementById(ref); 130 | if (frontMatter.contains(element)) a.classList.add('front-matter-ref'); 131 | } 132 | 133 | // update the toc, lof and lot for front matter sections 134 | const frontMatterSectionsLinks = content.querySelectorAll('.toc .front-matter-ref, .lof .front-matter-ref, .lot .front-matter-ref'); 135 | for (let i = frontMatterSectionsLinks.length - 1; i >= 0; i--) { 136 | const listItem = frontMatterSectionsLinks[i].parentNode; 137 | const list = listItem.parentNode; 138 | list.insertBefore(listItem, list.firstChild); 139 | } 140 | } 141 | }); 142 | 143 | // This hook expands the links in the lists of figures and tables 144 | Paged.registerHandlers(class extends Paged.Handler { 145 | constructor(chunker, polisher, caller) { 146 | super(chunker, polisher, caller); 147 | } 148 | 149 | beforeParsed(content) { 150 | const items = content.querySelectorAll('.lof li, .lot li'); 151 | for (const item of items) { 152 | const anchor = item.firstChild; 153 | anchor.innerText = item.innerText; 154 | item.innerText = ''; 155 | item.append(anchor); 156 | } 157 | } 158 | }); 159 | 160 | // This hook adds spans for leading symbols 161 | Paged.registerHandlers(class extends Paged.Handler { 162 | constructor(chunker, polisher, caller) { 163 | super(chunker, polisher, caller); 164 | } 165 | 166 | beforeParsed(content) { 167 | const anchors = content.querySelectorAll('.toc a, .lof a, .lot a'); 168 | for (const a of anchors) { 169 | a.innerHTML = a.innerHTML + ''; 170 | } 171 | } 172 | }); 173 | 174 | // This hook appends short titles spans 175 | Paged.registerHandlers(class extends Paged.Handler { 176 | constructor(chunker, polisher, caller) { 177 | super(chunker, polisher, caller); 178 | } 179 | 180 | beforeParsed(content) { 181 | /* A factory returning a function that appends short titles spans. 182 | The text content of these spans are reused for running titles (see default.css). 183 | Argument: level - An integer between 1 and 6. 184 | */ 185 | function appendShortTitleSpans(level) { 186 | return () => { 187 | const divs = Array.from(content.querySelectorAll('.level' + level)); 188 | 189 | function addSpan(div) { 190 | const mainHeader = div.getElementsByTagName('h' + level)[0]; 191 | if (!mainHeader) return; 192 | const mainTitle = mainHeader.textContent; 193 | const spanSectionNumber = mainHeader.getElementsByClassName('header-section-number')[0]; 194 | const mainNumber = !!spanSectionNumber ? spanSectionNumber.textContent : ''; 195 | const runningTitle = 'shortTitle' in div.dataset ? mainNumber + ' ' + div.dataset.shortTitle : mainTitle; 196 | const span = document.createElement('span'); 197 | span.className = 'shorttitle' + level; 198 | span.innerText = runningTitle; 199 | span.style.display = "none"; 200 | mainHeader.appendChild(span); 201 | if (level == 1 && div.querySelector('.level2') === null) { 202 | let span2 = document.createElement('span'); 203 | span2.className = 'shorttitle2'; 204 | span2.innerText = ' '; 205 | span2.style.display = "none"; 206 | span.insertAdjacentElement('afterend', span2); 207 | } 208 | } 209 | 210 | for (const div of divs) { 211 | addSpan(div); 212 | } 213 | }; 214 | } 215 | 216 | appendShortTitleSpans(1)(); 217 | appendShortTitleSpans(2)(); 218 | } 219 | }); 220 | 221 | // Footnotes support 222 | Paged.registerHandlers(class extends Paged.Handler { 223 | constructor(chunker, polisher, caller) { 224 | super(chunker, polisher, caller); 225 | 226 | this.splittedParagraphRefs = []; 227 | } 228 | 229 | beforeParsed(content) { 230 | // remove footnotes in toc, lof, lot 231 | // see https://github.com/rstudio/pagedown/issues/54 232 | let removeThese = content.querySelectorAll('.toc .footnote, .lof .footnote, .lot .footnote'); 233 | for (const el of removeThese) { 234 | el.remove(); 235 | } 236 | 237 | let footnotes = content.querySelectorAll('.footnote'); 238 | 239 | for (let footnote of footnotes) { 240 | let parentElement = footnote.parentElement; 241 | let footnoteCall = document.createElement('a'); 242 | let footnoteNumber = footnote.dataset.pagedownFootnoteNumber; 243 | 244 | footnoteCall.className = 'footnote-ref'; // same class as Pandoc 245 | footnoteCall.setAttribute('id', 'fnref' + footnoteNumber); // same notation as Pandoc 246 | footnoteCall.setAttribute('href', '#' + footnote.id); 247 | footnoteCall.innerHTML = '' + footnoteNumber +''; 248 | parentElement.insertBefore(footnoteCall, footnote); 249 | 250 | // Here comes a hack. Fortunately, it works with Chrome and FF. 251 | let handler = document.createElement('p'); 252 | handler.className = 'footnoteHandler'; 253 | parentElement.insertBefore(handler, footnote); 254 | handler.appendChild(footnote); 255 | handler.style.display = 'inline-block'; 256 | handler.style.width = '100%'; 257 | handler.style.float = 'right'; 258 | handler.style.pageBreakInside = 'avoid'; 259 | } 260 | } 261 | 262 | afterPageLayout(pageFragment, page, breakToken) { 263 | function hasItemParent(node) { 264 | if (node.parentElement === null) { 265 | return false; 266 | } else { 267 | if (node.parentElement.tagName === 'LI') { 268 | return true; 269 | } else { 270 | return hasItemParent(node.parentElement); 271 | } 272 | } 273 | } 274 | // If a li item is broken, we store the reference of the p child element 275 | // see https://github.com/rstudio/pagedown/issues/23#issue-376548000 276 | if (breakToken !== undefined) { 277 | if (breakToken.node.nodeName === "#text" && hasItemParent(breakToken.node)) { 278 | this.splittedParagraphRefs.push(breakToken.node.parentElement.dataset.ref); 279 | } 280 | } 281 | } 282 | 283 | afterRendered(pages) { 284 | for (let page of pages) { 285 | const footnotes = page.element.querySelectorAll('.footnote'); 286 | if (footnotes.length === 0) { 287 | continue; 288 | } 289 | 290 | const pageContent = page.element.querySelector('.pagedjs_page_content'); 291 | let hr = document.createElement('hr'); 292 | let footnoteArea = document.createElement('div'); 293 | 294 | pageContent.style.display = 'flex'; 295 | pageContent.style.flexDirection = 'column'; 296 | 297 | hr.className = 'footnote-break'; 298 | hr.style.marginTop = 'auto'; 299 | hr.style.marginBottom = 0; 300 | hr.style.marginLeft = 0; 301 | hr.style.marginRight = 'auto'; 302 | pageContent.appendChild(hr); 303 | 304 | footnoteArea.className = 'footnote-area'; 305 | pageContent.appendChild(footnoteArea); 306 | 307 | for (let footnote of footnotes) { 308 | let handler = footnote.parentElement; 309 | 310 | footnoteArea.appendChild(footnote); 311 | handler.parentNode.removeChild(handler); 312 | 313 | footnote.innerHTML = '' + footnote.dataset.pagedownFootnoteNumber + '' + footnote.innerHTML; 314 | footnote.style.fontSize = 'x-small'; 315 | footnote.style.marginTop = 0; 316 | footnote.style.marginBottom = 0; 317 | footnote.style.paddingTop = 0; 318 | footnote.style.paddingBottom = 0; 319 | footnote.style.display = 'block'; 320 | } 321 | } 322 | 323 | for (let ref of this.splittedParagraphRefs) { 324 | let paragraphFirstPage = document.querySelector('[data-split-to="' + ref + '"]'); 325 | // We test whether the paragraph is empty 326 | // see https://github.com/rstudio/pagedown/issues/23#issue-376548000 327 | if (paragraphFirstPage.innerText === "") { 328 | paragraphFirstPage.parentElement.style.display = "none"; 329 | let paragraphSecondPage = document.querySelector('[data-split-from="' + ref + '"]'); 330 | paragraphSecondPage.parentElement.style.setProperty('list-style', 'inherit', 'important'); 331 | } 332 | } 333 | } 334 | }); 335 | 336 | // Support for "Chapter " label on section with class `.chapter` 337 | Paged.registerHandlers(class extends Paged.Handler { 338 | constructor(chunker, polisher, caller) { 339 | super(chunker, polisher, caller); 340 | 341 | this.options = pandocMeta['chapter_name']; 342 | 343 | let styles; 344 | if (isString(this.options)) { 345 | this.options = pandocMetaToString(this.options); 346 | styles = ` 347 | :root { 348 | --chapter-name-before: "${this.options}"; 349 | } 350 | `; 351 | } 352 | if (isArray(this.options)) { 353 | this.options = this.options.map(pandocMetaToString); 354 | styles = ` 355 | :root { 356 | --chapter-name-before: "${this.options[0]}"; 357 | --chapter-name-after: "${this.options[1]}"; 358 | } 359 | `; 360 | } 361 | if (styles) polisher.insert(styles); 362 | } 363 | 364 | beforeParsed(content) { 365 | const tocAnchors = content.querySelectorAll('.toc a[href^="#"]:not([href*=":"]'); 366 | for (const anchor of tocAnchors) { 367 | const ref = anchor.getAttribute('href').replace(/^#/, ''); 368 | const element = content.getElementById(ref); 369 | if (element.classList.contains('chapter')) { 370 | anchor.classList.add('chapter-ref'); 371 | } 372 | } 373 | } 374 | }); 375 | 376 | // Main text line numbering, 377 | // see https://github.com/rstudio/pagedown/issues/115 378 | // Original idea: Julien Taquet, thanks! 379 | Paged.registerHandlers(class extends Paged.Handler { 380 | constructor(chunker, polisher, caller) { 381 | super(chunker, polisher, caller); 382 | 383 | // get the number-lines option from Pandoc metavariables 384 | this.options = pandocMeta['number-lines']; 385 | // quit early if the "number-lines" option is false or missing 386 | if (!this.options) return; 387 | // retrieve the selector if provided, otherwise use the default selector 388 | this.selector = this.options.selector ? pandocMetaToString(this.options.selector) : '.level1:not(.front-matter) h1, .level1 h2, .level1 h3, .level1 p'; 389 | 390 | const styles = ` 391 | :root { 392 | --line-numbers-padding-right: 10px; 393 | --line-numbers-font-size: 8pt; 394 | } 395 | .pagedown-linenumbers-container { 396 | position: absolute; 397 | margin-top: var(--pagedjs-margin-top); 398 | right: calc(var(--pagedjs-width) - var(--pagedjs-margin-left)); 399 | } 400 | .maintextlinenumbers { 401 | position: absolute; 402 | right: 0; 403 | text-align: right; 404 | padding-right: var(--line-numbers-padding-right); 405 | font-size: var(--line-numbers-font-size); 406 | } 407 | `; 408 | polisher.insert(styles); 409 | 410 | this.resetLinesCounter(); 411 | } 412 | 413 | appendLineNumbersContainer(page) { 414 | const pagebox = page.element.querySelector('.pagedjs_pagebox'); 415 | const lineNumbersContainer = document.createElement('div'); 416 | lineNumbersContainer.classList.add('pagedown-linenumbers-container'); 417 | 418 | return pagebox.appendChild(lineNumbersContainer); 419 | } 420 | 421 | lineHeight(element) { 422 | // If the document stylesheet does not define a value for line-height, 423 | // Blink returns "normal". Therefore, parseInt may return NaN. 424 | return parseInt(getComputedStyle(element).lineHeight); 425 | } 426 | 427 | innerHeight(element) { 428 | let outerHeight = element.getBoundingClientRect().height; 429 | let {borderTopWidth, 430 | borderBottomWidth, 431 | paddingTop, 432 | paddingBottom} = getComputedStyle(element); 433 | 434 | borderTopWidth = parseFloat(borderTopWidth); 435 | borderBottomWidth = parseFloat(borderBottomWidth); 436 | paddingTop = parseFloat(paddingTop); 437 | paddingBottom = parseFloat(paddingBottom); 438 | 439 | return Math.round(outerHeight - borderTopWidth - borderBottomWidth - paddingTop - paddingBottom); 440 | } 441 | 442 | arrayOfInt(from, length) { 443 | // adapted from https://stackoverflow.com/a/50234108/6500804 444 | return Array.from(Array(length).keys(), n => n + from); 445 | } 446 | 447 | incrementLinesCounter(value) { 448 | this.linesCounter = this.linesCounter + value; 449 | } 450 | 451 | resetLinesCounter() { 452 | this.linesCounter = 0; 453 | } 454 | 455 | isDisplayMath(element) { 456 | const nodes = element.childNodes; 457 | if (nodes.length != 1) return false; 458 | return (nodes[0].nodeName === 'SPAN') && (nodes[0].classList.value === 'math display'); 459 | } 460 | 461 | afterRendered(pages) { 462 | if (!this.options) return; 463 | 464 | for (let page of pages) { 465 | const lineNumbersContainer = this.appendLineNumbersContainer(page); 466 | const pageAreaY = page.area.getBoundingClientRect().y; 467 | let elementsToNumber = page.area.querySelectorAll(this.selector); 468 | 469 | for (let element of elementsToNumber) { 470 | // Do not add line numbers for display math environment 471 | if (this.isDisplayMath(element)) continue; 472 | 473 | // Try to retrieve line height 474 | const lineHeight = this.lineHeight(element); 475 | // Test against lineHeight method returns NaN 476 | if (!lineHeight) { 477 | console.warn('Failed to compute line height value on "' + page.id + '".'); 478 | continue; 479 | } 480 | 481 | const innerHeight = this.innerHeight(element); 482 | 483 | // Number of lines estimation 484 | // There is no built-in method to detect the number of lines in a block. 485 | // The main caveat is that an actual line height can differ from 486 | // the line-height CSS property. 487 | // Mixed fonts, subscripts, superscripts, inline math... can increase 488 | // the actual line height. 489 | // Here, we divide the inner height of the block by the line-height 490 | // computed property and round to the floor to take into account that 491 | // sometimes the actual line height is greater than its property value. 492 | // This is far from perfect and can be easily broken especially by 493 | // inline math. 494 | const nLines = Math.floor(innerHeight / lineHeight); 495 | 496 | // do not add line numbers for void paragraphs 497 | if (nLines <= 0) continue; 498 | 499 | const linenumbers = document.createElement('div'); 500 | lineNumbersContainer.appendChild(linenumbers); 501 | linenumbers.classList.add('maintextlinenumbers'); 502 | 503 | const elementY = element.getBoundingClientRect().y; 504 | linenumbers.style.top = (elementY - pageAreaY) + 'px'; 505 | 506 | const cs = getComputedStyle(element); 507 | const paddingTop = parseFloat(cs.paddingTop) + parseFloat(cs.borderTopWidth); 508 | linenumbers.style.paddingTop = paddingTop + 'px'; 509 | 510 | linenumbers.style.lineHeight = cs.lineHeight; 511 | 512 | linenumbers.innerHTML = this.arrayOfInt(this.linesCounter + 1, nLines) 513 | .reduce((t, v) => t + '
' + v); 514 | this.incrementLinesCounter(nLines); 515 | } 516 | 517 | if (this.options['reset-page']) { 518 | this.resetLinesCounter(); 519 | } 520 | } 521 | } 522 | }); 523 | 524 | // Clean links to avoid impossible line breaking of long urls in a justified text 525 | // Author: Julien Taquet (Paged.js core team) 526 | // see https://github.com/spyrales/gouvdown/issues/37 527 | Paged.registerHandlers(class extends Paged.Handler { 528 | constructor(chunker, polisher, caller) { 529 | super(chunker, polisher, caller); 530 | } 531 | beforeParsed(content) { 532 | // add wbr to / in links 533 | const links = content.querySelectorAll('a[href^="http"], a[href^="www"]'); 534 | links.forEach(link => { 535 | // Rerun to avoid large spaces. 536 | // Break after a colon or a double slash (//) 537 | // or before a single slash (/), a tilde (~), a period, a comma, a hyphen, 538 | // an underline (_), a question mark, a number sign, or a percent symbol. 539 | const content = link.textContent; 540 | if (!(link.childElementCount === 0 && content.match(/^http|^www/))) return; 541 | let printableUrl = content.replace(/\/\//g, "//\u003Cwbr\u003E"); 542 | printableUrl = printableUrl.replace(/\,/g, ",\u003Cwbr\u003E"); 543 | // put wbr around everything. 544 | printableUrl = printableUrl.replace( 545 | /(\/|\~|\-|\.|\,|\_|\?|\#|\%)/g, 546 | "\u003Cwbr\u003E$1" 547 | ); 548 | // turn hyphen in non breaking hyphen 549 | printableUrl = printableUrl.replace(/\-/g, "\u003Cwbr\u003E‑"); 550 | link.setAttribute("data-print-url", printableUrl); 551 | link.innerHTML = printableUrl; 552 | }); 553 | } 554 | }); 555 | 556 | // Repeat table headers on multiple pages 557 | // Authors: Julien Taquet, Lucas Maciuga and Tafael Caixeta, see https://gitlab.coko.foundation/pagedjs/pagedjs/-/issues/84 558 | // TODO: remove this hook when Paged.js integrates this feature 559 | Paged.registerHandlers(class RepeatingTableHeadersHandler extends Paged.Handler { 560 | 561 | constructor(chunker, polisher, caller) { 562 | super(chunker, polisher, caller); 563 | this.splitTablesRefs = []; 564 | } 565 | 566 | afterPageLayout(pageElement, page, breakToken, chunker) { 567 | this.chunker = chunker; 568 | this.splitTablesRefs = []; 569 | 570 | if (breakToken) { 571 | const node = breakToken.node; 572 | const tables = this.findAllAncestors(node, "table"); 573 | if (node.tagName === "TABLE") { 574 | tables.push(node); 575 | } 576 | 577 | if (tables.length > 0) { 578 | this.splitTablesRefs = tables.map(t => t.dataset.ref); 579 | 580 | //checks if split inside thead and if so, set breakToken to next sibling element 581 | let thead = node.tagName === "THEAD" ? node : this.findFirstAncestor(node, "thead"); 582 | if (thead) { 583 | let lastTheadNode = thead.hasChildNodes() ? thead.lastChild : thead; 584 | breakToken.node = this.nodeAfter(lastTheadNode, chunker.source); 585 | } 586 | 587 | this.hideEmptyTables(pageElement, node); 588 | } 589 | } 590 | } 591 | 592 | hideEmptyTables(pageElement, breakTokenNode) { 593 | this.splitTablesRefs.forEach(ref => { 594 | let table = pageElement.querySelector("[data-ref='" + ref + "']"); 595 | if (table) { 596 | let sourceBody = table.querySelector("tbody > tr"); 597 | if (!sourceBody || this.refEquals(sourceBody.firstElementChild, breakTokenNode)) { 598 | table.style.visibility = "hidden"; 599 | table.style.position = "absolute"; 600 | let lineSpacer = table.nextSibling; 601 | if (lineSpacer) { 602 | lineSpacer.style.visibility = "hidden"; 603 | lineSpacer.style.position = "absolute"; 604 | } 605 | } 606 | } 607 | }); 608 | } 609 | 610 | refEquals(a, b) { 611 | return a && a.dataset && b && b.dataset && a.dataset.ref === b.dataset.ref; 612 | } 613 | 614 | findFirstAncestor(element, selector) { 615 | while (element.parentNode && element.parentNode.nodeType === 1) { 616 | if (element.parentNode.matches(selector)) { 617 | return element.parentNode; 618 | } 619 | element = element.parentNode; 620 | } 621 | return null; 622 | } 623 | 624 | findAllAncestors(element, selector) { 625 | const ancestors = []; 626 | while (element.parentNode && element.parentNode.nodeType === 1) { 627 | if (element.parentNode.matches(selector)) { 628 | ancestors.unshift(element.parentNode); 629 | } 630 | element = element.parentNode; 631 | } 632 | return ancestors; 633 | } 634 | 635 | // The addition of repeating Table Headers is done here because this hook is triggered before overflow handling 636 | layout(rendered, layout) { 637 | this.splitTablesRefs.forEach(ref => { 638 | const renderedTable = rendered.querySelector("[data-ref='" + ref + "']"); 639 | if (renderedTable && renderedTable.hasAttribute("data-split-from")) { 640 | // this event can be triggered multiple times 641 | // added a flag repeated-headers to control when table headers already repeated in current page. 642 | if (!renderedTable.getAttribute("repeated-headers")) { 643 | const sourceTable = this.chunker.source.querySelector("[data-ref='" + ref + "']"); 644 | this.repeatColgroup(sourceTable, renderedTable); 645 | this.repeatTHead(sourceTable, renderedTable); 646 | renderedTable.setAttribute("repeated-headers", true); 647 | } 648 | } 649 | }); 650 | } 651 | 652 | repeatColgroup(sourceTable, renderedTable) { 653 | let colgroup = sourceTable.querySelectorAll("colgroup"); 654 | let firstChild = renderedTable.firstChild; 655 | colgroup.forEach((colgroup) => { 656 | let clonedColgroup = colgroup.cloneNode(true); 657 | renderedTable.insertBefore(clonedColgroup, firstChild); 658 | }); 659 | } 660 | 661 | repeatTHead(sourceTable, renderedTable) { 662 | let thead = sourceTable.querySelector("thead"); 663 | if (thead) { 664 | let clonedThead = thead.cloneNode(true); 665 | renderedTable.insertBefore(clonedThead, renderedTable.firstChild); 666 | } 667 | } 668 | 669 | // the functions below are from pagedjs utils/dom.js 670 | nodeAfter(node, limiter) { 671 | if (limiter && node === limiter) { 672 | return; 673 | } 674 | let significantNode = this.nextSignificantNode(node); 675 | if (significantNode) { 676 | return significantNode; 677 | } 678 | if (node.parentNode) { 679 | while ((node = node.parentNode)) { 680 | if (limiter && node === limiter) { 681 | return; 682 | } 683 | significantNode = this.nextSignificantNode(node); 684 | if (significantNode) { 685 | return significantNode; 686 | } 687 | } 688 | } 689 | } 690 | 691 | nextSignificantNode(sib) { 692 | while ((sib = sib.nextSibling)) { 693 | if (!this.isIgnorable(sib)) return sib; 694 | } 695 | return null; 696 | } 697 | 698 | isIgnorable(node) { 699 | return (node.nodeType === 8) || // A comment node 700 | ((node.nodeType === 3) && this.isAllWhitespace(node)); // a text node, all whitespace 701 | } 702 | 703 | isAllWhitespace(node) { 704 | return !(/[^\t\n\r ]/.test(node.textContent)); 705 | } 706 | }); 707 | 708 | } 709 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/paged-0.18.1/js/hooks.js: -------------------------------------------------------------------------------- 1 | // Hooks for paged.js 2 | { 3 | // Utils 4 | let pandocMeta, pandocMetaToString; 5 | { 6 | let el = document.getElementById('pandoc-meta'); 7 | pandocMeta = el ? JSON.parse(el.firstChild.data) : {}; 8 | } 9 | 10 | pandocMetaToString = meta => { 11 | let el = document.createElement('div'); 12 | el.innerHTML = meta; 13 | return el.innerText; 14 | }; 15 | 16 | let isString = value => { 17 | return typeof value === 'string' || value instanceof String; 18 | }; 19 | 20 | let isArray = value => { 21 | return value && typeof value === 'object' && value.constructor === Array; 22 | }; 23 | 24 | // This hook is an attempt to fix https://github.com/rstudio/pagedown/issues/131 25 | // Sometimes, the {break-after: avoid;} declaration applied on headers 26 | // lead to duplicated headers. I hate this bug. 27 | // This is linked to the way the HTML source is written 28 | // When we have the \n character like this:
\n

...

29 | // the header may be duplicated. 30 | // But, if we have

...

without any \n, the problem disappear 31 | // I think this handler can fix most of cases 32 | // Obviously, we cannot suppress all the \n in the HTML document 33 | // because carriage returns are important in
 elements.
 34 |   // Tested with Chrome 76.0.3809.100/Windows
 35 |   Paged.registerHandlers(class extends Paged.Handler {
 36 |     constructor(chunker, polisher, caller) {
 37 |       super(chunker, polisher, caller);
 38 |       this.carriageReturn = String.fromCharCode(10);
 39 |     }
 40 | 
 41 |     checkNode(node) {
 42 |       if (!node) return;
 43 |       if (node.nodeType !== 3) return;
 44 |       if (node.textContent === this.carriageReturn) {
 45 |         node.remove();
 46 |       }
 47 |     }
 48 | 
 49 |     afterParsed(parsed) {
 50 |       let template = document.querySelector('template').content;
 51 |       const breakAfterAvoidElements = template.querySelectorAll('[data-break-after="avoid"], [data-break-before="avoid"]');
 52 |       for (let el of breakAfterAvoidElements) {
 53 |         this.checkNode(el.previousSibling);
 54 |         this.checkNode(el.nextSibling);
 55 |       }
 56 |     }
 57 |   });
 58 | 
 59 |   // This hook creates a list of abbreviations
 60 |   // Note: we also could implement this feature using a Pandoc filter
 61 |   Paged.registerHandlers(class extends Paged.Handler {
 62 |     constructor(chunker, polisher, caller) {
 63 |       super(chunker, polisher, caller);
 64 |     }
 65 |     beforeParsed(content) {
 66 |       // Find the abbreviation nodes
 67 |       const abbrNodeList = content.querySelectorAll('abbr');
 68 | 
 69 |       // Return early if there is no abbreviation
 70 |       if (abbrNodeList.length === 0) return;
 71 | 
 72 |       // Store unique values of abbreviations, see https://github.com/rstudio/pagedown/issues/218
 73 |       let abbreviations = [];
 74 |       for (const {title, innerHTML} of abbrNodeList.values()) {
 75 |         if (abbreviations.find(el => el.title === title && el.innerHTML === innerHTML)) {
 76 |           continue;
 77 |         }
 78 |         abbreviations.push({title: title, innerHTML: innerHTML});
 79 |       }
 80 | 
 81 |       const loaTitle = pandocMeta['loa-title'] ? pandocMetaToString(pandocMeta['loa-title']) : 'List of Abbreviations';
 82 |       const loaId = 'LOA';
 83 |       const tocList = content.querySelector('.toc ul');
 84 |       let listOfAbbreviations = document.createElement('div');
 85 |       let descriptionList = document.createElement('dl');
 86 |       content.appendChild(listOfAbbreviations);
 87 |       listOfAbbreviations.id = loaId;
 88 |       listOfAbbreviations.classList.add('section', 'front-matter', 'level1', 'loa');
 89 |       listOfAbbreviations.innerHTML = '

' + loaTitle + '

'; 90 | listOfAbbreviations.appendChild(descriptionList); 91 | for(let abbr of abbreviations) { 92 | if(!abbr.title) continue; 93 | let term = document.createElement('dt'); 94 | let definition = document.createElement('dd'); 95 | descriptionList.appendChild(term); 96 | descriptionList.appendChild(definition); 97 | term.innerHTML = abbr.innerHTML; 98 | definition.innerText = abbr.title; 99 | } 100 | if (tocList) { 101 | const loaTOCItem = document.createElement('li'); 102 | loaTOCItem.innerHTML = '' + loaTitle + ''; 103 | tocList.appendChild(loaTOCItem); 104 | } 105 | } 106 | }); 107 | 108 | // This hook moves the sections of class front-matter in the div.front-matter-container 109 | Paged.registerHandlers(class extends Paged.Handler { 110 | constructor(chunker, polisher, caller) { 111 | super(chunker, polisher, caller); 112 | } 113 | 114 | beforeParsed(content) { 115 | const frontMatter = content.querySelector('.front-matter-container'); 116 | if (!frontMatter) return; 117 | 118 | // move front matter sections in the front matter container 119 | const frontMatterSections = content.querySelectorAll('.level1.front-matter'); 120 | for (const section of frontMatterSections) { 121 | frontMatter.appendChild(section); 122 | } 123 | 124 | // add the class front-matter-ref to any element 125 | // referring to an entry in the front matter 126 | const anchors = content.querySelectorAll('a[href^="#"]:not([href*=":"])'); 127 | for (const a of anchors) { 128 | const ref = a.getAttribute('href').replace(/^#/, ''); 129 | const element = content.getElementById(ref); 130 | if (frontMatter.contains(element)) a.classList.add('front-matter-ref'); 131 | } 132 | 133 | // update the toc, lof and lot for front matter sections 134 | const frontMatterSectionsLinks = content.querySelectorAll('.toc .front-matter-ref, .lof .front-matter-ref, .lot .front-matter-ref'); 135 | for (let i = frontMatterSectionsLinks.length - 1; i >= 0; i--) { 136 | const listItem = frontMatterSectionsLinks[i].parentNode; 137 | const list = listItem.parentNode; 138 | list.insertBefore(listItem, list.firstChild); 139 | } 140 | } 141 | }); 142 | 143 | // This hook expands the links in the lists of figures and tables 144 | Paged.registerHandlers(class extends Paged.Handler { 145 | constructor(chunker, polisher, caller) { 146 | super(chunker, polisher, caller); 147 | } 148 | 149 | beforeParsed(content) { 150 | const items = content.querySelectorAll('.lof li, .lot li'); 151 | for (const item of items) { 152 | const anchor = item.firstChild; 153 | anchor.innerText = item.innerText; 154 | item.innerText = ''; 155 | item.append(anchor); 156 | } 157 | } 158 | }); 159 | 160 | // This hook adds spans for leading symbols 161 | Paged.registerHandlers(class extends Paged.Handler { 162 | constructor(chunker, polisher, caller) { 163 | super(chunker, polisher, caller); 164 | } 165 | 166 | beforeParsed(content) { 167 | const anchors = content.querySelectorAll('.toc a, .lof a, .lot a'); 168 | for (const a of anchors) { 169 | a.innerHTML = a.innerHTML + ''; 170 | } 171 | } 172 | }); 173 | 174 | // This hook appends short titles spans 175 | Paged.registerHandlers(class extends Paged.Handler { 176 | constructor(chunker, polisher, caller) { 177 | super(chunker, polisher, caller); 178 | } 179 | 180 | beforeParsed(content) { 181 | /* A factory returning a function that appends short titles spans. 182 | The text content of these spans are reused for running titles (see default.css). 183 | Argument: level - An integer between 1 and 6. 184 | */ 185 | function appendShortTitleSpans(level) { 186 | return () => { 187 | const divs = Array.from(content.querySelectorAll('.level' + level)); 188 | 189 | function addSpan(div) { 190 | const mainHeader = div.getElementsByTagName('h' + level)[0]; 191 | if (!mainHeader) return; 192 | const mainTitle = mainHeader.textContent; 193 | const spanSectionNumber = mainHeader.getElementsByClassName('header-section-number')[0]; 194 | const mainNumber = !!spanSectionNumber ? spanSectionNumber.textContent : ''; 195 | const runningTitle = 'shortTitle' in div.dataset ? mainNumber + ' ' + div.dataset.shortTitle : mainTitle; 196 | const span = document.createElement('span'); 197 | span.className = 'shorttitle' + level; 198 | span.innerText = runningTitle; 199 | span.style.display = "none"; 200 | mainHeader.appendChild(span); 201 | if (level == 1 && div.querySelector('.level2') === null) { 202 | let span2 = document.createElement('span'); 203 | span2.className = 'shorttitle2'; 204 | span2.innerText = ' '; 205 | span2.style.display = "none"; 206 | span.insertAdjacentElement('afterend', span2); 207 | } 208 | } 209 | 210 | for (const div of divs) { 211 | addSpan(div); 212 | } 213 | }; 214 | } 215 | 216 | appendShortTitleSpans(1)(); 217 | appendShortTitleSpans(2)(); 218 | } 219 | }); 220 | 221 | // Footnotes support 222 | Paged.registerHandlers(class extends Paged.Handler { 223 | constructor(chunker, polisher, caller) { 224 | super(chunker, polisher, caller); 225 | 226 | this.splittedParagraphRefs = []; 227 | } 228 | 229 | beforeParsed(content) { 230 | // remove footnotes in toc, lof, lot 231 | // see https://github.com/rstudio/pagedown/issues/54 232 | let removeThese = content.querySelectorAll('.toc .footnote, .lof .footnote, .lot .footnote'); 233 | for (const el of removeThese) { 234 | el.remove(); 235 | } 236 | 237 | let footnotes = content.querySelectorAll('.footnote'); 238 | 239 | for (let footnote of footnotes) { 240 | let parentElement = footnote.parentElement; 241 | let footnoteCall = document.createElement('a'); 242 | let footnoteNumber = footnote.dataset.pagedownFootnoteNumber; 243 | 244 | footnoteCall.className = 'footnote-ref'; // same class as Pandoc 245 | footnoteCall.setAttribute('id', 'fnref' + footnoteNumber); // same notation as Pandoc 246 | footnoteCall.setAttribute('href', '#' + footnote.id); 247 | footnoteCall.innerHTML = '' + footnoteNumber +''; 248 | parentElement.insertBefore(footnoteCall, footnote); 249 | 250 | // Here comes a hack. Fortunately, it works with Chrome and FF. 251 | let handler = document.createElement('p'); 252 | handler.className = 'footnoteHandler'; 253 | parentElement.insertBefore(handler, footnote); 254 | handler.appendChild(footnote); 255 | handler.style.display = 'inline-block'; 256 | handler.style.width = '100%'; 257 | handler.style.float = 'right'; 258 | handler.style.pageBreakInside = 'avoid'; 259 | } 260 | } 261 | 262 | afterPageLayout(pageFragment, page, breakToken) { 263 | function hasItemParent(node) { 264 | if (node.parentElement === null) { 265 | return false; 266 | } else { 267 | if (node.parentElement.tagName === 'LI') { 268 | return true; 269 | } else { 270 | return hasItemParent(node.parentElement); 271 | } 272 | } 273 | } 274 | // If a li item is broken, we store the reference of the p child element 275 | // see https://github.com/rstudio/pagedown/issues/23#issue-376548000 276 | if (breakToken !== undefined) { 277 | if (breakToken.node.nodeName === "#text" && hasItemParent(breakToken.node)) { 278 | this.splittedParagraphRefs.push(breakToken.node.parentElement.dataset.ref); 279 | } 280 | } 281 | } 282 | 283 | afterRendered(pages) { 284 | for (let page of pages) { 285 | const footnotes = page.element.querySelectorAll('.footnote'); 286 | if (footnotes.length === 0) { 287 | continue; 288 | } 289 | 290 | const pageContent = page.element.querySelector('.pagedjs_page_content'); 291 | let hr = document.createElement('hr'); 292 | let footnoteArea = document.createElement('div'); 293 | 294 | pageContent.style.display = 'flex'; 295 | pageContent.style.flexDirection = 'column'; 296 | 297 | hr.className = 'footnote-break'; 298 | hr.style.marginTop = 'auto'; 299 | hr.style.marginBottom = 0; 300 | hr.style.marginLeft = 0; 301 | hr.style.marginRight = 'auto'; 302 | pageContent.appendChild(hr); 303 | 304 | footnoteArea.className = 'footnote-area'; 305 | pageContent.appendChild(footnoteArea); 306 | 307 | for (let footnote of footnotes) { 308 | let handler = footnote.parentElement; 309 | 310 | footnoteArea.appendChild(footnote); 311 | handler.parentNode.removeChild(handler); 312 | 313 | footnote.innerHTML = '' + footnote.dataset.pagedownFootnoteNumber + '' + footnote.innerHTML; 314 | footnote.style.fontSize = 'x-small'; 315 | footnote.style.marginTop = 0; 316 | footnote.style.marginBottom = 0; 317 | footnote.style.paddingTop = 0; 318 | footnote.style.paddingBottom = 0; 319 | footnote.style.display = 'block'; 320 | } 321 | } 322 | 323 | for (let ref of this.splittedParagraphRefs) { 324 | let paragraphFirstPage = document.querySelector('[data-split-to="' + ref + '"]'); 325 | // We test whether the paragraph is empty 326 | // see https://github.com/rstudio/pagedown/issues/23#issue-376548000 327 | if (paragraphFirstPage.innerText === "") { 328 | paragraphFirstPage.parentElement.style.display = "none"; 329 | let paragraphSecondPage = document.querySelector('[data-split-from="' + ref + '"]'); 330 | paragraphSecondPage.parentElement.style.setProperty('list-style', 'inherit', 'important'); 331 | } 332 | } 333 | } 334 | }); 335 | 336 | // Support for "Chapter " label on section with class `.chapter` 337 | Paged.registerHandlers(class extends Paged.Handler { 338 | constructor(chunker, polisher, caller) { 339 | super(chunker, polisher, caller); 340 | 341 | this.options = pandocMeta['chapter_name']; 342 | 343 | let styles; 344 | if (isString(this.options)) { 345 | this.options = pandocMetaToString(this.options); 346 | styles = ` 347 | :root { 348 | --chapter-name-before: "${this.options}"; 349 | } 350 | `; 351 | } 352 | if (isArray(this.options)) { 353 | this.options = this.options.map(pandocMetaToString); 354 | styles = ` 355 | :root { 356 | --chapter-name-before: "${this.options[0]}"; 357 | --chapter-name-after: "${this.options[1]}"; 358 | } 359 | `; 360 | } 361 | if (styles) polisher.insert(styles); 362 | } 363 | 364 | beforeParsed(content) { 365 | const tocAnchors = content.querySelectorAll('.toc a[href^="#"]:not([href*=":"]'); 366 | for (const anchor of tocAnchors) { 367 | const ref = anchor.getAttribute('href').replace(/^#/, ''); 368 | const element = content.getElementById(ref); 369 | if (element.classList.contains('chapter')) { 370 | anchor.classList.add('chapter-ref'); 371 | } 372 | } 373 | } 374 | }); 375 | 376 | // Main text line numbering, 377 | // see https://github.com/rstudio/pagedown/issues/115 378 | // Original idea: Julien Taquet, thanks! 379 | Paged.registerHandlers(class extends Paged.Handler { 380 | constructor(chunker, polisher, caller) { 381 | super(chunker, polisher, caller); 382 | 383 | // get the number-lines option from Pandoc metavariables 384 | this.options = pandocMeta['number-lines']; 385 | // quit early if the "number-lines" option is false or missing 386 | if (!this.options) return; 387 | // retrieve the selector if provided, otherwise use the default selector 388 | this.selector = this.options.selector ? pandocMetaToString(this.options.selector) : '.level1:not(.front-matter) h1, .level1 h2, .level1 h3, .level1 p'; 389 | 390 | const styles = ` 391 | :root { 392 | --line-numbers-padding-right: 10px; 393 | --line-numbers-font-size: 8pt; 394 | } 395 | .pagedown-linenumbers-container { 396 | position: absolute; 397 | margin-top: var(--pagedjs-margin-top); 398 | right: calc(var(--pagedjs-width) - var(--pagedjs-margin-left)); 399 | } 400 | .maintextlinenumbers { 401 | position: absolute; 402 | right: 0; 403 | text-align: right; 404 | padding-right: var(--line-numbers-padding-right); 405 | font-size: var(--line-numbers-font-size); 406 | } 407 | `; 408 | polisher.insert(styles); 409 | 410 | this.resetLinesCounter(); 411 | } 412 | 413 | appendLineNumbersContainer(page) { 414 | const pagebox = page.element.querySelector('.pagedjs_pagebox'); 415 | const lineNumbersContainer = document.createElement('div'); 416 | lineNumbersContainer.classList.add('pagedown-linenumbers-container'); 417 | 418 | return pagebox.appendChild(lineNumbersContainer); 419 | } 420 | 421 | lineHeight(element) { 422 | // If the document stylesheet does not define a value for line-height, 423 | // Blink returns "normal". Therefore, parseInt may return NaN. 424 | return parseInt(getComputedStyle(element).lineHeight); 425 | } 426 | 427 | innerHeight(element) { 428 | let outerHeight = element.getBoundingClientRect().height; 429 | let {borderTopWidth, 430 | borderBottomWidth, 431 | paddingTop, 432 | paddingBottom} = getComputedStyle(element); 433 | 434 | borderTopWidth = parseFloat(borderTopWidth); 435 | borderBottomWidth = parseFloat(borderBottomWidth); 436 | paddingTop = parseFloat(paddingTop); 437 | paddingBottom = parseFloat(paddingBottom); 438 | 439 | return Math.round(outerHeight - borderTopWidth - borderBottomWidth - paddingTop - paddingBottom); 440 | } 441 | 442 | arrayOfInt(from, length) { 443 | // adapted from https://stackoverflow.com/a/50234108/6500804 444 | return Array.from(Array(length).keys(), n => n + from); 445 | } 446 | 447 | incrementLinesCounter(value) { 448 | this.linesCounter = this.linesCounter + value; 449 | } 450 | 451 | resetLinesCounter() { 452 | this.linesCounter = 0; 453 | } 454 | 455 | isDisplayMath(element) { 456 | const nodes = element.childNodes; 457 | if (nodes.length != 1) return false; 458 | return (nodes[0].nodeName === 'SPAN') && (nodes[0].classList.value === 'math display'); 459 | } 460 | 461 | afterRendered(pages) { 462 | if (!this.options) return; 463 | 464 | for (let page of pages) { 465 | const lineNumbersContainer = this.appendLineNumbersContainer(page); 466 | const pageAreaY = page.area.getBoundingClientRect().y; 467 | let elementsToNumber = page.area.querySelectorAll(this.selector); 468 | 469 | for (let element of elementsToNumber) { 470 | // Do not add line numbers for display math environment 471 | if (this.isDisplayMath(element)) continue; 472 | 473 | // Try to retrieve line height 474 | const lineHeight = this.lineHeight(element); 475 | // Test against lineHeight method returns NaN 476 | if (!lineHeight) { 477 | console.warn('Failed to compute line height value on "' + page.id + '".'); 478 | continue; 479 | } 480 | 481 | const innerHeight = this.innerHeight(element); 482 | 483 | // Number of lines estimation 484 | // There is no built-in method to detect the number of lines in a block. 485 | // The main caveat is that an actual line height can differ from 486 | // the line-height CSS property. 487 | // Mixed fonts, subscripts, superscripts, inline math... can increase 488 | // the actual line height. 489 | // Here, we divide the inner height of the block by the line-height 490 | // computed property and round to the floor to take into account that 491 | // sometimes the actual line height is greater than its property value. 492 | // This is far from perfect and can be easily broken especially by 493 | // inline math. 494 | const nLines = Math.floor(innerHeight / lineHeight); 495 | 496 | // do not add line numbers for void paragraphs 497 | if (nLines <= 0) continue; 498 | 499 | const linenumbers = document.createElement('div'); 500 | lineNumbersContainer.appendChild(linenumbers); 501 | linenumbers.classList.add('maintextlinenumbers'); 502 | 503 | const elementY = element.getBoundingClientRect().y; 504 | linenumbers.style.top = (elementY - pageAreaY) + 'px'; 505 | 506 | const cs = getComputedStyle(element); 507 | const paddingTop = parseFloat(cs.paddingTop) + parseFloat(cs.borderTopWidth); 508 | linenumbers.style.paddingTop = paddingTop + 'px'; 509 | 510 | linenumbers.style.lineHeight = cs.lineHeight; 511 | 512 | linenumbers.innerHTML = this.arrayOfInt(this.linesCounter + 1, nLines) 513 | .reduce((t, v) => t + '
' + v); 514 | this.incrementLinesCounter(nLines); 515 | } 516 | 517 | if (this.options['reset-page']) { 518 | this.resetLinesCounter(); 519 | } 520 | } 521 | } 522 | }); 523 | 524 | // Clean links to avoid impossible line breaking of long urls in a justified text 525 | // Author: Julien Taquet (Paged.js core team) 526 | // see https://github.com/spyrales/gouvdown/issues/37 527 | Paged.registerHandlers(class extends Paged.Handler { 528 | constructor(chunker, polisher, caller) { 529 | super(chunker, polisher, caller); 530 | } 531 | beforeParsed(content) { 532 | // add wbr to / in links 533 | const links = content.querySelectorAll('a[href^="http"], a[href^="www"]'); 534 | links.forEach(link => { 535 | // Rerun to avoid large spaces. 536 | // Break after a colon or a double slash (//) 537 | // or before a single slash (/), a tilde (~), a period, a comma, a hyphen, 538 | // an underline (_), a question mark, a number sign, or a percent symbol. 539 | const content = link.textContent; 540 | if (!(link.childElementCount === 0 && content.match(/^http|^www/))) return; 541 | let printableUrl = content.replace(/\/\//g, "//\u003Cwbr\u003E"); 542 | printableUrl = printableUrl.replace(/\,/g, ",\u003Cwbr\u003E"); 543 | // put wbr around everything. 544 | printableUrl = printableUrl.replace( 545 | /(\/|\~|\-|\.|\,|\_|\?|\#|\%)/g, 546 | "\u003Cwbr\u003E$1" 547 | ); 548 | // turn hyphen in non breaking hyphen 549 | printableUrl = printableUrl.replace(/\-/g, "\u003Cwbr\u003E‑"); 550 | link.setAttribute("data-print-url", printableUrl); 551 | link.innerHTML = printableUrl; 552 | }); 553 | } 554 | }); 555 | 556 | // Repeat table headers on multiple pages 557 | // Authors: Julien Taquet, Lucas Maciuga and Tafael Caixeta, see https://gitlab.coko.foundation/pagedjs/pagedjs/-/issues/84 558 | // TODO: remove this hook when Paged.js integrates this feature 559 | Paged.registerHandlers(class RepeatingTableHeadersHandler extends Paged.Handler { 560 | 561 | constructor(chunker, polisher, caller) { 562 | super(chunker, polisher, caller); 563 | this.splitTablesRefs = []; 564 | } 565 | 566 | afterPageLayout(pageElement, page, breakToken, chunker) { 567 | this.chunker = chunker; 568 | this.splitTablesRefs = []; 569 | 570 | if (breakToken) { 571 | const node = breakToken.node; 572 | const tables = this.findAllAncestors(node, "table"); 573 | if (node.tagName === "TABLE") { 574 | tables.push(node); 575 | } 576 | 577 | if (tables.length > 0) { 578 | this.splitTablesRefs = tables.map(t => t.dataset.ref); 579 | 580 | //checks if split inside thead and if so, set breakToken to next sibling element 581 | let thead = node.tagName === "THEAD" ? node : this.findFirstAncestor(node, "thead"); 582 | if (thead) { 583 | let lastTheadNode = thead.hasChildNodes() ? thead.lastChild : thead; 584 | breakToken.node = this.nodeAfter(lastTheadNode, chunker.source); 585 | } 586 | 587 | this.hideEmptyTables(pageElement, node); 588 | } 589 | } 590 | } 591 | 592 | hideEmptyTables(pageElement, breakTokenNode) { 593 | this.splitTablesRefs.forEach(ref => { 594 | let table = pageElement.querySelector("[data-ref='" + ref + "']"); 595 | if (table) { 596 | let sourceBody = table.querySelector("tbody > tr"); 597 | if (!sourceBody || this.refEquals(sourceBody.firstElementChild, breakTokenNode)) { 598 | table.style.visibility = "hidden"; 599 | table.style.position = "absolute"; 600 | let lineSpacer = table.nextSibling; 601 | if (lineSpacer) { 602 | lineSpacer.style.visibility = "hidden"; 603 | lineSpacer.style.position = "absolute"; 604 | } 605 | } 606 | } 607 | }); 608 | } 609 | 610 | refEquals(a, b) { 611 | return a && a.dataset && b && b.dataset && a.dataset.ref === b.dataset.ref; 612 | } 613 | 614 | findFirstAncestor(element, selector) { 615 | while (element.parentNode && element.parentNode.nodeType === 1) { 616 | if (element.parentNode.matches(selector)) { 617 | return element.parentNode; 618 | } 619 | element = element.parentNode; 620 | } 621 | return null; 622 | } 623 | 624 | findAllAncestors(element, selector) { 625 | const ancestors = []; 626 | while (element.parentNode && element.parentNode.nodeType === 1) { 627 | if (element.parentNode.matches(selector)) { 628 | ancestors.unshift(element.parentNode); 629 | } 630 | element = element.parentNode; 631 | } 632 | return ancestors; 633 | } 634 | 635 | // The addition of repeating Table Headers is done here because this hook is triggered before overflow handling 636 | layout(rendered, layout) { 637 | this.splitTablesRefs.forEach(ref => { 638 | const renderedTable = rendered.querySelector("[data-ref='" + ref + "']"); 639 | if (renderedTable && renderedTable.hasAttribute("data-split-from")) { 640 | // this event can be triggered multiple times 641 | // added a flag repeated-headers to control when table headers already repeated in current page. 642 | if (!renderedTable.getAttribute("repeated-headers")) { 643 | const sourceTable = this.chunker.source.querySelector("[data-ref='" + ref + "']"); 644 | this.repeatColgroup(sourceTable, renderedTable); 645 | this.repeatTHead(sourceTable, renderedTable); 646 | renderedTable.setAttribute("repeated-headers", true); 647 | } 648 | } 649 | }); 650 | } 651 | 652 | repeatColgroup(sourceTable, renderedTable) { 653 | let colgroup = sourceTable.querySelectorAll("colgroup"); 654 | let firstChild = renderedTable.firstChild; 655 | colgroup.forEach((colgroup) => { 656 | let clonedColgroup = colgroup.cloneNode(true); 657 | renderedTable.insertBefore(clonedColgroup, firstChild); 658 | }); 659 | } 660 | 661 | repeatTHead(sourceTable, renderedTable) { 662 | let thead = sourceTable.querySelector("thead"); 663 | if (thead) { 664 | let clonedThead = thead.cloneNode(true); 665 | renderedTable.insertBefore(clonedThead, renderedTable.firstChild); 666 | } 667 | } 668 | 669 | // the functions below are from pagedjs utils/dom.js 670 | nodeAfter(node, limiter) { 671 | if (limiter && node === limiter) { 672 | return; 673 | } 674 | let significantNode = this.nextSignificantNode(node); 675 | if (significantNode) { 676 | return significantNode; 677 | } 678 | if (node.parentNode) { 679 | while ((node = node.parentNode)) { 680 | if (limiter && node === limiter) { 681 | return; 682 | } 683 | significantNode = this.nextSignificantNode(node); 684 | if (significantNode) { 685 | return significantNode; 686 | } 687 | } 688 | } 689 | } 690 | 691 | nextSignificantNode(sib) { 692 | while ((sib = sib.nextSibling)) { 693 | if (!this.isIgnorable(sib)) return sib; 694 | } 695 | return null; 696 | } 697 | 698 | isIgnorable(node) { 699 | return (node.nodeType === 8) || // A comment node 700 | ((node.nodeType === 3) && this.isAllWhitespace(node)); // a text node, all whitespace 701 | } 702 | 703 | isAllWhitespace(node) { 704 | return !(/[^\t\n\r ]/.test(node.textContent)); 705 | } 706 | }); 707 | 708 | } 709 | -------------------------------------------------------------------------------- /Jiena_McLellan_CV_files/font-awesome-5.1.0/css/all.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.1.0 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | .fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:a 2s infinite linear}.fa-pulse{animation:a 1s infinite steps(8)}@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blind:before{content:"\f29d"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-open:before{content:"\f518"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-certificate:before{content:"\f0a3"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-concierge-bell:before{content:"\f562"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-credit-card:before{content:"\f09d"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-deviantart:before{content:"\f1bd"}.fa-diagnoses:before{content:"\f470"}.fa-dice:before{content:"\f522"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-gift:before{content:"\f06b"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hashtag:before{content:"\f292"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-internet-explorer:before{content:"\f26b"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mercury:before{content:"\f223"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-motorcycle:before{content:"\f21c"}.fa-mouse-pointer:before{content:"\f245"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-old-republic:before{content:"\f510"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-people-carry:before{content:"\f4ce"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poo:before{content:"\f2fe"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-r-project:before{content:"\f4f7"}.fa-random:before{content:"\f074"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-rendact:before{content:"\f3e4"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-retweet:before{content:"\f079"}.fa-ribbon:before{content:"\f4d6"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-search:before{content:"\f002"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skull:before{content:"\f54c"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowflake:before{content:"\f2dc"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toolbox:before{content:"\f552"}.fa-tooth:before{content:"\f5c9"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-train:before{content:"\f238"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-moving:before{content:"\f4df"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} --------------------------------------------------------------------------------