diff --git a/asset-manifest.json b/asset-manifest.json index 803d67f..c857890 100644 --- a/asset-manifest.json +++ b/asset-manifest.json @@ -1,8 +1,8 @@ { "files": { - "main.css": "/static/css/main.bcdb8345.chunk.css", - "main.js": "/static/js/main.4bfc845b.chunk.js", - "main.js.map": "/static/js/main.4bfc845b.chunk.js.map", + "main.css": "/static/css/main.3f3d8f60.chunk.css", + "main.js": "/static/js/main.29b65be5.chunk.js", + "main.js.map": "/static/js/main.29b65be5.chunk.js.map", "runtime-main.js": "/static/js/runtime-main.7363c9f6.js", "runtime-main.js.map": "/static/js/runtime-main.7363c9f6.js.map", "static/css/2.1a02f21c.chunk.css": "/static/css/2.1a02f21c.chunk.css", @@ -10,14 +10,14 @@ "static/js/2.932d9689.chunk.js.map": "/static/js/2.932d9689.chunk.js.map", "index.html": "/index.html", "static/css/2.1a02f21c.chunk.css.map": "/static/css/2.1a02f21c.chunk.css.map", - "static/css/main.bcdb8345.chunk.css.map": "/static/css/main.bcdb8345.chunk.css.map", + "static/css/main.3f3d8f60.chunk.css.map": "/static/css/main.3f3d8f60.chunk.css.map", "static/js/2.932d9689.chunk.js.LICENSE.txt": "/static/js/2.932d9689.chunk.js.LICENSE.txt" }, "entrypoints": [ "static/js/runtime-main.7363c9f6.js", "static/css/2.1a02f21c.chunk.css", "static/js/2.932d9689.chunk.js", - "static/css/main.bcdb8345.chunk.css", - "static/js/main.4bfc845b.chunk.js" + "static/css/main.3f3d8f60.chunk.css", + "static/js/main.29b65be5.chunk.js" ] } \ No newline at end of file diff --git a/index.html b/index.html index ef57ca3..2fdb50b 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -Portfolio | Alayna Truttmann
\ No newline at end of file +Portfolio | Alayna Truttmann
\ No newline at end of file diff --git a/static/css/main.bcdb8345.chunk.css b/static/css/main.3f3d8f60.chunk.css similarity index 98% rename from static/css/main.bcdb8345.chunk.css rename to static/css/main.3f3d8f60.chunk.css index 52849c6..5750dd3 100644 --- a/static/css/main.bcdb8345.chunk.css +++ b/static/css/main.3f3d8f60.chunk.css @@ -1,2 +1,2 @@ -@import url(https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap);.header{text-align:center;max-width:55rem;margin:0 auto 3.75rem;padding:0 1.25rem}@media screen and (min-width:768px){.header{margin:0 auto 6.25rem;padding:0 6.25rem}}.header h1{margin-bottom:1.75rem;animation:titleEntrance .6s ease-in}.header .linkIcons{margin-top:2.5rem}.header .linkIcons a{display:inline-block;padding:0 1rem;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;transform:translateY(0);transition:transform .2s ease-in,color .2s ease;color:var(--color-icon)}.header .linkIcons a:hover{transform:translateY(-.25rem);transition:transform .2s ease-out,color .2s ease;color:var(--color-accent)}.header .linkIcons a svg{width:auto;height:2.5rem}.projectTile{height:20rem;max-width:40rem;margin:auto;cursor:pointer;display:flex;flex-direction:column;border-radius:8px;overflow:hidden;transform:translateY(0);transition:transform .2s ease-in,box-shadow .2s;box-shadow:0 .25rem .5rem 2px var(--color-tile-shadow)}.projectTile:hover{transform:translateY(-.5rem);transition:transform .2s ease-out,box-shadow .2s}.projectTile .projectImage{width:100%;height:100%;background-size:cover}.projectTile .projectLabel{padding:1rem}.passwordContainer,.projectTile .projectLabel{background-color:var(--color-background-light)}.passwordContainer{max-width:32rem;margin:6.25rem auto 0;text-align:center;border-radius:12px;line-height:normal;box-shadow:0 .25rem .5rem 2px var(--color-tile-shadow);border:1px solid var(--color-icon);padding:2.5rem}@media screen and (min-width:576px){.passwordContainer{padding:2.5rem 3.75rem}}.passwordContainer .passwordIcon{width:auto;height:1.75rem;margin-bottom:1.25rem;color:var(--color-icon)}.passwordContainer .email{text-decoration:underline}.passwordContainer .formContainer{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;margin:2.5rem auto 0}.passwordContainer .passwordForm{display:flex;justify-content:center;align-items:center;margin-bottom:.75rem}.passwordContainer .passwordForm .passwordInput{font-family:"Merriweather",serif;font-weight:var(--font-weight-body);font-size:1rem;width:12rem;padding:.25rem;outline:none;margin-right:.75rem;transition:border-color .2s ease;color:var(--color-text);border:none;border-bottom:2px solid var(--color-button);background-color:var(--color-background-light)}.passwordContainer .passwordForm .passwordInput:focus{border-color:var(--color-button-hover)}.passwordContainer .passwordForm .passwordButton{font-family:"Josefin Sans",sans-serif;font-weight:var(--font-weight-heading);font-size:.875rem;text-transform:uppercase;padding:.5rem 1rem;border-radius:1.75rem;transition:background-color .2s ease;height:1.75rem;border:none;outline:none;text-decoration:none;background-color:var(--color-button);color:var(--color-text-light)}.passwordContainer .passwordForm .passwordButton:hover{background-color:var(--color-button-hover)}.passwordContainer .passwordError{display:flex;align-items:center;justify-content:center;font-style:italic}.passwordContainer .passwordError .passwordWarning{height:1rem;width:auto;margin-right:.5rem}.modalContainer{width:100%;height:100%;overflow:auto;position:absolute;top:0;left:0;z-index:1;padding:6.25rem 1.5rem}.modalContainer.enter{transform:translateY(50vh);opacity:0}.modalContainer.enter-active{transform:none;transition:.4s ease-in;opacity:1}.modalContainer.exit{transform:none;opacity:1}.modalContainer.exit-active{transform:translateY(50vh);transition:.4s ease-out;opacity:0}.modalContainer .projectContent{max-width:60rem;margin:0 auto;display:flex;flex-direction:column;align-items:center}.modalContainer .projectContent .closeButton{position:fixed;top:1.5rem;right:1.5rem;border:none;outline:none;padding:.5rem;z-index:1;border-radius:100%;background-color:transparent}.modalContainer .projectContent .closeButton:hover svg{color:var(--color-icon-button-hover)}.modalContainer .projectContent .closeButton svg{width:auto;height:1.75rem;transition:color .2s ease;color:var(--color-icon-button)}.modalContainer .projectContent .projectHeader{text-align:center;margin-bottom:3.75rem}.modalContainer .projectContent .projectHeader h1{animation:titleEntrance .6s ease-in}.modalContainer .projectContent .projectHeader h3{animation:subtitleEntrance .6s ease-in}.modalContainer .projectContent .sectionTitle{margin-bottom:1.25rem;text-transform:uppercase;color:var(--color-accent)}.modalContainer .projectContent .sectionTitle,.modalContainer .projectContent h4{align-self:flex-start}.modalContainer .projectContent .overview{width:100%}.modalContainer .projectContent .overview p{margin-bottom:1.25rem}.modalContainer .projectContent .overview p b{text-transform:capitalize}.modalContainer .projectContent .contentDivider{margin:2.5rem 0;width:100%;border-color:var(--color-text)}.modalContainer .projectContent p{width:100%}.modalContainer .projectContent img,.modalContainer .projectContent video{width:100%;border-radius:12px;margin:.5rem 0 3.75rem;box-shadow:0 .25rem .5rem 2px var(--color-tile-shadow)}.modalContainer .projectContent img:has(+.caption),.modalContainer .projectContent video:has(+.caption){margin-bottom:1rem}.modalContainer .projectContent .twoImg{display:flex;justify-content:space-evenly;width:100%}.modalContainer .projectContent .twoImg img{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:100%;max-height:540px}@media screen and (max-width:768px){.modalContainer .projectContent .twoImg{flex-direction:column;align-items:center}}.modalContainer .projectContent video{outline:none;opacity:0;transition:opacity .4s ease}.modalContainer .projectContent .caption{font-family:"Merriweather",serif;font-weight:var(--font-weight-body);font-size:1rem;margin-bottom:3.75rem;font-style:italic;text-align:center}:root{--color-accent:#006f79;--color-highlight:#4fa9b0;--color-background:#efefef;--color-background-light:#f7f7f7;--color-text:#333;--color-text-light:#fff;--color-icon:grey;--color-icon-button:#acacac;--color-icon-button-hover:grey;--color-button:#006f79;--color-button-hover:#4fa9b0;--color-tile-shadow:rgba(0,0,0,0.1);--font-weight-heading:700;--font-weight-heading-small:700;--font-weight-body:400}[data-theme=dark]{--color-accent:#378e98;--color-highlight:#00707a;--color-background:#191919;--color-background-light:#272727;--color-text:#cfcfcf;--color-text-light:#cfcfcf;--color-icon:#707070;--color-icon-button:#525252;--color-icon-button-hover:#707070;--color-button:#006f79;--color-button-hover:#378e98;--color-tile-shadow:rgba(0,0,0,0.4);--font-weight-heading:700;--font-weight-heading-small:600;--font-weight-body:300}@keyframes titleEntrance{0%{transform:translateY(1.25rem)}to{transform:translate(0)}}@keyframes subtitleEntrance{0%{opacity:0;transform:translateY(.75rem)}33%{opacity:0}to{opacity:1;transform:translate(0)}}html{font-size:12px;overflow:hidden;height:100vh}@media screen and (min-width:576px){html{font-size:14px}}@media screen and (min-width:992px){html{font-size:16px}}h1,h2,h3,h4,p{padding:0;margin:0}h1{font-family:"Josefin Sans",sans-serif;font-weight:700;font-weight:var(--font-weight-heading);font-size:4rem;text-transform:uppercase;letter-spacing:3px;color:#006f79;color:var(--color-accent);margin:0 0 .5rem}@media screen and (max-width:576px){h1{font-size:3rem}}h2{font-weight:var(--font-weight-heading);font-size:1.75rem;font-weight:700;font-weight:var(--font-weight-heading-small)}h2,h3{font-family:"Josefin Sans",sans-serif}h3{font-weight:700;font-weight:var(--font-weight-heading);font-weight:500;font-size:1.5rem}h4{font-weight:700;margin:.5rem 0 .75rem}h4,p{font-family:"Merriweather",serif;font-size:1.25rem}p{font-weight:400;font-weight:var(--font-weight-body);line-height:1.75rem;margin-bottom:1.25rem}p.body2{font-size:1rem}p.body2,p.body3{font-family:"Merriweather",serif;font-weight:400;font-weight:var(--font-weight-body);margin-bottom:0}p.body3{font-size:.75rem}b{font-weight:700}a,a:hover{color:#006f79;color:var(--color-accent)}.app{overflow:hidden;height:100vh}.appContainer,.modalContainer{transition:background-color .4s ease,color .2s ease;background-color:#efefef;background-color:var(--color-background);color:#333;color:var(--color-text)}.appContainer ::selection,.modalContainer ::selection{color:#fff;color:var(--color-text-light);background:#4fa9b0;background:var(--color-highlight)}.appContainer{overflow:auto;height:100%}.appContainer .themeToggle{position:fixed;top:1.5rem;right:1.5rem;border:none;outline:none;padding:.5rem;z-index:1;border-radius:100%;background-color:transparent}.appContainer .themeToggle:hover svg{color:grey;color:var(--color-icon-button-hover)}.appContainer .themeToggle svg{width:auto;height:1.75rem;transition:color .2s ease;color:#acacac;color:var(--color-icon-button)}.appContainer .projectsGrid{padding:3.75rem 2.5rem;min-height:calc(100vh - 4rem);max-width:90rem}@media screen and (min-width:768px){.appContainer .projectsGrid{padding:6.25rem 3.75rem}}.appContainer .projectsGrid .projectsRow{justify-content:center}.appContainer .projectsGrid .projectColumn{padding:1rem}.appContainer .footer{width:100%;height:4rem;text-align:center;padding:0 2.5rem;font-style:italic} -/*# sourceMappingURL=main.bcdb8345.chunk.css.map */ \ No newline at end of file +@import url(https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap);.header{text-align:center;max-width:47rem;margin:0 auto 3.75rem;padding:0 1.25rem}@media screen and (min-width:768px){.header{margin:0 auto 6.25rem;padding:0 6.25rem}}.header h1{margin-bottom:1.75rem;animation:titleEntrance .6s ease-in}.header .linkIcons{margin-top:2.5rem}.header .linkIcons a{display:inline-block;padding:0 1rem;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;transform:translateY(0);transition:transform .2s ease-in,color .2s ease;color:var(--color-icon)}.header .linkIcons a:hover{transform:translateY(-.25rem);transition:transform .2s ease-out,color .2s ease;color:var(--color-accent)}.header .linkIcons a svg{width:auto;height:2.5rem}.projectTile{height:20rem;max-width:40rem;margin:auto;cursor:pointer;display:flex;flex-direction:column;border-radius:8px;overflow:hidden;transform:translateY(0);transition:transform .2s ease-in,box-shadow .2s;box-shadow:0 .25rem .5rem 2px var(--color-tile-shadow)}.projectTile:hover{transform:translateY(-.5rem);transition:transform .2s ease-out,box-shadow .2s}.projectTile .projectImage{width:100%;height:100%;background-size:cover}.projectTile .projectLabel{padding:1rem}.passwordContainer,.projectTile .projectLabel{background-color:var(--color-background-light)}.passwordContainer{max-width:32rem;margin:6.25rem auto 0;text-align:center;border-radius:12px;line-height:normal;box-shadow:0 .25rem .5rem 2px var(--color-tile-shadow);border:1px solid var(--color-icon);padding:2.5rem}@media screen and (min-width:576px){.passwordContainer{padding:2.5rem 3.75rem}}.passwordContainer .passwordIcon{width:auto;height:1.75rem;margin-bottom:1.25rem;color:var(--color-icon)}.passwordContainer .email{text-decoration:underline}.passwordContainer .formContainer{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;margin:2.5rem auto 0}.passwordContainer .passwordForm{display:flex;justify-content:center;align-items:center;margin-bottom:.75rem}.passwordContainer .passwordForm .passwordInput{font-family:"Merriweather",serif;font-weight:var(--font-weight-body);font-size:1rem;width:12rem;padding:.25rem;outline:none;margin-right:.75rem;transition:border-color .2s ease;color:var(--color-text);border:none;border-bottom:2px solid var(--color-button);background-color:var(--color-background-light)}.passwordContainer .passwordForm .passwordInput:focus{border-color:var(--color-button-hover)}.passwordContainer .passwordForm .passwordButton{font-family:"Josefin Sans",sans-serif;font-weight:var(--font-weight-heading);font-size:.875rem;text-transform:uppercase;padding:.5rem 1rem;border-radius:1.75rem;transition:background-color .2s ease;height:1.75rem;border:none;outline:none;text-decoration:none;background-color:var(--color-button);color:var(--color-text-light)}.passwordContainer .passwordForm .passwordButton:hover{background-color:var(--color-button-hover)}.passwordContainer .passwordError{display:flex;align-items:center;justify-content:center;font-style:italic}.passwordContainer .passwordError .passwordWarning{height:1rem;width:auto;margin-right:.5rem}.modalContainer{width:100%;height:100%;overflow:auto;position:absolute;top:0;left:0;z-index:1;padding:6.25rem 1.5rem}.modalContainer.enter{transform:translateY(50vh);opacity:0}.modalContainer.enter-active{transform:none;transition:.4s ease-in;opacity:1}.modalContainer.exit{transform:none;opacity:1}.modalContainer.exit-active{transform:translateY(50vh);transition:.4s ease-out;opacity:0}.modalContainer .projectContent{max-width:60rem;margin:0 auto;display:flex;flex-direction:column;align-items:center}.modalContainer .projectContent .closeButton{position:fixed;top:1.5rem;right:1.5rem;border:none;outline:none;padding:.5rem;z-index:1;border-radius:100%;background-color:transparent}.modalContainer .projectContent .closeButton:hover svg{color:var(--color-icon-button-hover)}.modalContainer .projectContent .closeButton svg{width:auto;height:1.75rem;transition:color .2s ease;color:var(--color-icon-button)}.modalContainer .projectContent .projectHeader{text-align:center;margin-bottom:3.75rem}.modalContainer .projectContent .projectHeader h1{animation:titleEntrance .6s ease-in}.modalContainer .projectContent .projectHeader h3{animation:subtitleEntrance .6s ease-in}.modalContainer .projectContent .sectionTitle{margin-bottom:1.25rem;text-transform:uppercase;color:var(--color-accent)}.modalContainer .projectContent .sectionTitle,.modalContainer .projectContent h4{align-self:flex-start}.modalContainer .projectContent .overview{width:100%}.modalContainer .projectContent .overview p{margin-bottom:1.25rem}.modalContainer .projectContent .overview p b{text-transform:capitalize}.modalContainer .projectContent .contentDivider{margin:2.5rem 0;width:100%;border-color:var(--color-text)}.modalContainer .projectContent p{width:100%}.modalContainer .projectContent img,.modalContainer .projectContent video{width:100%;border-radius:12px;margin:.5rem 0 3.75rem;box-shadow:0 .25rem .5rem 2px var(--color-tile-shadow)}.modalContainer .projectContent img:has(+.caption),.modalContainer .projectContent video:has(+.caption){margin-bottom:1rem}.modalContainer .projectContent .twoImg{display:flex;justify-content:space-evenly;width:100%}.modalContainer .projectContent .twoImg img{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:100%;max-height:540px}@media screen and (max-width:768px){.modalContainer .projectContent .twoImg{flex-direction:column;align-items:center}}.modalContainer .projectContent video{outline:none;opacity:0;transition:opacity .4s ease}.modalContainer .projectContent .caption{font-family:"Merriweather",serif;font-weight:var(--font-weight-body);font-size:1rem;margin-bottom:3.75rem;font-style:italic;text-align:center}:root{--color-accent:#006f79;--color-highlight:#4fa9b0;--color-background:#efefef;--color-background-light:#f7f7f7;--color-text:#333;--color-text-light:#fff;--color-icon:grey;--color-icon-button:#acacac;--color-icon-button-hover:grey;--color-button:#006f79;--color-button-hover:#4fa9b0;--color-tile-shadow:rgba(0,0,0,0.1);--font-weight-heading:700;--font-weight-heading-small:700;--font-weight-body:400}[data-theme=dark]{--color-accent:#378e98;--color-highlight:#00707a;--color-background:#191919;--color-background-light:#272727;--color-text:#cfcfcf;--color-text-light:#cfcfcf;--color-icon:#707070;--color-icon-button:#525252;--color-icon-button-hover:#707070;--color-button:#006f79;--color-button-hover:#378e98;--color-tile-shadow:rgba(0,0,0,0.4);--font-weight-heading:700;--font-weight-heading-small:600;--font-weight-body:300}@keyframes titleEntrance{0%{transform:translateY(1.25rem)}to{transform:translate(0)}}@keyframes subtitleEntrance{0%{opacity:0;transform:translateY(.75rem)}33%{opacity:0}to{opacity:1;transform:translate(0)}}html{font-size:12px;overflow:hidden;height:100vh}@media screen and (min-width:576px){html{font-size:14px}}@media screen and (min-width:992px){html{font-size:16px}}h1,h2,h3,h4,p{padding:0;margin:0}h1{font-family:"Josefin Sans",sans-serif;font-weight:700;font-weight:var(--font-weight-heading);font-size:4rem;text-transform:uppercase;letter-spacing:3px;color:#006f79;color:var(--color-accent);margin:0 0 .5rem}@media screen and (max-width:576px){h1{font-size:3rem}}h2{font-weight:var(--font-weight-heading);font-size:1.75rem;font-weight:700;font-weight:var(--font-weight-heading-small)}h2,h3{font-family:"Josefin Sans",sans-serif}h3{font-weight:700;font-weight:var(--font-weight-heading);font-weight:500;font-size:1.5rem}h4{font-weight:700;margin:.5rem 0 .75rem}h4,p{font-family:"Merriweather",serif;font-size:1.25rem}p{font-weight:400;font-weight:var(--font-weight-body);line-height:1.75rem;margin-bottom:1.25rem}p.body2{font-size:1rem}p.body2,p.body3{font-family:"Merriweather",serif;font-weight:400;font-weight:var(--font-weight-body);margin-bottom:0}p.body3{font-size:.75rem}b{font-weight:700}a,a:hover{color:#006f79;color:var(--color-accent)}.app{overflow:hidden;height:100vh}.appContainer,.modalContainer{transition:background-color .4s ease,color .2s ease;background-color:#efefef;background-color:var(--color-background);color:#333;color:var(--color-text)}.appContainer ::selection,.modalContainer ::selection{color:#fff;color:var(--color-text-light);background:#4fa9b0;background:var(--color-highlight)}.appContainer{overflow:auto;height:100%}.appContainer .themeToggle{position:fixed;top:1.5rem;right:1.5rem;border:none;outline:none;padding:.5rem;z-index:1;border-radius:100%;background-color:transparent}.appContainer .themeToggle:hover svg{color:grey;color:var(--color-icon-button-hover)}.appContainer .themeToggle svg{width:auto;height:1.75rem;transition:color .2s ease;color:#acacac;color:var(--color-icon-button)}.appContainer .projectsGrid{padding:3.75rem 2.5rem;min-height:calc(100vh - 4rem);max-width:90rem}@media screen and (min-width:768px){.appContainer .projectsGrid{padding:6.25rem 3.75rem}}.appContainer .projectsGrid .projectsRow{justify-content:center}.appContainer .projectsGrid .projectColumn{padding:1rem}.appContainer .footer{width:100%;height:4rem;text-align:center;padding:0 2.5rem;font-style:italic} +/*# sourceMappingURL=main.3f3d8f60.chunk.css.map */ \ No newline at end of file diff --git a/static/css/main.bcdb8345.chunk.css.map b/static/css/main.3f3d8f60.chunk.css.map similarity index 53% rename from static/css/main.bcdb8345.chunk.css.map rename to static/css/main.3f3d8f60.chunk.css.map index 808739d..f7cc7be 100644 --- a/static/css/main.bcdb8345.chunk.css.map +++ b/static/css/main.3f3d8f60.chunk.css.map @@ -1 +1 @@ -{"version":3,"sources":["main.bcdb8345.chunk.css","webpack://src/Header/Header.scss","webpack://src/styles/_sizes.scss","webpack://src/styles/_animations.scss","webpack://src/ProjectTile/ProjectTile.scss","webpack://src/styles/_borderRadius.scss","webpack://src/styles/_variables.scss","webpack://src/ProjectModal/PasswordProtector/PasswordProtector.scss","webpack://src/styles/_typography.scss","webpack://src/ProjectModal/ProjectModal.scss","webpack://src/styles/_theme.scss","webpack://src/App.scss"],"names":[],"mappings":"AAIA,4JAA4J,CCF5J,QACE,iBAAA,CACA,eAAA,CACA,qBAAA,CACA,iBAAA,CAEA,oCANF,QAOI,qBAAA,CACA,iBAAA,CAAA,CAGF,WACE,qBCRK,CDSL,mCETc,CFYhB,mBACE,iBCZK,CDcL,qBACE,oBAAA,CACA,cAAA,CACA,0BAAA,CAAA,uBAAA,CAAA,kBAAA,CACA,uBAAA,CACA,+CAAA,CACA,uBAAA,CAEA,2BACE,6BAAA,CACA,gDAAA,CACA,yBAAA,CAGF,yBACE,UAAA,CACA,aC9BC,CELT,aACE,YAAA,CACA,eAAA,CACA,WAAA,CACA,cAAA,CACA,YAAA,CACA,qBAAA,CACA,iBCTsB,CDUtB,eAAA,CACA,uBAAA,CACA,+CAAA,CELA,sDAAA,CFQA,mBACE,4BAAA,CACA,gDAAA,CAGF,2BACE,UAAA,CACA,WAAA,CACA,qBAAA,CAGF,2BACE,YACA,CG1BJ,8CH0BI,8CFrBK,CKLT,mBACE,eAAA,CACA,qBAAA,CACA,iBAAA,CACA,kBFLoB,CEMpB,kBAAA,CAAA,sDAAA,CAEA,kCAAA,CAEA,cLJO,CKKP,oCAVF,mBAWI,sBAAA,CAAA,CAGF,iCACE,UAAA,CACA,cLZK,CKaL,qBLfK,CKgBL,uBAAA,CAGF,0BACE,yBAAA,CAGF,kCACE,yBAAA,CAAA,sBAAA,CAAA,iBAAA,CACA,oBAAA,CAGF,iCACE,YAAA,CACA,sBAAA,CACA,kBAAA,CACA,oBLlCK,CKoCL,gDCWF,gCA9CU,CA+CV,mCAAA,CAWA,cAAA,CDrBI,WAAA,CACA,cLzCG,CK6CH,YAAA,CACA,mBL5CG,CK6CH,gCAAA,CACA,uBAAA,CACA,WAAA,CAAA,2CAAA,CACA,8CAAA,CAEA,sDACE,sCAAA,CAIJ,iDClDF,qCALW,CAMX,sCAAA,CAmCA,iBAAA,CACA,wBAAA,CFhCA,kBAAA,CACA,qBJPO,CIQP,oCAAA,CACA,cJTO,CIUP,WAAA,CACA,YAAA,CACA,oBAAA,CACA,oCAAA,CACA,6BAAA,CAEA,uDACE,0CAAA,CCuCF,kCACE,YAAA,CACA,kBAAA,CACA,sBAAA,CACA,iBAAA,CAEA,mDACE,WLlEG,CKmEH,UAAA,CACA,kBLtEG,COCT,gBACE,UAAA,CACA,WAAA,CACA,aAAA,CACA,iBAAA,CACA,KAAA,CACA,MAAA,CACA,SAAA,CACA,sBAAA,CAEA,sBACE,0BAAA,CACA,SAAA,CAEF,6BACE,cAAA,CACA,sBNPkB,CMQlB,SAAA,CAEF,qBACE,cAAA,CACA,SAAA,CAEF,4BACE,0BAAA,CACA,uBNfmB,CMgBnB,SAAA,CAGF,gCACE,eAAA,CACA,aAAA,CACA,YAAA,CACA,qBAAA,CACA,kBAAA,CAEA,6CHVF,cAAA,CACA,UJxBO,CIyBP,YJzBO,CI0BP,WAAA,CACA,YAAA,CACA,aJhCO,CIiCP,SAAA,CACA,kBAAA,CACA,4BAAA,CAEA,uDACE,oCAAA,CAGF,iDACE,UAAA,CACA,cJtCK,CIuCL,yBAAA,CACA,8BAAA,CGJA,+CACE,iBAAA,CACA,qBPpCG,COsCH,kDACE,mCNzCU,CM2CZ,kDACE,sCN3Ca,CM+CjB,8CACE,qBPnDG,COoDH,wBAAA,CACA,yBAAA,CAEF,iFAEE,qBAAA,CAGF,0CACE,UAAA,CACA,4CACE,qBP/DC,COgED,8CACE,yBAAA,CAKN,gDACE,eAAA,CACA,UAAA,CACA,8BAAA,CAGF,kCACE,UAAA,CAGF,0EAEE,UAAA,CACA,kBJtFgB,CIuFhB,sBAAA,CHjFJ,sDAAA,CGoFI,wGACE,kBPzFC,CO6FL,wCACE,YAAA,CACA,4BAAA,CACA,UAAA,CAEA,4CACE,yBAAA,CAAA,sBAAA,CAAA,iBAAA,CACA,cAAA,CACA,gBAAA,CAGF,oCAXF,wCAYI,qBAAA,CACA,kBAAA,CAAA,CAIJ,sCACE,YAAA,CACA,SAAA,CACA,2BNrGkB,CMwGpB,yCDtEF,gCA9CU,CA+CV,mCAAA,CAWA,cAAA,CC4DI,qBPjHG,COkHH,iBAAA,CACA,iBAAA,CD3HE,MEEN,sBAAA,CACA,yBAAA,CACA,0BAAA,CACA,gCAAA,CACA,iBAAA,CACA,uBAAA,CACA,iBAAA,CACA,2BAAA,CACA,8BAAA,CACA,sBAAA,CACA,4BAAA,CACA,mCAAA,CAGA,yBAAA,CACA,+BAAA,CACA,sBAAA,CAGF,kBAEE,sBAAA,CACA,yBAAA,CACA,0BAAA,CACA,gCAAA,CACA,oBAAA,CACA,0BAAA,CACA,oBAAA,CACA,2BAAA,CACA,iCAAA,CACA,sBAAA,CACA,4BAAA,CACA,mCAAA,CAGA,yBAAA,CACA,+BAAA,CACA,sBAAA,CPtBF,yBACE,GACE,6BAAA,CAEF,GACE,sBAAA,CAAA,CAIJ,4BACE,GACE,SAAA,CACA,4BAAA,CAEF,IACE,SAAA,CAEF,GACE,SAAA,CACA,sBAAA,CAAA,CQjCJ,KACE,cAAA,CACA,eAAA,CACA,YAAA,CAEA,oCALF,KAMI,cAAA,CAAA,CAEF,oCARF,KASI,cAAA,CAAA,CAIJ,cAKE,SAAA,CACA,QAAA,CAGF,GHlBE,qCALW,CAMX,eAAA,CAAA,sCAAA,CAKA,cAAA,CACA,wBAAA,CACA,kBAAA,CGYA,aAAA,CAAA,yBAAA,CACA,gBAAA,CHXA,oCGQF,GHPI,cAAA,CAAA,CGaJ,GHvBE,sCAAA,CAgBA,iBAAA,CACA,eAAA,CAAA,4CAAA,CGUF,MH5BE,qCAwBA,CGIF,GH3BE,eAAA,CAAA,sCAAA,CAsBA,eAAA,CACA,gBAAA,CGQF,GHHE,eAAA,CGKA,qBAAA,CAGF,KHTE,gCAhCU,CAkCV,iBNjCO,CSwCT,EHME,eAAA,CAAA,mCAAA,CAMA,mBAAA,CGVA,qBT1CO,CS4CP,QHaA,cGXE,CAEF,gBHHA,gCA9CU,CA+CV,eAAA,CAAA,mCAAA,CGAE,eAIA,CAFF,QHcA,gBGZE,CAIJ,EACE,eAAA,CAKA,UACE,aAAA,CAAA,yBAAA,CAIJ,KACE,eAAA,CACA,YAAA,CAGF,8BAEE,mDAAA,CACA,wBAAA,CAAA,wCAAA,CACA,UAAA,CAAA,uBAAA,CAEA,sDACE,UAAA,CAAA,6BAAA,CACA,kBAAA,CAAA,iCAAA,CAIJ,cACE,aAAA,CACA,WAAA,CAEA,2BL9DA,cAAA,CACA,UJxBO,CIyBP,YJzBO,CI0BP,WAAA,CACA,YAAA,CACA,aJhCO,CIiCP,SAAA,CACA,kBAAA,CACA,4BAAA,CAEA,qCACE,UAAA,CAAA,oCAAA,CAGF,+BACE,UAAA,CACA,cJtCK,CIuCL,yBAAA,CACA,aAAA,CAAA,8BAAA,CKgDF,4BACE,sBAAA,CACA,6BAAA,CAKA,eAAA,CAHA,oCAJF,4BAKI,uBAAA,CAAA,CAIF,yCACE,sBAAA,CAEF,2CACE,YTxGG,CS4GP,sBACE,UAAA,CACA,WAAA,CACA,iBAAA,CACA,gBAAA,CACA,iBAAA","file":"main.bcdb8345.chunk.css","sourcesContent":["@import url(https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap);\n@import url(https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap);\n@import url(https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap);\n@import url(https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap);\n@import url(https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap);\n@keyframes titleEntrance{0%{transform:translate(0, 1.25rem)}100%{transform:translate(0, 0)}}@keyframes subtitleEntrance{0%{opacity:0;transform:translate(0, 0.75rem)}33%{opacity:0}100%{opacity:1;transform:translate(0, 0)}}.header{text-align:center;max-width:55rem;margin:0 auto 3.75rem auto;padding:0 1.25rem}@media screen and (min-width: 768px){.header{margin:0 auto 6.25rem auto;padding:0 6.25rem}}.header h1{margin-bottom:1.75rem;animation:.6s titleEntrance ease-in}.header .linkIcons{margin-top:2.5rem}.header .linkIcons a{display:inline-block;padding:0 1rem;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;transform:translateY(0);transition:transform .2s ease-in,color .2s ease;color:var(--color-icon)}.header .linkIcons a:hover{transform:translateY(-0.25rem);transition:transform .2s ease-out,color .2s ease;color:var(--color-accent)}.header .linkIcons a svg{width:auto;height:2.5rem}\n@keyframes titleEntrance{0%{transform:translate(0, 1.25rem)}100%{transform:translate(0, 0)}}@keyframes subtitleEntrance{0%{opacity:0;transform:translate(0, 0.75rem)}33%{opacity:0}100%{opacity:1;transform:translate(0, 0)}}.projectTile{height:20rem;max-width:40rem;margin:auto;cursor:pointer;display:flex;flex-direction:column;border-radius:8px;overflow:hidden;transform:translateY(0);transition:transform .2s ease-in,box-shadow .2s;box-shadow:0 .25rem .5rem 2px var(--color-tile-shadow)}.projectTile:hover{transform:translateY(-0.5rem);transition:transform .2s ease-out,box-shadow .2s}.projectTile .projectImage{width:100%;height:100%;background-size:cover}.projectTile .projectLabel{padding:1rem;background-color:var(--color-background-light)}\n@keyframes titleEntrance{0%{transform:translate(0, 1.25rem)}100%{transform:translate(0, 0)}}@keyframes subtitleEntrance{0%{opacity:0;transform:translate(0, 0.75rem)}33%{opacity:0}100%{opacity:1;transform:translate(0, 0)}}.passwordContainer{max-width:32rem;margin:6.25rem auto 0 auto;text-align:center;border-radius:12px;line-height:normal;box-shadow:0 .25rem .5rem 2px var(--color-tile-shadow);border:1px solid var(--color-icon);background-color:var(--color-background-light);padding:2.5rem}@media screen and (min-width: 576px){.passwordContainer{padding:2.5rem 3.75rem}}.passwordContainer .passwordIcon{width:auto;height:1.75rem;margin-bottom:1.25rem;color:var(--color-icon)}.passwordContainer .email{text-decoration:underline}.passwordContainer .formContainer{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;margin:2.5rem auto 0 auto}.passwordContainer .passwordForm{display:flex;justify-content:center;align-items:center;margin-bottom:.75rem}.passwordContainer .passwordForm .passwordInput{font-family:\"Merriweather\",serif;font-weight:var(--font-weight-body);font-size:1rem;width:12rem;padding:.25rem;border-top:none;border-right:none;border-left:none;outline:none;margin-right:.75rem;transition:border-color .2s ease;color:var(--color-text);border-bottom:2px solid var(--color-button);background-color:var(--color-background-light)}.passwordContainer .passwordForm .passwordInput:focus{border-color:var(--color-button-hover)}.passwordContainer .passwordForm .passwordButton{font-family:\"Josefin Sans\",sans-serif;font-weight:var(--font-weight-heading);font-size:.875rem;text-transform:uppercase;padding:.5rem 1rem;border-radius:1.75rem;transition:background-color .2s ease;height:1.75rem;border:none;outline:none;text-decoration:none;background-color:var(--color-button);color:var(--color-text-light)}.passwordContainer .passwordForm .passwordButton:hover{background-color:var(--color-button-hover)}.passwordContainer .passwordError{display:flex;align-items:center;justify-content:center;font-style:italic}.passwordContainer .passwordError .passwordWarning{height:1rem;width:auto;margin-right:.5rem}\n@keyframes titleEntrance{0%{transform:translate(0, 1.25rem)}100%{transform:translate(0, 0)}}@keyframes subtitleEntrance{0%{opacity:0;transform:translate(0, 0.75rem)}33%{opacity:0}100%{opacity:1;transform:translate(0, 0)}}.modalContainer{width:100%;height:100%;overflow:auto;position:absolute;top:0;left:0;z-index:1;padding:6.25rem 1.5rem}.modalContainer.enter{transform:translate(0, 50vh);opacity:0}.modalContainer.enter-active{transform:none;transition:.4s ease-in;opacity:1}.modalContainer.exit{transform:none;opacity:1}.modalContainer.exit-active{transform:translate(0, 50vh);transition:.4s ease-out;opacity:0}.modalContainer .projectContent{max-width:60rem;margin:0 auto;display:flex;flex-direction:column;align-items:center}.modalContainer .projectContent .closeButton{position:fixed;top:1.5rem;right:1.5rem;border:none;outline:none;padding:.5rem;z-index:1;border-radius:100%;background-color:transparent}.modalContainer .projectContent .closeButton:hover svg{color:var(--color-icon-button-hover)}.modalContainer .projectContent .closeButton svg{width:auto;height:1.75rem;transition:color .2s ease;color:var(--color-icon-button)}.modalContainer .projectContent .projectHeader{text-align:center;margin-bottom:3.75rem}.modalContainer .projectContent .projectHeader h1{animation:.6s titleEntrance ease-in}.modalContainer .projectContent .projectHeader h3{animation:.6s subtitleEntrance ease-in}.modalContainer .projectContent .sectionTitle{margin-bottom:1.25rem;text-transform:uppercase;color:var(--color-accent)}.modalContainer .projectContent .sectionTitle,.modalContainer .projectContent h4{align-self:flex-start}.modalContainer .projectContent .overview{width:100%}.modalContainer .projectContent .overview p{margin-bottom:1.25rem}.modalContainer .projectContent .overview p b{text-transform:capitalize}.modalContainer .projectContent .contentDivider{margin:2.5rem 0;width:100%;border-color:var(--color-text)}.modalContainer .projectContent p{width:100%}.modalContainer .projectContent img,.modalContainer .projectContent video{width:100%;border-radius:12px;margin:.5rem 0 3.75rem 0;box-shadow:0 .25rem .5rem 2px var(--color-tile-shadow)}.modalContainer .projectContent img:has(+.caption),.modalContainer .projectContent video:has(+.caption){margin-bottom:1rem}.modalContainer .projectContent .twoImg{display:flex;justify-content:space-evenly;width:100%}.modalContainer .projectContent .twoImg img{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:100%;max-height:540px}@media screen and (max-width: 768px){.modalContainer .projectContent .twoImg{flex-direction:column;align-items:center}}.modalContainer .projectContent video{outline:none;opacity:0;transition:opacity .4s ease}.modalContainer .projectContent .caption{font-family:\"Merriweather\",serif;font-weight:var(--font-weight-body);font-size:1rem;margin-bottom:3.75rem;font-style:italic;text-align:center}\n:root{--color-accent: #006f79;--color-highlight: #4fa9b0;--color-background: #efefef;--color-background-light: #f7f7f7;--color-text: #333333;--color-text-light: #ffffff;--color-icon: #808080;--color-icon-button: #acacac;--color-icon-button-hover: #808080;--color-button: #006f79;--color-button-hover: #4fa9b0;--color-tile-shadow: rgba(0, 0, 0, 0.1);--font-weight-heading: 700;--font-weight-heading-small: 700;--font-weight-body: 400}[data-theme=dark]{--color-accent: #378e98;--color-highlight: #00707a;--color-background: #191919;--color-background-light: #272727;--color-text: #cfcfcf;--color-text-light: #cfcfcf;--color-icon: #707070;--color-icon-button: #525252;--color-icon-button-hover: #707070;--color-button: #006f79;--color-button-hover: #378e98;--color-tile-shadow: rgba(0, 0, 0, 0.4);--font-weight-heading: 700;--font-weight-heading-small: 600;--font-weight-body: 300}@keyframes titleEntrance{0%{transform:translate(0, 1.25rem)}100%{transform:translate(0, 0)}}@keyframes subtitleEntrance{0%{opacity:0;transform:translate(0, 0.75rem)}33%{opacity:0}100%{opacity:1;transform:translate(0, 0)}}html{font-size:12px;overflow:hidden;height:100vh}@media screen and (min-width: 576px){html{font-size:14px}}@media screen and (min-width: 992px){html{font-size:16px}}h1,h2,h3,h4,p{padding:0;margin:0}h1{font-family:\"Josefin Sans\",sans-serif;font-weight:700;font-weight:var(--font-weight-heading);font-size:4rem;text-transform:uppercase;letter-spacing:3px;color:#006f79;color:var(--color-accent);margin:0 0 .5rem 0}@media screen and (max-width: 576px){h1{font-size:3rem}}h2{font-family:\"Josefin Sans\",sans-serif;font-weight:700;font-weight:var(--font-weight-heading);font-size:1.75rem;font-weight:700;font-weight:var(--font-weight-heading-small)}h3{font-family:\"Josefin Sans\",sans-serif;font-weight:700;font-weight:var(--font-weight-heading);font-weight:500;font-size:1.5rem}h4{font-family:\"Merriweather\",serif;font-weight:700;font-size:1.25rem;margin:.5rem 0 .75rem 0}p{font-family:\"Merriweather\",serif;font-weight:400;font-weight:var(--font-weight-body);font-size:1.25rem;line-height:1.75rem;margin-bottom:1.25rem}p.body2{font-family:\"Merriweather\",serif;font-weight:400;font-weight:var(--font-weight-body);font-size:1rem;margin-bottom:0}p.body3{font-family:\"Merriweather\",serif;font-weight:400;font-weight:var(--font-weight-body);font-size:.75rem;margin-bottom:0}b{font-weight:700}a{color:#006f79;color:var(--color-accent)}a:hover{color:#006f79;color:var(--color-accent)}.app{overflow:hidden;height:100vh}.appContainer,.modalContainer{transition:background-color .4s ease,color .2s ease;background-color:#efefef;background-color:var(--color-background);color:#333333;color:var(--color-text)}.appContainer ::selection,.modalContainer ::selection{color:#ffffff;color:var(--color-text-light);background:#4fa9b0;background:var(--color-highlight)}.appContainer{overflow:auto;height:100%}.appContainer .themeToggle{position:fixed;top:1.5rem;right:1.5rem;border:none;outline:none;padding:.5rem;z-index:1;border-radius:100%;background-color:transparent}.appContainer .themeToggle:hover svg{color:#808080;color:var(--color-icon-button-hover)}.appContainer .themeToggle svg{width:auto;height:1.75rem;transition:color .2s ease;color:#acacac;color:var(--color-icon-button)}.appContainer .projectsGrid{padding:3.75rem 2.5rem;min-height:calc(100vh - 4rem);max-width:90rem}@media screen and (min-width: 768px){.appContainer .projectsGrid{padding:6.25rem 3.75rem}}.appContainer .projectsGrid .projectsRow{justify-content:center}.appContainer .projectsGrid .projectColumn{padding:1rem}.appContainer .footer{width:100%;height:4rem;text-align:center;padding:0 2.5rem;font-style:italic}\n","@import \"../styles/variables\";\r\n\r\n.header {\r\n text-align: center;\r\n max-width: 55rem;\r\n margin: 0 auto $size-9 auto;\r\n padding: 0 $size-5;\r\n\r\n @media screen and (min-width: $breakpoint-md) {\r\n margin: 0 auto $size-10 auto;\r\n padding: 0 $size-10;\r\n }\r\n\r\n h1 {\r\n margin-bottom: $size-7;\r\n animation: $animation-title;\r\n }\r\n\r\n .linkIcons {\r\n margin-top: $size-8;\r\n\r\n a {\r\n display: inline-block;\r\n padding: 0 $size-4;\r\n height: fit-content;\r\n transform: translateY(0);\r\n transition: $transition-hover-up, color $transition-color;\r\n color: var(--color-icon);\r\n\r\n &:hover {\r\n transform: translateY(-$size-1);\r\n transition: $transition-hover-down, color $transition-color;\r\n color: var(--color-accent);\r\n }\r\n\r\n svg {\r\n width: auto;\r\n height: $size-8;\r\n }\r\n }\r\n }\r\n}\r\n","$size-1: 0.25rem;\r\n$size-2: 0.5rem;\r\n$size-3: 0.75rem;\r\n$size-4: 1rem;\r\n$size-5: 1.25rem;\r\n$size-6: 1.5rem;\r\n$size-7: 1.75rem;\r\n$size-8: 2.5rem;\r\n$size-9: 3.75rem;\r\n$size-10: 6.25rem;\r\n","@import \"sizes\";\r\n\r\n$animation-duration-1: 0.2s;\r\n$animation-duration-2: 0.4s;\r\n$animation-duration-3: 0.6s;\r\n\r\n$animation-title: $animation-duration-3 titleEntrance ease-in;\r\n$animation-subTitle: $animation-duration-3 subtitleEntrance ease-in;\r\n\r\n$transition-hover-up: transform $animation-duration-1 ease-in;\r\n$transition-hover-down: transform $animation-duration-1 ease-out;\r\n$transition-modal-in: $animation-duration-2 ease-in;\r\n$transition-modal-out: $animation-duration-2 ease-out;\r\n$transition-color: $animation-duration-1 ease;\r\n$transition-background-color: background-color $animation-duration-2 ease;\r\n$transition-video-load: opacity $animation-duration-2 ease;\r\n\r\n@keyframes titleEntrance {\r\n 0% {\r\n transform: translate(0, $size-5);\r\n }\r\n 100% {\r\n transform: translate(0, 0);\r\n }\r\n}\r\n\r\n@keyframes subtitleEntrance {\r\n 0% {\r\n opacity: 0;\r\n transform: translate(0, $size-3);\r\n }\r\n 33% {\r\n opacity: 0;\r\n }\r\n 100% {\r\n opacity: 1;\r\n transform: translate(0, 0);\r\n }\r\n}\r\n","@import \"../styles/variables\";\r\n\r\n.projectTile {\r\n height: 20rem;\r\n max-width: 40rem;\r\n margin: auto;\r\n cursor: pointer;\r\n display: flex;\r\n flex-direction: column;\r\n border-radius: $border-radius-default;\r\n overflow: hidden;\r\n transform: translateY(0);\r\n transition: $transition-hover-up, box-shadow $animation-duration-1;\r\n @include tile-box-shadow;\r\n\r\n &:hover {\r\n transform: translateY(-$size-2);\r\n transition: $transition-hover-down, box-shadow $animation-duration-1;\r\n }\r\n\r\n .projectImage {\r\n width: 100%;\r\n height: 100%;\r\n background-size: cover;\r\n }\r\n\r\n .projectLabel {\r\n padding: $size-4;\r\n background-color: var(--color-background-light);\r\n }\r\n}\r\n","$border-radius-default: 8px;\r\n$border-radius-large: 12px;\r\n","@import \"animations\";\r\n@import \"borderRadius\";\r\n@import \"breakpoints\";\r\n@import \"sizes\";\r\n@import \"typography\";\r\n\r\n@mixin tile-box-shadow {\r\n box-shadow: 0 $size-1 $size-2 2px var(--color-tile-shadow);\r\n}\r\n\r\n@mixin button-style {\r\n @include button-text;\r\n padding: $size-2 $size-4;\r\n border-radius: $size-7;\r\n transition: background-color $transition-color;\r\n height: $size-7; // Fixes text vertical alignment issue\r\n border: none;\r\n outline: none;\r\n text-decoration: none;\r\n background-color: var(--color-button);\r\n color: var(--color-text-light);\r\n\r\n &:hover {\r\n background-color: var(--color-button-hover);\r\n }\r\n}\r\n\r\n@mixin icon-button-style {\r\n position: fixed;\r\n top: $size-6;\r\n right: $size-6;\r\n border: none;\r\n outline: none;\r\n padding: $size-2;\r\n z-index: 1;\r\n border-radius: 100%;\r\n background-color: transparent;\r\n\r\n &:hover svg {\r\n color: var(--color-icon-button-hover);\r\n }\r\n\r\n svg {\r\n width: auto;\r\n height: $size-7;\r\n transition: color $transition-color;\r\n color: var(--color-icon-button);\r\n }\r\n}\r\n","@import \"../../styles/variables\";\r\n\r\n.passwordContainer {\r\n max-width: 32rem;\r\n margin: $size-10 auto 0 auto;\r\n text-align: center;\r\n border-radius: $border-radius-large;\r\n line-height: normal;\r\n @include tile-box-shadow;\r\n border: 1px solid var(--color-icon);\r\n background-color: var(--color-background-light);\r\n padding: $size-8;\r\n @media screen and (min-width: $breakpoint-sm) {\r\n padding: $size-8 $size-9;\r\n }\r\n\r\n .passwordIcon {\r\n width: auto;\r\n height: $size-7;\r\n margin-bottom: $size-5;\r\n color: var(--color-icon);\r\n }\r\n\r\n .email {\r\n text-decoration: underline;\r\n }\r\n\r\n .formContainer {\r\n width: fit-content;\r\n margin: $size-8 auto 0 auto;\r\n }\r\n\r\n .passwordForm {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n margin-bottom: $size-3;\r\n\r\n .passwordInput {\r\n @include body-2;\r\n width: 12rem;\r\n padding: $size-1;\r\n border-top: none;\r\n border-right: none;\r\n border-left: none;\r\n outline: none;\r\n margin-right: $size-3;\r\n transition: border-color $transition-color;\r\n color: var(--color-text);\r\n border-bottom: 2px solid var(--color-button);\r\n background-color: var(--color-background-light);\r\n\r\n &:focus {\r\n border-color: var(--color-button-hover);\r\n }\r\n }\r\n\r\n .passwordButton {\r\n @include button-style;\r\n }\r\n }\r\n\r\n .passwordError {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-style: italic;\r\n\r\n .passwordWarning {\r\n height: $size-4;\r\n width: auto;\r\n margin-right: $size-2;\r\n }\r\n }\r\n}\r\n","@import url(\"https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap\");\r\n\r\n$font-title: \"Josefin Sans\", sans-serif;\r\n$font-text: \"Merriweather\", serif;\r\n\r\n// Headings\r\n@mixin heading {\r\n font-family: $font-title;\r\n font-weight: var(--font-weight-heading);\r\n}\r\n\r\n@mixin heading-1 {\r\n @include heading;\r\n font-size: 4rem;\r\n text-transform: uppercase;\r\n letter-spacing: 3px;\r\n\r\n @media screen and (max-width: $breakpoint-sm) {\r\n font-size: 3rem;\r\n }\r\n}\r\n\r\n@mixin heading-2 {\r\n @include heading;\r\n font-size: 1.75rem;\r\n font-weight: var(--font-weight-heading-small);\r\n}\r\n\r\n@mixin heading-3 {\r\n @include heading;\r\n font-weight: 500;\r\n font-size: 1.5rem;\r\n}\r\n\r\n@mixin heading-4 {\r\n font-family: $font-text;\r\n font-weight: 700;\r\n font-size: 1.25rem;\r\n}\r\n\r\n// Button\r\n@mixin button-text {\r\n @include heading;\r\n font-size: 0.875rem;\r\n text-transform: uppercase;\r\n}\r\n\r\n// Body\r\n@mixin body {\r\n font-family: $font-text;\r\n font-weight: var(--font-weight-body);\r\n}\r\n\r\n@mixin body-1 {\r\n @include body;\r\n font-size: 1.25rem;\r\n line-height: 1.75rem;\r\n}\r\n\r\n@mixin body-2 {\r\n @include body;\r\n font-size: 1rem;\r\n}\r\n\r\n@mixin body-3 {\r\n @include body;\r\n font-size: 0.75rem;\r\n}\r\n","@import \"../styles/variables\";\r\n\r\n.modalContainer {\r\n width: 100%;\r\n height: 100%;\r\n overflow: auto;\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n z-index: 1;\r\n padding: $size-10 $size-6;\r\n\r\n &.enter {\r\n transform: translate(0, 50vh);\r\n opacity: 0;\r\n }\r\n &.enter-active {\r\n transform: none;\r\n transition: $transition-modal-in;\r\n opacity: 1;\r\n }\r\n &.exit {\r\n transform: none;\r\n opacity: 1;\r\n }\r\n &.exit-active {\r\n transform: translate(0, 50vh);\r\n transition: $transition-modal-out;\r\n opacity: 0;\r\n }\r\n\r\n .projectContent {\r\n max-width: 60rem;\r\n margin: 0 auto;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n\r\n .closeButton {\r\n @include icon-button-style;\r\n }\r\n\r\n .projectHeader {\r\n text-align: center;\r\n margin-bottom: $size-9;\r\n\r\n h1 {\r\n animation: $animation-title;\r\n }\r\n h3 {\r\n animation: $animation-subTitle;\r\n }\r\n }\r\n\r\n .sectionTitle {\r\n margin-bottom: $size-5;\r\n text-transform: uppercase;\r\n color: var(--color-accent);\r\n }\r\n .sectionTitle,\r\n h4 {\r\n align-self: flex-start;\r\n }\r\n\r\n .overview {\r\n width: 100%;\r\n p {\r\n margin-bottom: $size-5;\r\n b {\r\n text-transform: capitalize;\r\n }\r\n }\r\n }\r\n\r\n .contentDivider {\r\n margin: $size-8 0;\r\n width: 100%;\r\n border-color: var(--color-text);\r\n }\r\n\r\n p {\r\n width: 100%;\r\n }\r\n\r\n img,\r\n video {\r\n width: 100%;\r\n border-radius: $border-radius-large;\r\n margin: $size-2 0 $size-9 0;\r\n @include tile-box-shadow;\r\n\r\n &:has(+ .caption) {\r\n margin-bottom: $size-4;\r\n }\r\n }\r\n\r\n .twoImg {\r\n display: flex;\r\n justify-content: space-evenly;\r\n width: 100%;\r\n\r\n img {\r\n width: fit-content;\r\n max-width: 100%;\r\n max-height: 540px;\r\n }\r\n\r\n @media screen and (max-width: $breakpoint-md) {\r\n flex-direction: column;\r\n align-items: center;\r\n }\r\n }\r\n\r\n video {\r\n outline: none;\r\n opacity: 0;\r\n transition: $transition-video-load;\r\n }\r\n\r\n .caption {\r\n @include body-2;\r\n margin-bottom: $size-9;\r\n font-style: italic;\r\n text-align: center;\r\n }\r\n }\r\n}\r\n",":root {\r\n // Colors\r\n --color-accent: #006f79;\r\n --color-highlight: #4fa9b0;\r\n --color-background: #efefef;\r\n --color-background-light: #f7f7f7;\r\n --color-text: #333333;\r\n --color-text-light: #ffffff;\r\n --color-icon: #808080;\r\n --color-icon-button: #acacac;\r\n --color-icon-button-hover: #808080;\r\n --color-button: #006f79;\r\n --color-button-hover: #4fa9b0;\r\n --color-tile-shadow: rgba(0, 0, 0, 0.1);\r\n\r\n // Font\r\n --font-weight-heading: 700;\r\n --font-weight-heading-small: 700;\r\n --font-weight-body: 400;\r\n}\r\n\r\n[data-theme=\"dark\"] {\r\n // Colors\r\n --color-accent: #378e98;\r\n --color-highlight: #00707a;\r\n --color-background: #191919;\r\n --color-background-light: #272727;\r\n --color-text: #cfcfcf;\r\n --color-text-light: #cfcfcf;\r\n --color-icon: #707070;\r\n --color-icon-button: #525252;\r\n --color-icon-button-hover: #707070;\r\n --color-button: #006f79;\r\n --color-button-hover: #378e98;\r\n --color-tile-shadow: rgba(0, 0, 0, 0.4);\r\n\r\n // Font\r\n --font-weight-heading: 700;\r\n --font-weight-heading-small: 600;\r\n --font-weight-body: 300;\r\n}\r\n","@import \"./styles/theme\";\r\n@import \"./styles/variables\";\r\n\r\nhtml {\r\n font-size: 12px;\r\n overflow: hidden;\r\n height: 100vh;\r\n\r\n @media screen and (min-width: $breakpoint-sm) {\r\n font-size: 14px;\r\n }\r\n @media screen and (min-width: $breakpoint-lg) {\r\n font-size: 16px;\r\n }\r\n}\r\n\r\nh1,\r\nh2,\r\nh3,\r\nh4,\r\np {\r\n padding: 0;\r\n margin: 0;\r\n}\r\n\r\nh1 {\r\n @include heading-1;\r\n color: var(--color-accent);\r\n margin: 0 0 $size-2 0;\r\n}\r\n\r\nh2 {\r\n @include heading-2;\r\n}\r\n\r\nh3 {\r\n @include heading-3;\r\n}\r\n\r\nh4 {\r\n @include heading-4;\r\n margin: $size-2 0 $size-3 0;\r\n}\r\n\r\np {\r\n @include body-1;\r\n margin-bottom: $size-5;\r\n\r\n &.body2 {\r\n @include body-2;\r\n margin-bottom: 0;\r\n }\r\n &.body3 {\r\n @include body-3;\r\n margin-bottom: 0;\r\n }\r\n}\r\n\r\nb {\r\n font-weight: 700;\r\n}\r\n\r\na {\r\n color: var(--color-accent);\r\n &:hover {\r\n color: var(--color-accent);\r\n }\r\n}\r\n\r\n.app {\r\n overflow: hidden;\r\n height: 100vh;\r\n}\r\n\r\n.appContainer,\r\n.modalContainer {\r\n transition: $transition-background-color, color $transition-color;\r\n background-color: var(--color-background);\r\n color: var(--color-text);\r\n\r\n ::selection {\r\n color: var(--color-text-light);\r\n background: var(--color-highlight);\r\n }\r\n}\r\n\r\n.appContainer {\r\n overflow: auto;\r\n height: 100%;\r\n\r\n .themeToggle {\r\n @include icon-button-style;\r\n }\r\n // Overrides for Bootstrap Container\r\n .projectsGrid {\r\n padding: $size-9 $size-8;\r\n min-height: calc(100vh - 4rem); // account for footer height\r\n\r\n @media screen and (min-width: $breakpoint-md) {\r\n padding: $size-10 $size-9;\r\n }\r\n max-width: 90rem;\r\n\r\n .projectsRow {\r\n justify-content: center;\r\n }\r\n .projectColumn {\r\n padding: $size-4;\r\n }\r\n }\r\n\r\n .footer {\r\n width: 100%;\r\n height: 4rem;\r\n text-align: center;\r\n padding: 0 $size-8;\r\n font-style: italic;\r\n }\r\n}\r\n"]} \ No newline at end of file +{"version":3,"sources":["main.3f3d8f60.chunk.css","webpack://src/Header/Header.scss","webpack://src/styles/_sizes.scss","webpack://src/styles/_animations.scss","webpack://src/ProjectTile/ProjectTile.scss","webpack://src/styles/_borderRadius.scss","webpack://src/styles/_variables.scss","webpack://src/ProjectModal/PasswordProtector/PasswordProtector.scss","webpack://src/styles/_typography.scss","webpack://src/ProjectModal/ProjectModal.scss","webpack://src/styles/_theme.scss","webpack://src/App.scss"],"names":[],"mappings":"AAIA,4JAA4J,CCF5J,QACE,iBAAA,CACA,eAAA,CACA,qBAAA,CACA,iBAAA,CAEA,oCANF,QAOI,qBAAA,CACA,iBAAA,CAAA,CAGF,WACE,qBCRK,CDSL,mCETc,CFYhB,mBACE,iBCZK,CDcL,qBACE,oBAAA,CACA,cAAA,CACA,0BAAA,CAAA,uBAAA,CAAA,kBAAA,CACA,uBAAA,CACA,+CAAA,CACA,uBAAA,CAEA,2BACE,6BAAA,CACA,gDAAA,CACA,yBAAA,CAGF,yBACE,UAAA,CACA,aC9BC,CELT,aACE,YAAA,CACA,eAAA,CACA,WAAA,CACA,cAAA,CACA,YAAA,CACA,qBAAA,CACA,iBCTsB,CDUtB,eAAA,CACA,uBAAA,CACA,+CAAA,CELA,sDAAA,CFQA,mBACE,4BAAA,CACA,gDAAA,CAGF,2BACE,UAAA,CACA,WAAA,CACA,qBAAA,CAGF,2BACE,YACA,CG1BJ,8CH0BI,8CFrBK,CKLT,mBACE,eAAA,CACA,qBAAA,CACA,iBAAA,CACA,kBFLoB,CEMpB,kBAAA,CAAA,sDAAA,CAEA,kCAAA,CAEA,cLJO,CKKP,oCAVF,mBAWI,sBAAA,CAAA,CAGF,iCACE,UAAA,CACA,cLZK,CKaL,qBLfK,CKgBL,uBAAA,CAGF,0BACE,yBAAA,CAGF,kCACE,yBAAA,CAAA,sBAAA,CAAA,iBAAA,CACA,oBAAA,CAGF,iCACE,YAAA,CACA,sBAAA,CACA,kBAAA,CACA,oBLlCK,CKoCL,gDCWF,gCA9CU,CA+CV,mCAAA,CAWA,cAAA,CDrBI,WAAA,CACA,cLzCG,CK6CH,YAAA,CACA,mBL5CG,CK6CH,gCAAA,CACA,uBAAA,CACA,WAAA,CAAA,2CAAA,CACA,8CAAA,CAEA,sDACE,sCAAA,CAIJ,iDClDF,qCALW,CAMX,sCAAA,CAmCA,iBAAA,CACA,wBAAA,CFhCA,kBAAA,CACA,qBJPO,CIQP,oCAAA,CACA,cJTO,CIUP,WAAA,CACA,YAAA,CACA,oBAAA,CACA,oCAAA,CACA,6BAAA,CAEA,uDACE,0CAAA,CCuCF,kCACE,YAAA,CACA,kBAAA,CACA,sBAAA,CACA,iBAAA,CAEA,mDACE,WLlEG,CKmEH,UAAA,CACA,kBLtEG,COCT,gBACE,UAAA,CACA,WAAA,CACA,aAAA,CACA,iBAAA,CACA,KAAA,CACA,MAAA,CACA,SAAA,CACA,sBAAA,CAEA,sBACE,0BAAA,CACA,SAAA,CAEF,6BACE,cAAA,CACA,sBNPkB,CMQlB,SAAA,CAEF,qBACE,cAAA,CACA,SAAA,CAEF,4BACE,0BAAA,CACA,uBNfmB,CMgBnB,SAAA,CAGF,gCACE,eAAA,CACA,aAAA,CACA,YAAA,CACA,qBAAA,CACA,kBAAA,CAEA,6CHVF,cAAA,CACA,UJxBO,CIyBP,YJzBO,CI0BP,WAAA,CACA,YAAA,CACA,aJhCO,CIiCP,SAAA,CACA,kBAAA,CACA,4BAAA,CAEA,uDACE,oCAAA,CAGF,iDACE,UAAA,CACA,cJtCK,CIuCL,yBAAA,CACA,8BAAA,CGJA,+CACE,iBAAA,CACA,qBPpCG,COsCH,kDACE,mCNzCU,CM2CZ,kDACE,sCN3Ca,CM+CjB,8CACE,qBPnDG,COoDH,wBAAA,CACA,yBAAA,CAEF,iFAEE,qBAAA,CAGF,0CACE,UAAA,CACA,4CACE,qBP/DC,COgED,8CACE,yBAAA,CAKN,gDACE,eAAA,CACA,UAAA,CACA,8BAAA,CAGF,kCACE,UAAA,CAGF,0EAEE,UAAA,CACA,kBJtFgB,CIuFhB,sBAAA,CHjFJ,sDAAA,CGoFI,wGACE,kBPzFC,CO6FL,wCACE,YAAA,CACA,4BAAA,CACA,UAAA,CAEA,4CACE,yBAAA,CAAA,sBAAA,CAAA,iBAAA,CACA,cAAA,CACA,gBAAA,CAGF,oCAXF,wCAYI,qBAAA,CACA,kBAAA,CAAA,CAIJ,sCACE,YAAA,CACA,SAAA,CACA,2BNrGkB,CMwGpB,yCDtEF,gCA9CU,CA+CV,mCAAA,CAWA,cAAA,CC4DI,qBPjHG,COkHH,iBAAA,CACA,iBAAA,CD3HE,MEEN,sBAAA,CACA,yBAAA,CACA,0BAAA,CACA,gCAAA,CACA,iBAAA,CACA,uBAAA,CACA,iBAAA,CACA,2BAAA,CACA,8BAAA,CACA,sBAAA,CACA,4BAAA,CACA,mCAAA,CAGA,yBAAA,CACA,+BAAA,CACA,sBAAA,CAGF,kBAEE,sBAAA,CACA,yBAAA,CACA,0BAAA,CACA,gCAAA,CACA,oBAAA,CACA,0BAAA,CACA,oBAAA,CACA,2BAAA,CACA,iCAAA,CACA,sBAAA,CACA,4BAAA,CACA,mCAAA,CAGA,yBAAA,CACA,+BAAA,CACA,sBAAA,CPtBF,yBACE,GACE,6BAAA,CAEF,GACE,sBAAA,CAAA,CAIJ,4BACE,GACE,SAAA,CACA,4BAAA,CAEF,IACE,SAAA,CAEF,GACE,SAAA,CACA,sBAAA,CAAA,CQjCJ,KACE,cAAA,CACA,eAAA,CACA,YAAA,CAEA,oCALF,KAMI,cAAA,CAAA,CAEF,oCARF,KASI,cAAA,CAAA,CAIJ,cAKE,SAAA,CACA,QAAA,CAGF,GHlBE,qCALW,CAMX,eAAA,CAAA,sCAAA,CAKA,cAAA,CACA,wBAAA,CACA,kBAAA,CGYA,aAAA,CAAA,yBAAA,CACA,gBAAA,CHXA,oCGQF,GHPI,cAAA,CAAA,CGaJ,GHvBE,sCAAA,CAgBA,iBAAA,CACA,eAAA,CAAA,4CAAA,CGUF,MH5BE,qCAwBA,CGIF,GH3BE,eAAA,CAAA,sCAAA,CAsBA,eAAA,CACA,gBAAA,CGQF,GHHE,eAAA,CGKA,qBAAA,CAGF,KHTE,gCAhCU,CAkCV,iBNjCO,CSwCT,EHME,eAAA,CAAA,mCAAA,CAMA,mBAAA,CGVA,qBT1CO,CS4CP,QHaA,cGXE,CAEF,gBHHA,gCA9CU,CA+CV,eAAA,CAAA,mCAAA,CGAE,eAIA,CAFF,QHcA,gBGZE,CAIJ,EACE,eAAA,CAKA,UACE,aAAA,CAAA,yBAAA,CAIJ,KACE,eAAA,CACA,YAAA,CAGF,8BAEE,mDAAA,CACA,wBAAA,CAAA,wCAAA,CACA,UAAA,CAAA,uBAAA,CAEA,sDACE,UAAA,CAAA,6BAAA,CACA,kBAAA,CAAA,iCAAA,CAIJ,cACE,aAAA,CACA,WAAA,CAEA,2BL9DA,cAAA,CACA,UJxBO,CIyBP,YJzBO,CI0BP,WAAA,CACA,YAAA,CACA,aJhCO,CIiCP,SAAA,CACA,kBAAA,CACA,4BAAA,CAEA,qCACE,UAAA,CAAA,oCAAA,CAGF,+BACE,UAAA,CACA,cJtCK,CIuCL,yBAAA,CACA,aAAA,CAAA,8BAAA,CKgDF,4BACE,sBAAA,CACA,6BAAA,CAKA,eAAA,CAHA,oCAJF,4BAKI,uBAAA,CAAA,CAIF,yCACE,sBAAA,CAEF,2CACE,YTxGG,CS4GP,sBACE,UAAA,CACA,WAAA,CACA,iBAAA,CACA,gBAAA,CACA,iBAAA","file":"main.3f3d8f60.chunk.css","sourcesContent":["@import url(https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap);\n@import url(https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap);\n@import url(https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap);\n@import url(https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap);\n@import url(https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap);\n@keyframes titleEntrance{0%{transform:translate(0, 1.25rem)}100%{transform:translate(0, 0)}}@keyframes subtitleEntrance{0%{opacity:0;transform:translate(0, 0.75rem)}33%{opacity:0}100%{opacity:1;transform:translate(0, 0)}}.header{text-align:center;max-width:47rem;margin:0 auto 3.75rem auto;padding:0 1.25rem}@media screen and (min-width: 768px){.header{margin:0 auto 6.25rem auto;padding:0 6.25rem}}.header h1{margin-bottom:1.75rem;animation:.6s titleEntrance ease-in}.header .linkIcons{margin-top:2.5rem}.header .linkIcons a{display:inline-block;padding:0 1rem;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;transform:translateY(0);transition:transform .2s ease-in,color .2s ease;color:var(--color-icon)}.header .linkIcons a:hover{transform:translateY(-0.25rem);transition:transform .2s ease-out,color .2s ease;color:var(--color-accent)}.header .linkIcons a svg{width:auto;height:2.5rem}\n@keyframes titleEntrance{0%{transform:translate(0, 1.25rem)}100%{transform:translate(0, 0)}}@keyframes subtitleEntrance{0%{opacity:0;transform:translate(0, 0.75rem)}33%{opacity:0}100%{opacity:1;transform:translate(0, 0)}}.projectTile{height:20rem;max-width:40rem;margin:auto;cursor:pointer;display:flex;flex-direction:column;border-radius:8px;overflow:hidden;transform:translateY(0);transition:transform .2s ease-in,box-shadow .2s;box-shadow:0 .25rem .5rem 2px var(--color-tile-shadow)}.projectTile:hover{transform:translateY(-0.5rem);transition:transform .2s ease-out,box-shadow .2s}.projectTile .projectImage{width:100%;height:100%;background-size:cover}.projectTile .projectLabel{padding:1rem;background-color:var(--color-background-light)}\n@keyframes titleEntrance{0%{transform:translate(0, 1.25rem)}100%{transform:translate(0, 0)}}@keyframes subtitleEntrance{0%{opacity:0;transform:translate(0, 0.75rem)}33%{opacity:0}100%{opacity:1;transform:translate(0, 0)}}.passwordContainer{max-width:32rem;margin:6.25rem auto 0 auto;text-align:center;border-radius:12px;line-height:normal;box-shadow:0 .25rem .5rem 2px var(--color-tile-shadow);border:1px solid var(--color-icon);background-color:var(--color-background-light);padding:2.5rem}@media screen and (min-width: 576px){.passwordContainer{padding:2.5rem 3.75rem}}.passwordContainer .passwordIcon{width:auto;height:1.75rem;margin-bottom:1.25rem;color:var(--color-icon)}.passwordContainer .email{text-decoration:underline}.passwordContainer .formContainer{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;margin:2.5rem auto 0 auto}.passwordContainer .passwordForm{display:flex;justify-content:center;align-items:center;margin-bottom:.75rem}.passwordContainer .passwordForm .passwordInput{font-family:\"Merriweather\",serif;font-weight:var(--font-weight-body);font-size:1rem;width:12rem;padding:.25rem;border-top:none;border-right:none;border-left:none;outline:none;margin-right:.75rem;transition:border-color .2s ease;color:var(--color-text);border-bottom:2px solid var(--color-button);background-color:var(--color-background-light)}.passwordContainer .passwordForm .passwordInput:focus{border-color:var(--color-button-hover)}.passwordContainer .passwordForm .passwordButton{font-family:\"Josefin Sans\",sans-serif;font-weight:var(--font-weight-heading);font-size:.875rem;text-transform:uppercase;padding:.5rem 1rem;border-radius:1.75rem;transition:background-color .2s ease;height:1.75rem;border:none;outline:none;text-decoration:none;background-color:var(--color-button);color:var(--color-text-light)}.passwordContainer .passwordForm .passwordButton:hover{background-color:var(--color-button-hover)}.passwordContainer .passwordError{display:flex;align-items:center;justify-content:center;font-style:italic}.passwordContainer .passwordError .passwordWarning{height:1rem;width:auto;margin-right:.5rem}\n@keyframes titleEntrance{0%{transform:translate(0, 1.25rem)}100%{transform:translate(0, 0)}}@keyframes subtitleEntrance{0%{opacity:0;transform:translate(0, 0.75rem)}33%{opacity:0}100%{opacity:1;transform:translate(0, 0)}}.modalContainer{width:100%;height:100%;overflow:auto;position:absolute;top:0;left:0;z-index:1;padding:6.25rem 1.5rem}.modalContainer.enter{transform:translate(0, 50vh);opacity:0}.modalContainer.enter-active{transform:none;transition:.4s ease-in;opacity:1}.modalContainer.exit{transform:none;opacity:1}.modalContainer.exit-active{transform:translate(0, 50vh);transition:.4s ease-out;opacity:0}.modalContainer .projectContent{max-width:60rem;margin:0 auto;display:flex;flex-direction:column;align-items:center}.modalContainer .projectContent .closeButton{position:fixed;top:1.5rem;right:1.5rem;border:none;outline:none;padding:.5rem;z-index:1;border-radius:100%;background-color:transparent}.modalContainer .projectContent .closeButton:hover svg{color:var(--color-icon-button-hover)}.modalContainer .projectContent .closeButton svg{width:auto;height:1.75rem;transition:color .2s ease;color:var(--color-icon-button)}.modalContainer .projectContent .projectHeader{text-align:center;margin-bottom:3.75rem}.modalContainer .projectContent .projectHeader h1{animation:.6s titleEntrance ease-in}.modalContainer .projectContent .projectHeader h3{animation:.6s subtitleEntrance ease-in}.modalContainer .projectContent .sectionTitle{margin-bottom:1.25rem;text-transform:uppercase;color:var(--color-accent)}.modalContainer .projectContent .sectionTitle,.modalContainer .projectContent h4{align-self:flex-start}.modalContainer .projectContent .overview{width:100%}.modalContainer .projectContent .overview p{margin-bottom:1.25rem}.modalContainer .projectContent .overview p b{text-transform:capitalize}.modalContainer .projectContent .contentDivider{margin:2.5rem 0;width:100%;border-color:var(--color-text)}.modalContainer .projectContent p{width:100%}.modalContainer .projectContent img,.modalContainer .projectContent video{width:100%;border-radius:12px;margin:.5rem 0 3.75rem 0;box-shadow:0 .25rem .5rem 2px var(--color-tile-shadow)}.modalContainer .projectContent img:has(+.caption),.modalContainer .projectContent video:has(+.caption){margin-bottom:1rem}.modalContainer .projectContent .twoImg{display:flex;justify-content:space-evenly;width:100%}.modalContainer .projectContent .twoImg img{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:100%;max-height:540px}@media screen and (max-width: 768px){.modalContainer .projectContent .twoImg{flex-direction:column;align-items:center}}.modalContainer .projectContent video{outline:none;opacity:0;transition:opacity .4s ease}.modalContainer .projectContent .caption{font-family:\"Merriweather\",serif;font-weight:var(--font-weight-body);font-size:1rem;margin-bottom:3.75rem;font-style:italic;text-align:center}\n:root{--color-accent: #006f79;--color-highlight: #4fa9b0;--color-background: #efefef;--color-background-light: #f7f7f7;--color-text: #333333;--color-text-light: #ffffff;--color-icon: #808080;--color-icon-button: #acacac;--color-icon-button-hover: #808080;--color-button: #006f79;--color-button-hover: #4fa9b0;--color-tile-shadow: rgba(0, 0, 0, 0.1);--font-weight-heading: 700;--font-weight-heading-small: 700;--font-weight-body: 400}[data-theme=dark]{--color-accent: #378e98;--color-highlight: #00707a;--color-background: #191919;--color-background-light: #272727;--color-text: #cfcfcf;--color-text-light: #cfcfcf;--color-icon: #707070;--color-icon-button: #525252;--color-icon-button-hover: #707070;--color-button: #006f79;--color-button-hover: #378e98;--color-tile-shadow: rgba(0, 0, 0, 0.4);--font-weight-heading: 700;--font-weight-heading-small: 600;--font-weight-body: 300}@keyframes titleEntrance{0%{transform:translate(0, 1.25rem)}100%{transform:translate(0, 0)}}@keyframes subtitleEntrance{0%{opacity:0;transform:translate(0, 0.75rem)}33%{opacity:0}100%{opacity:1;transform:translate(0, 0)}}html{font-size:12px;overflow:hidden;height:100vh}@media screen and (min-width: 576px){html{font-size:14px}}@media screen and (min-width: 992px){html{font-size:16px}}h1,h2,h3,h4,p{padding:0;margin:0}h1{font-family:\"Josefin Sans\",sans-serif;font-weight:700;font-weight:var(--font-weight-heading);font-size:4rem;text-transform:uppercase;letter-spacing:3px;color:#006f79;color:var(--color-accent);margin:0 0 .5rem 0}@media screen and (max-width: 576px){h1{font-size:3rem}}h2{font-family:\"Josefin Sans\",sans-serif;font-weight:700;font-weight:var(--font-weight-heading);font-size:1.75rem;font-weight:700;font-weight:var(--font-weight-heading-small)}h3{font-family:\"Josefin Sans\",sans-serif;font-weight:700;font-weight:var(--font-weight-heading);font-weight:500;font-size:1.5rem}h4{font-family:\"Merriweather\",serif;font-weight:700;font-size:1.25rem;margin:.5rem 0 .75rem 0}p{font-family:\"Merriweather\",serif;font-weight:400;font-weight:var(--font-weight-body);font-size:1.25rem;line-height:1.75rem;margin-bottom:1.25rem}p.body2{font-family:\"Merriweather\",serif;font-weight:400;font-weight:var(--font-weight-body);font-size:1rem;margin-bottom:0}p.body3{font-family:\"Merriweather\",serif;font-weight:400;font-weight:var(--font-weight-body);font-size:.75rem;margin-bottom:0}b{font-weight:700}a{color:#006f79;color:var(--color-accent)}a:hover{color:#006f79;color:var(--color-accent)}.app{overflow:hidden;height:100vh}.appContainer,.modalContainer{transition:background-color .4s ease,color .2s ease;background-color:#efefef;background-color:var(--color-background);color:#333333;color:var(--color-text)}.appContainer ::selection,.modalContainer ::selection{color:#ffffff;color:var(--color-text-light);background:#4fa9b0;background:var(--color-highlight)}.appContainer{overflow:auto;height:100%}.appContainer .themeToggle{position:fixed;top:1.5rem;right:1.5rem;border:none;outline:none;padding:.5rem;z-index:1;border-radius:100%;background-color:transparent}.appContainer .themeToggle:hover svg{color:#808080;color:var(--color-icon-button-hover)}.appContainer .themeToggle svg{width:auto;height:1.75rem;transition:color .2s ease;color:#acacac;color:var(--color-icon-button)}.appContainer .projectsGrid{padding:3.75rem 2.5rem;min-height:calc(100vh - 4rem);max-width:90rem}@media screen and (min-width: 768px){.appContainer .projectsGrid{padding:6.25rem 3.75rem}}.appContainer .projectsGrid .projectsRow{justify-content:center}.appContainer .projectsGrid .projectColumn{padding:1rem}.appContainer .footer{width:100%;height:4rem;text-align:center;padding:0 2.5rem;font-style:italic}\n","@import '../styles/variables';\r\n\r\n.header {\r\n text-align: center;\r\n max-width: 47rem;\r\n margin: 0 auto $size-9 auto;\r\n padding: 0 $size-5;\r\n\r\n @media screen and (min-width: $breakpoint-md) {\r\n margin: 0 auto $size-10 auto;\r\n padding: 0 $size-10;\r\n }\r\n\r\n h1 {\r\n margin-bottom: $size-7;\r\n animation: $animation-title;\r\n }\r\n\r\n .linkIcons {\r\n margin-top: $size-8;\r\n\r\n a {\r\n display: inline-block;\r\n padding: 0 $size-4;\r\n height: fit-content;\r\n transform: translateY(0);\r\n transition: $transition-hover-up, color $transition-color;\r\n color: var(--color-icon);\r\n\r\n &:hover {\r\n transform: translateY(-$size-1);\r\n transition: $transition-hover-down, color $transition-color;\r\n color: var(--color-accent);\r\n }\r\n\r\n svg {\r\n width: auto;\r\n height: $size-8;\r\n }\r\n }\r\n }\r\n}\r\n","$size-1: 0.25rem;\r\n$size-2: 0.5rem;\r\n$size-3: 0.75rem;\r\n$size-4: 1rem;\r\n$size-5: 1.25rem;\r\n$size-6: 1.5rem;\r\n$size-7: 1.75rem;\r\n$size-8: 2.5rem;\r\n$size-9: 3.75rem;\r\n$size-10: 6.25rem;\r\n","@import \"sizes\";\r\n\r\n$animation-duration-1: 0.2s;\r\n$animation-duration-2: 0.4s;\r\n$animation-duration-3: 0.6s;\r\n\r\n$animation-title: $animation-duration-3 titleEntrance ease-in;\r\n$animation-subTitle: $animation-duration-3 subtitleEntrance ease-in;\r\n\r\n$transition-hover-up: transform $animation-duration-1 ease-in;\r\n$transition-hover-down: transform $animation-duration-1 ease-out;\r\n$transition-modal-in: $animation-duration-2 ease-in;\r\n$transition-modal-out: $animation-duration-2 ease-out;\r\n$transition-color: $animation-duration-1 ease;\r\n$transition-background-color: background-color $animation-duration-2 ease;\r\n$transition-video-load: opacity $animation-duration-2 ease;\r\n\r\n@keyframes titleEntrance {\r\n 0% {\r\n transform: translate(0, $size-5);\r\n }\r\n 100% {\r\n transform: translate(0, 0);\r\n }\r\n}\r\n\r\n@keyframes subtitleEntrance {\r\n 0% {\r\n opacity: 0;\r\n transform: translate(0, $size-3);\r\n }\r\n 33% {\r\n opacity: 0;\r\n }\r\n 100% {\r\n opacity: 1;\r\n transform: translate(0, 0);\r\n }\r\n}\r\n","@import \"../styles/variables\";\r\n\r\n.projectTile {\r\n height: 20rem;\r\n max-width: 40rem;\r\n margin: auto;\r\n cursor: pointer;\r\n display: flex;\r\n flex-direction: column;\r\n border-radius: $border-radius-default;\r\n overflow: hidden;\r\n transform: translateY(0);\r\n transition: $transition-hover-up, box-shadow $animation-duration-1;\r\n @include tile-box-shadow;\r\n\r\n &:hover {\r\n transform: translateY(-$size-2);\r\n transition: $transition-hover-down, box-shadow $animation-duration-1;\r\n }\r\n\r\n .projectImage {\r\n width: 100%;\r\n height: 100%;\r\n background-size: cover;\r\n }\r\n\r\n .projectLabel {\r\n padding: $size-4;\r\n background-color: var(--color-background-light);\r\n }\r\n}\r\n","$border-radius-default: 8px;\r\n$border-radius-large: 12px;\r\n","@import \"animations\";\r\n@import \"borderRadius\";\r\n@import \"breakpoints\";\r\n@import \"sizes\";\r\n@import \"typography\";\r\n\r\n@mixin tile-box-shadow {\r\n box-shadow: 0 $size-1 $size-2 2px var(--color-tile-shadow);\r\n}\r\n\r\n@mixin button-style {\r\n @include button-text;\r\n padding: $size-2 $size-4;\r\n border-radius: $size-7;\r\n transition: background-color $transition-color;\r\n height: $size-7; // Fixes text vertical alignment issue\r\n border: none;\r\n outline: none;\r\n text-decoration: none;\r\n background-color: var(--color-button);\r\n color: var(--color-text-light);\r\n\r\n &:hover {\r\n background-color: var(--color-button-hover);\r\n }\r\n}\r\n\r\n@mixin icon-button-style {\r\n position: fixed;\r\n top: $size-6;\r\n right: $size-6;\r\n border: none;\r\n outline: none;\r\n padding: $size-2;\r\n z-index: 1;\r\n border-radius: 100%;\r\n background-color: transparent;\r\n\r\n &:hover svg {\r\n color: var(--color-icon-button-hover);\r\n }\r\n\r\n svg {\r\n width: auto;\r\n height: $size-7;\r\n transition: color $transition-color;\r\n color: var(--color-icon-button);\r\n }\r\n}\r\n","@import \"../../styles/variables\";\r\n\r\n.passwordContainer {\r\n max-width: 32rem;\r\n margin: $size-10 auto 0 auto;\r\n text-align: center;\r\n border-radius: $border-radius-large;\r\n line-height: normal;\r\n @include tile-box-shadow;\r\n border: 1px solid var(--color-icon);\r\n background-color: var(--color-background-light);\r\n padding: $size-8;\r\n @media screen and (min-width: $breakpoint-sm) {\r\n padding: $size-8 $size-9;\r\n }\r\n\r\n .passwordIcon {\r\n width: auto;\r\n height: $size-7;\r\n margin-bottom: $size-5;\r\n color: var(--color-icon);\r\n }\r\n\r\n .email {\r\n text-decoration: underline;\r\n }\r\n\r\n .formContainer {\r\n width: fit-content;\r\n margin: $size-8 auto 0 auto;\r\n }\r\n\r\n .passwordForm {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n margin-bottom: $size-3;\r\n\r\n .passwordInput {\r\n @include body-2;\r\n width: 12rem;\r\n padding: $size-1;\r\n border-top: none;\r\n border-right: none;\r\n border-left: none;\r\n outline: none;\r\n margin-right: $size-3;\r\n transition: border-color $transition-color;\r\n color: var(--color-text);\r\n border-bottom: 2px solid var(--color-button);\r\n background-color: var(--color-background-light);\r\n\r\n &:focus {\r\n border-color: var(--color-button-hover);\r\n }\r\n }\r\n\r\n .passwordButton {\r\n @include button-style;\r\n }\r\n }\r\n\r\n .passwordError {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-style: italic;\r\n\r\n .passwordWarning {\r\n height: $size-4;\r\n width: auto;\r\n margin-right: $size-2;\r\n }\r\n }\r\n}\r\n","@import url(\"https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@500;600;700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400&display=swap\");\r\n\r\n$font-title: \"Josefin Sans\", sans-serif;\r\n$font-text: \"Merriweather\", serif;\r\n\r\n// Headings\r\n@mixin heading {\r\n font-family: $font-title;\r\n font-weight: var(--font-weight-heading);\r\n}\r\n\r\n@mixin heading-1 {\r\n @include heading;\r\n font-size: 4rem;\r\n text-transform: uppercase;\r\n letter-spacing: 3px;\r\n\r\n @media screen and (max-width: $breakpoint-sm) {\r\n font-size: 3rem;\r\n }\r\n}\r\n\r\n@mixin heading-2 {\r\n @include heading;\r\n font-size: 1.75rem;\r\n font-weight: var(--font-weight-heading-small);\r\n}\r\n\r\n@mixin heading-3 {\r\n @include heading;\r\n font-weight: 500;\r\n font-size: 1.5rem;\r\n}\r\n\r\n@mixin heading-4 {\r\n font-family: $font-text;\r\n font-weight: 700;\r\n font-size: 1.25rem;\r\n}\r\n\r\n// Button\r\n@mixin button-text {\r\n @include heading;\r\n font-size: 0.875rem;\r\n text-transform: uppercase;\r\n}\r\n\r\n// Body\r\n@mixin body {\r\n font-family: $font-text;\r\n font-weight: var(--font-weight-body);\r\n}\r\n\r\n@mixin body-1 {\r\n @include body;\r\n font-size: 1.25rem;\r\n line-height: 1.75rem;\r\n}\r\n\r\n@mixin body-2 {\r\n @include body;\r\n font-size: 1rem;\r\n}\r\n\r\n@mixin body-3 {\r\n @include body;\r\n font-size: 0.75rem;\r\n}\r\n","@import \"../styles/variables\";\r\n\r\n.modalContainer {\r\n width: 100%;\r\n height: 100%;\r\n overflow: auto;\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n z-index: 1;\r\n padding: $size-10 $size-6;\r\n\r\n &.enter {\r\n transform: translate(0, 50vh);\r\n opacity: 0;\r\n }\r\n &.enter-active {\r\n transform: none;\r\n transition: $transition-modal-in;\r\n opacity: 1;\r\n }\r\n &.exit {\r\n transform: none;\r\n opacity: 1;\r\n }\r\n &.exit-active {\r\n transform: translate(0, 50vh);\r\n transition: $transition-modal-out;\r\n opacity: 0;\r\n }\r\n\r\n .projectContent {\r\n max-width: 60rem;\r\n margin: 0 auto;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n\r\n .closeButton {\r\n @include icon-button-style;\r\n }\r\n\r\n .projectHeader {\r\n text-align: center;\r\n margin-bottom: $size-9;\r\n\r\n h1 {\r\n animation: $animation-title;\r\n }\r\n h3 {\r\n animation: $animation-subTitle;\r\n }\r\n }\r\n\r\n .sectionTitle {\r\n margin-bottom: $size-5;\r\n text-transform: uppercase;\r\n color: var(--color-accent);\r\n }\r\n .sectionTitle,\r\n h4 {\r\n align-self: flex-start;\r\n }\r\n\r\n .overview {\r\n width: 100%;\r\n p {\r\n margin-bottom: $size-5;\r\n b {\r\n text-transform: capitalize;\r\n }\r\n }\r\n }\r\n\r\n .contentDivider {\r\n margin: $size-8 0;\r\n width: 100%;\r\n border-color: var(--color-text);\r\n }\r\n\r\n p {\r\n width: 100%;\r\n }\r\n\r\n img,\r\n video {\r\n width: 100%;\r\n border-radius: $border-radius-large;\r\n margin: $size-2 0 $size-9 0;\r\n @include tile-box-shadow;\r\n\r\n &:has(+ .caption) {\r\n margin-bottom: $size-4;\r\n }\r\n }\r\n\r\n .twoImg {\r\n display: flex;\r\n justify-content: space-evenly;\r\n width: 100%;\r\n\r\n img {\r\n width: fit-content;\r\n max-width: 100%;\r\n max-height: 540px;\r\n }\r\n\r\n @media screen and (max-width: $breakpoint-md) {\r\n flex-direction: column;\r\n align-items: center;\r\n }\r\n }\r\n\r\n video {\r\n outline: none;\r\n opacity: 0;\r\n transition: $transition-video-load;\r\n }\r\n\r\n .caption {\r\n @include body-2;\r\n margin-bottom: $size-9;\r\n font-style: italic;\r\n text-align: center;\r\n }\r\n }\r\n}\r\n",":root {\r\n // Colors\r\n --color-accent: #006f79;\r\n --color-highlight: #4fa9b0;\r\n --color-background: #efefef;\r\n --color-background-light: #f7f7f7;\r\n --color-text: #333333;\r\n --color-text-light: #ffffff;\r\n --color-icon: #808080;\r\n --color-icon-button: #acacac;\r\n --color-icon-button-hover: #808080;\r\n --color-button: #006f79;\r\n --color-button-hover: #4fa9b0;\r\n --color-tile-shadow: rgba(0, 0, 0, 0.1);\r\n\r\n // Font\r\n --font-weight-heading: 700;\r\n --font-weight-heading-small: 700;\r\n --font-weight-body: 400;\r\n}\r\n\r\n[data-theme=\"dark\"] {\r\n // Colors\r\n --color-accent: #378e98;\r\n --color-highlight: #00707a;\r\n --color-background: #191919;\r\n --color-background-light: #272727;\r\n --color-text: #cfcfcf;\r\n --color-text-light: #cfcfcf;\r\n --color-icon: #707070;\r\n --color-icon-button: #525252;\r\n --color-icon-button-hover: #707070;\r\n --color-button: #006f79;\r\n --color-button-hover: #378e98;\r\n --color-tile-shadow: rgba(0, 0, 0, 0.4);\r\n\r\n // Font\r\n --font-weight-heading: 700;\r\n --font-weight-heading-small: 600;\r\n --font-weight-body: 300;\r\n}\r\n","@import \"./styles/theme\";\r\n@import \"./styles/variables\";\r\n\r\nhtml {\r\n font-size: 12px;\r\n overflow: hidden;\r\n height: 100vh;\r\n\r\n @media screen and (min-width: $breakpoint-sm) {\r\n font-size: 14px;\r\n }\r\n @media screen and (min-width: $breakpoint-lg) {\r\n font-size: 16px;\r\n }\r\n}\r\n\r\nh1,\r\nh2,\r\nh3,\r\nh4,\r\np {\r\n padding: 0;\r\n margin: 0;\r\n}\r\n\r\nh1 {\r\n @include heading-1;\r\n color: var(--color-accent);\r\n margin: 0 0 $size-2 0;\r\n}\r\n\r\nh2 {\r\n @include heading-2;\r\n}\r\n\r\nh3 {\r\n @include heading-3;\r\n}\r\n\r\nh4 {\r\n @include heading-4;\r\n margin: $size-2 0 $size-3 0;\r\n}\r\n\r\np {\r\n @include body-1;\r\n margin-bottom: $size-5;\r\n\r\n &.body2 {\r\n @include body-2;\r\n margin-bottom: 0;\r\n }\r\n &.body3 {\r\n @include body-3;\r\n margin-bottom: 0;\r\n }\r\n}\r\n\r\nb {\r\n font-weight: 700;\r\n}\r\n\r\na {\r\n color: var(--color-accent);\r\n &:hover {\r\n color: var(--color-accent);\r\n }\r\n}\r\n\r\n.app {\r\n overflow: hidden;\r\n height: 100vh;\r\n}\r\n\r\n.appContainer,\r\n.modalContainer {\r\n transition: $transition-background-color, color $transition-color;\r\n background-color: var(--color-background);\r\n color: var(--color-text);\r\n\r\n ::selection {\r\n color: var(--color-text-light);\r\n background: var(--color-highlight);\r\n }\r\n}\r\n\r\n.appContainer {\r\n overflow: auto;\r\n height: 100%;\r\n\r\n .themeToggle {\r\n @include icon-button-style;\r\n }\r\n // Overrides for Bootstrap Container\r\n .projectsGrid {\r\n padding: $size-9 $size-8;\r\n min-height: calc(100vh - 4rem); // account for footer height\r\n\r\n @media screen and (min-width: $breakpoint-md) {\r\n padding: $size-10 $size-9;\r\n }\r\n max-width: 90rem;\r\n\r\n .projectsRow {\r\n justify-content: center;\r\n }\r\n .projectColumn {\r\n padding: $size-4;\r\n }\r\n }\r\n\r\n .footer {\r\n width: 100%;\r\n height: 4rem;\r\n text-align: center;\r\n padding: 0 $size-8;\r\n font-style: italic;\r\n }\r\n}\r\n"]} \ No newline at end of file diff --git a/static/js/main.29b65be5.chunk.js b/static/js/main.29b65be5.chunk.js new file mode 100644 index 0000000..f87d2a8 --- /dev/null +++ b/static/js/main.29b65be5.chunk.js @@ -0,0 +1,2 @@ +(this["webpackJsonpalayna-portfolio"]=this["webpackJsonpalayna-portfolio"]||[]).push([[0],{20:function(e,t,o){},21:function(e,t,o){},22:function(e,t,o){},23:function(e,t,o){},27:function(e,t,o){},28:function(e,t,o){"use strict";o.r(t);var n=o(0),s=o(1),a=o.n(s),i=o(8),r=o.n(i),c=o(5),l=o(12),d=o(10),h=o(13),p=o(2);o(20);var u=function(){return Object(n.jsxs)("header",{className:"header",children:[Object(n.jsx)("h1",{children:"Hi, I'm Alayna"}),Object(n.jsx)("p",{children:"I'm a Senior Design Engineer with several years of experience in web-based prototyping and creating design tools at NBC, Roku, and Intuit."}),Object(n.jsxs)("div",{className:"linkIcons",children:[Object(n.jsx)("a",{href:"https://www.linkedin.com/in/atruttmann/","aria-label":"LinkedIn",title:"LinkedIn",children:Object(n.jsx)(p.e,{})}),Object(n.jsx)("a",{href:"download/Resume-Alayna-Truttmann.pdf","aria-label":"Resume",title:"Resume",children:Object(n.jsx)(p.d,{})}),Object(n.jsx)("a",{href:"mailto:amtruttmann@gmail.com","aria-label":"Email",title:"Email",children:Object(n.jsx)(p.a,{})}),Object(n.jsx)("a",{href:"https://github.com/atruttmann","aria-label":"GitHub",title:"GitHub",children:Object(n.jsx)(p.c,{})})]})]})},g=(o(21),function(e){var t,o=e.project,s=e.setSelectedProject,a=void 0===s?function(){}:s,i=e.setModalOpen,r=void 0===i?function(){}:i;return Object(n.jsxs)("div",{className:"projectTile",onClick:function(e){a(o),r(!0),e.stopPropagation()},children:[Object(n.jsx)("div",{className:"projectImage",style:{backgroundImage:"url(".concat(o.coverImageSrc,")"),backgroundPosition:null!==(t=o.coverPosition)&&void 0!==t?t:"center"},alt:"Cover for ".concat(o.title)}),Object(n.jsxs)("div",{className:"projectLabel",children:[Object(n.jsx)("h2",{children:o.title}),Object(n.jsx)("p",{className:"body2",children:o.subTitle})]})]})}),m=function(e){return"".concat("","images/").concat(e,"/")},b=m("IEPShell"),j={title:"Intuit Expert Portal",subTitle:"Proof of concept for design updates",coverImageSrc:"".concat(b,"/1.png"),coverPosition:"top left",passwordRequired:!1,overview:{problem:"The portal Intuit customer service agents use needed updates to better align with the Intuit Design System and make usability improvements.",goal:"Create a React prototype with these design updates to be passed off to production engineers.",outcome:"Production engineers were able to reuse my code, which accelerated their release process.",role:"I was the sole developer for this project and partnered with a product design team to create this prototype.",technologies:"React, Styled Components",dates:"November - December 2020"},links:[],content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"For this project, I worked with the Intuit Export Portal design team. This portal is used by Intuit customer service agents (a.k.a. Intuit Experts) to assist customers. In this portal Experts can see customer data while on a call and also manage personal work information such as their schedule and notes."}),Object(n.jsx)("p",{children:'The focus for this prototype was updating the "shell" of the product - the left, top, and right navigation elements. The design team wanted to refresh the components and visual design to align with Intuit Design Systems. They also rearranged some of the navigation content based on feedback from their users. The inner content was not finalized for this phase of prototyping so a responsive column layout was used as a placeholder.'}),Object(n.jsx)("img",{src:"".concat(b,"1.png"),alt:"The Intuit Export Portal Shell"}),Object(n.jsx)("p",{children:'The functionality required for this prototype was to be able to click through the left navigation items and open and close the right drawer. The "Engagements" screen needed to show a header and tabs with client information. Navigation elements also had to behave responsively on smaller screens.'}),Object(n.jsx)("video",{controls:!0,muted:!0,preload:"none",children:Object(n.jsx)("source",{src:"".concat(b,"Demo.mp4"),type:"video/mp4"})}),Object(n.jsx)("p",{className:"caption",children:"This video shows the entire flow of the prototype."}),Object(n.jsx)("p",{children:"The most challenging development aspect of this project was the responsive design of the top navigation. This header included dropdowns, links, and other information that needed to be accessible on smaller screens. These navigation items needed to collapse into an overflow menu."}),Object(n.jsx)("img",{src:"".concat(b,"2.png"),alt:"Milestone dropdown when header is not in overflow."}),Object(n.jsx)("p",{className:"caption",children:"Opening the milestone dropdown when the header is not in an overflow state."}),Object(n.jsx)("img",{src:"".concat(b,"3.png"),alt:"Milestone dropdown when header is overflowing."}),Object(n.jsx)("p",{className:"caption",children:"Accessing the milestone dropdown in an overflow state."}),Object(n.jsx)("p",{children:"Another responsive aspect of this screen was the overflow behavior for tabs. Tabs needed an arrow to show that there were more tabs hidden. When clicked, this arrow needed to scroll the tabs by a set pixel value."}),Object(n.jsx)("img",{src:"".concat(b,"4.png"),alt:"Tabs in an overflow state"}),Object(n.jsx)("p",{children:"After this prototype had been finalized with the design team, I handed off the prototype and the code to the development team. They were able to reuse my work in their production code, which sped up the process to implement these changes."})]})},f=m("Pyro"),w={title:"Pyro",subTitle:"Prototyping tool for Intuit designers",coverImageSrc:"".concat(f,"/1.png"),coverPosition:"center",passwordRequired:!1,overview:{problem:"Intuit designers need a way to quickly create high-fidelity prototypes with interactive components and real user data.",goal:"Develop an intuitive drag-and-drop interface that leverages Intuit Design System components and supports custom data and logic.",outcome:"Pyro 1.0 was released in November 2020. I continued to maintain the tool and release feature updates.",role:"I worked on a team of five Design Technologists. I wore many hats including developing the product, designing new features, leading user testing sessions, and prioritizing our Jira board.",technologies:"React, Firebase",dates:"February 2020 - April 2022"},links:[],content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"Pyro is a custom prototyping tool built by Intuit Design Technologists for Intuit designers. It allows anyone to create prototypes using Intuit Design System components, user data, and logic without writing any code. I have been working on this project since February 2020 improving the editor and creating features that cater to QuickBooks design needs."}),Object(n.jsx)("video",{controls:!0,preload:"none",children:Object(n.jsx)("source",{src:"".concat(f,"Demo.mp4"),type:"video/mp4"})}),Object(n.jsx)("p",{className:"caption",children:"This is the demo video for the initial release of Pyro. Video editing credits go to my awesome colleagues Heather & Lynda."}),Object(n.jsxs)("p",{children:["Pyro leverages ",Object(n.jsx)("a",{href:"https://craft.js.org/",children:"Craft.js"})," with React to create drag and drop functionality in the editor. The prototype data syncs to a ",Object(n.jsx)("a",{href:"https://firebase.google.com/",children:"Firebase"})," backend. Users can grab components from the left-side panel and drag them into the editor. When a component is selected, you can edit its properties in the right-side panel. These components are either custom components built for Pyro or they are imported from Intuit's design system."]}),Object(n.jsx)("img",{src:"".concat(f,"2.png"),alt:"Pyro editor"}),Object(n.jsx)("p",{children:'In addition to changing the style of components, you can also set an "on click" action for the component or conditionally show or hide it. This allows users to build complex prototypes with many pages and branching flows. This feature is particularly important for TurboTax designers who often need to create flows with a series of questions.'}),Object(n.jsx)("img",{src:"".concat(f,"3.png"),alt:"Close up of component editing"}),Object(n.jsx)("p",{children:"Many QuickBooks designers need to incorporate real user data into customer testing sessions to help the customer feel like the prototype is real. Customer data often comes in the form of a list of transactions, and typically a Design Technologist would build a custom React prototype to display this data. We added an editable table component to Pyro that allows designers to upload user data as a CSV, saving us all time!"}),Object(n.jsx)("img",{src:"".concat(f,"4.png"),alt:"Table component"}),Object(n.jsx)("p",{children:"Once Pyro was close to being ready for release, a teammate and I conducted ten user testing sessions with Intuit designers. We wanted to learn if there were any major usability issues blocking the release and get feedback on what features should be added to Pyro. The reaction from our participants was very positive and they were excited to use Pyro"}),Object(n.jsxs)("p",{children:["The main issues that came out of testing were:",Object(n.jsx)("br",{}),"1. The onboarding flow was too long and there was more information than users could process.",Object(n.jsx)("br",{}),"2. Users expected to be able to undo and redo changes. (At the time of testing, this feature was still in development)",Object(n.jsx)("br",{}),"3. Adding a new page to the prototype was not intuitive."]}),Object(n.jsx)("img",{src:"".concat(f,"5.png"),alt:"Testing results"}),Object(n.jsx)("p",{children:"The majority of the issues from user testing were addressed and Pyro released to Intuit designers in November 2020."})]})},y=m("Toolkit"),x={title:"QB Designer Toolkit",subTitle:"Figma plugin for Intuit designers",coverImageSrc:"".concat(y,"/Cover.png"),coverPosition:"center",passwordRequired:!1,overview:{problem:"How can we leverage Figma plugins to improve Intuit designers' workflows?",goal:"Create a Figma plugin that empowers designers to easily add motion, content, and theming to their work.",outcome:"The first version of the plugin was released in April 2021, and over time gained more than 238 users. I continued to maintain the plugin and release feature updates.",role:"I was the lead for this tool. I maintained the design and code of this plugin and promoted it to our users.",technologies:"Figma Plugin API, TypeScript, React",dates:"February 2021 - April 2022"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("h2",{class:"sectionTitle",children:"Version 1"}),Object(n.jsxs)("p",{children:["During the fall of 2020, Intuit designers made the switch to using"," ",Object(n.jsx)("a",{href:"https://www.figma.com/",children:"Figma"})," as their primary design tool. Figma supports adding"," ",Object(n.jsx)("a",{href:"https://www.figma.com/community/plugins?tab=plugins",children:"plugins"}),", which are apps you can install to add functionality and improve your workflow. After experimenting with Figma plugins in a hackathon, I was eager to develop the first Figma plugin for Intuit designers."]}),Object(n.jsx)("p",{children:"A design hurdle I wanted to tackle was supporting theming, particularly dark mode. Dark mode has been a work in progress for Intuit design for some time and is currently an experimental beta setting for QuickBooks. Dark mode is a priority because it is a feature our users expect, and also has accessibility benefits such as better contrast and reduced eye strain. As this feature becomes more used, we need to make sure that designs will work for both light and dark modes."}),Object(n.jsx)("img",{src:"".concat(y,"1.gif"),alt:"Dark mode demo"}),Object(n.jsx)("p",{children:"I created a simple UI that would allow users to toggle both layers and pages between light and dark mode. I also included a color inspector that would display the fill and border colors for a selected layer and show their light and dark mode pairings."}),Object(n.jsx)("img",{src:"".concat(y,"2.png"),alt:"Plugin interface"}),Object(n.jsx)("p",{className:"caption",children:"Inspecting a dark mode design to see the color pairings."}),Object(n.jsxs)("p",{children:["Once I had a solid design, I moved on to developing the functionality using ",Object(n.jsx)("a",{href:"https://www.typescriptlang.org/",children:"TypeScript"})," and"," ",Object(n.jsx)("a",{href:"https://sass-lang.com/",children:"Sass"}),". The plugin analyzes a layer's fill and border colors, finding the appropriate contextual color pairing, and then changing the layer's colors to the new theme.This automatic process is completed in a matter of seconds, which saves designers hours of work in manually changing colors."]}),Object(n.jsx)("video",{controls:!0,preload:"none",children:Object(n.jsx)("source",{src:"".concat(y,"Demo.mp4"),type:"video/mp4"})}),Object(n.jsx)("p",{className:"caption",children:"This quick demo of Dark Mode that appeared within a QB Designer Toolkit instructional video I created."}),Object(n.jsx)("p",{children:"Design Technologists on my team also created two other plugins that focus on motion and content. We combined all three plugins to make it easy for designers to access all of our tools at once. I led the merge effort and refactored our code to use the same visual style and coding standards."}),Object(n.jsx)("p",{children:"Version 1 of this plugin was released to the QuickBooks design community in April 2021. The plugin now has now been installed by 238 users, roughly 2/3 of our total designers."}),Object(n.jsx)("hr",{className:"contentDivider"}),Object(n.jsx)("h2",{class:"sectionTitle",children:"Version 2"}),Object(n.jsx)("p",{children:"After the successful launch of the plugin, I paused development for several months to collect analytics and user feedback. In December 2021, I created a plan to add more functionality to the plugin. I wanted to target three key areas:"}),Object(n.jsx)("h4",{children:"Opportunity #1: Refresh the plugin design"}),Object(n.jsx)("p",{children:"The first version of this plugin had features that were designed by separate teams and lacked a common visual language. The design of the plugin did not support multiple modes of navigation. It was likely that we would need the flexibility to develop more complex UIs in the future."}),Object(n.jsx)("h4",{children:"Opportunity #2: Contextualize analytics data"}),Object(n.jsx)("p",{children:"Adding analytics to a Figma plugin can be a bit of a challenge. Since it is contained within an iFrame, there is no access to certain information most analytics tools need. For the first launch, I developed a simple click counting system for the buttons in the plugin. While this did help me understand which buttons were being used the most, it lacked the contextual data. Which user clicked, and in what file, and at what time? I needed this information to make data-driven decisions about the future of the plugin."}),Object(n.jsx)("h4",{children:"Opportunity #3: Add a requested feature"}),Object(n.jsx)("p",{children:"Currently, the most used feature within the plugin is the content generator. My team did user research to get feedback on this feature and found that a common ask from designers was a way to generate numbers."}),Object(n.jsx)("h4",{children:"Tackling opportunities"}),Object(n.jsxs)("p",{children:["I started by redesigning the plugin to create a common visual language. I chose to use"," ",Object(n.jsx)("a",{href:"https://www.figma.com/community/file/928108847914589057",children:"UI2, Figma's Design System"})," ","as the basis for my design. Using Figma's components and styles helps the plugin blend into Figma's UI and seem like a more natural extension of its capabilities. I also changed the plugin navigation system to include a flyout menu. Moving page navigation into the flyout menu gave each feature room for its own navigational elements."]}),Object(n.jsx)("img",{src:"".concat(y,"/Redesign.png"),alt:"Redesign before and after"}),Object(n.jsx)("p",{className:"caption",children:"Selection of redesigned screens before (left) and after (right)"}),Object(n.jsx)("img",{src:"".concat(y,"/Cover.png"),alt:"Cover art"}),Object(n.jsx)("p",{className:"caption",children:"Redesigned cover art for installation page"}),Object(n.jsxs)("p",{children:["My next step was to add analytics. I chose to use"," ",Object(n.jsx)("a",{href:"https://mixpanel.com/",children:"Mixpanel"})," because of its powerful capabilities and compatibility with Figma plugins. Now, when a user clicks a button I know their name, the file they are using, and their overall activity. I can track monthly active users, view a list of Figma files the plugin is being used in, and see which buttons are clicked the most. This will help me know which features of the plugin are most valuable and should be invested in. I now know who the top users of the plugin are and can ask them for feedback."]}),Object(n.jsx)("img",{src:"".concat(y,"/Mixpanel.png"),alt:"Mixpanel Analytics Dashboard"}),Object(n.jsxs)("p",{className:"caption",children:[Object(n.jsx)("a",{href:"https://mixpanel.com/public/7veU4Lv7JycMp3Ene9z4hu",children:"Mixpanel Analytics dashboard"})," ","with three weeks of data"]}),Object(n.jsx)("p",{children:"Next, I worked on the random number generation feature. QuickBooks designers often create data tables with transactional information such as dates, percentages, and currency values so I wanted to include all of these options in this feature. This feature has the flexibility to add one number or a range of numbers, with options to sort the range. This will reduce the work a designer has to do filling out a table from minutes to seconds."}),Object(n.jsx)("img",{src:"".concat(y,"/Number.png"),alt:"Random number generator"}),Object(n.jsx)("p",{className:"caption",children:"The UI allows users to customize the format of numbers, currencies, and dates"}),Object(n.jsx)("p",{children:"Version 2 was released in January 2022, and the plugin continues to be widely used amongst Intuit designers."})]})},v=m("Flow"),O={title:"Flow",subTitle:"Prototyping tool for web & TV",coverImageSrc:"".concat(v,"/1.png"),coverPosition:"center",passwordRequired:!1,overview:{problem:"Roku designers need a way to easily view their prototypes on TVs for user testing.",goal:"Create an easy to use web platform that exports prototypes to a Roku TV channel.",outcome:"The fist version of Flow was released in August 2022. I continued to maintain the tool and release feature updates.",role:"I was the sole designer and full stack engineer on this project.",technologies:"React, Node.js, Express, AWS Dynamo DB, AWS S3",dates:"April 2022 - September 2023"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"Prototyping is at the heart of the Roku UX Engineering team's focus, but we don't always have time to make every prototype designers request. Creating an internal prototyping tool allows designers to self-serve simple prototypes, freeing up UX Engineers to code complex experiences. Flow empowers designers to create and view their prototypes on a TV and share them with in-person or remote users for testing."}),Object(n.jsx)("p",{children:"The first step I took to make Flow was to plan out the user experience. The experience is split between creating a prototype on the web and viewing a prototype on a Roku channel."}),Object(n.jsx)("img",{src:"".concat(v,"2.png"),alt:"User experience diagram"}),Object(n.jsxs)("p",{children:["I started designing the experience using Roku's web design system components. I iterated on the design using feedback gathered from my team. The primary web views are:",Object(n.jsxs)("ol",{children:[Object(n.jsx)("li",{children:"Login"}),Object(n.jsx)("li",{children:"View all prototypes"}),Object(n.jsx)("li",{children:"Edit prototype"}),Object(n.jsx)("li",{children:"Preview prototype"})]})]}),Object(n.jsx)("img",{src:"".concat(v,"3.png"),alt:"Flow home page"}),Object(n.jsx)("p",{className:"caption",children:"View all of your prototypes on the Flow home page"}),Object(n.jsxs)("p",{children:["I started developing the website first. I used React to build the UI and a Node.js to send data to Dynamo DB and store images in a S3 bucket. I leveraged the open source"," ",Object(n.jsx)("a",{href:"https://reactflow.dev/",children:"React Flow"})," library as the editor for my interactive diagrams."]}),Object(n.jsx)("img",{src:"".concat(v,"4.png"),alt:"Flow editor"}),Object(n.jsx)("p",{className:"caption",children:"Editing a prototype"}),Object(n.jsx)("p",{children:"I built a web preview so users can try out the prototype on the web and fix issues before viewing on the TV."}),Object(n.jsx)("img",{src:"".concat(v,"5.png"),alt:"Prototype preview"}),Object(n.jsx)("p",{className:"caption",children:"Previewing a prototype"}),Object(n.jsxs)("p",{children:["The next phase of my development work was creating the Roku channel. I used an internal technology that functions like to React but works on TV channels. It is similar to the externally available"," ",Object(n.jsx)("a",{href:"https://developer.roku.com/develop",children:"Roku SDK"}),"."]}),Object(n.jsx)("img",{src:"".concat(v,"6.png"),alt:"Roku channel home view"}),Object(n.jsx)("p",{className:"caption",children:"Entering a prototype code on the installed channel"}),Object(n.jsx)("p",{children:"Flow 1.0 was released in August 2022. I created a demo video to introduce users to Flow."}),Object(n.jsx)("video",{controls:!0,preload:"none",poster:"".concat(v,"1.png"),children:Object(n.jsx)("source",{src:"".concat(v,"Demo.mp4"),type:"video/mp4"})}),Object(n.jsx)("p",{children:"As more designers have used the tool I added new features based on their use cases. These features include fade transitions, long-pressing remote buttons, allowing videos, screen reader support, and more. Eventually, I wanted to make a Figma plugin that can export images into Flow to accelerate the design process."})]})},k=m("Puffin"),I={title:"Puffin Bulk Generator",subTitle:"Figma plugin for Roku designers",coverImageSrc:"".concat(k,"/1.png"),coverPosition:"center",passwordRequired:!1,overview:{problem:"Bulk generating assets is an arduous task for designers, and handoff to engineering is a manual, non-standardized process.",goal:"Take the tedious work out of bulk generating assets. Automate work that is currently done with copy/paste, while letting designers have control over tweaking the details. Make it easy to hand off assets to engineers.",outcome:"I released the first version of the plugin in August 2023.",role:"I was the sole designer and developer on this project.",technologies:"Figma Plugin API, TypeScript, React",dates:"April - August 2023"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"A common problem in the Roku design community is that generating tile images is a long process involving a lot of copying and pasting. Tile assets can be a list of TV or movie categories, sports games, local news, and more. Designers need to verify text translations for each tile, which can involve generating 100+ tiles at a time. Tile images must be compressed and follow a naming convention before handing off to engineers."}),Object(n.jsx)("img",{src:"".concat(k,"2.png"),alt:"Example tiles"}),Object(n.jsx)("p",{className:"caption",children:"Example of tiles Roku designers generate"}),Object(n.jsx)("p",{children:"My first thought when hearing about these issues was that a Figma plugin could be a perfect fit to automate many of these tasks. The advantage to using Figma is that designers can keep their work in one tool, and have the functionality they need to customize assets."}),Object(n.jsxs)("p",{children:["When starting my design process I chose to use"," ",Object(n.jsx)("a",{href:"https://www.figma.com/community/file/928108847914589057",children:"UI2, Figma's Design System"}),". I learned from previous experience building plugins that they felt more integrated with Figma when they used the same design system. I chose to swap Figma's traditional blue accent with an electric purple to give it a Roku-themed flair."]}),Object(n.jsx)("p",{children:'The first challenge was understanding the process designers go through to customize each tile. I wanted to make bulk generation a "one-click" experience but found it didn\'t work with the designer\'s workflows. Each tile has to be customized - the color changed or an icon repositioned. It didn\'t make sense to generate 100 tiles all at once if the designer would have to go back and tweak each tile. I shifted my mindset to thinking of it as "applying a transformation" to tiles in a multi-step process.'}),Object(n.jsx)("img",{src:"".concat(k,"3.png"),alt:"User flow"}),Object(n.jsx)("p",{className:"caption",children:"Planning the tile generation flow"}),Object(n.jsx)("p",{children:"The next step was to design the export experience. My design stakeholders requested that I limit the export customizations to simplify the process. With that direction, I added settings for image resolutions, formats, and folder naming. Puffin takes care of standardizing the naming of each layer behind the scenes."}),Object(n.jsx)("p",{children:"Another request from the stakeholders was to add a shortcut for creating Figma components. This feature makes it easy to generate a blank component with aspect ratio variants since Roku tiles all use the same set of aspect ratios."}),Object(n.jsx)("img",{src:"".concat(k,"4.png"),alt:"Puffin screens"}),Object(n.jsx)("p",{className:"caption",children:"Finalized designs"}),Object(n.jsxs)("p",{children:["My previous experience building Figma plugins accelerated the development process. I used a handy"," ",Object(n.jsx)("a",{href:"https://github.com/nirsky/figma-plugin-react-template",children:"React Figma plugin template"})," ","to start the project. I leveraged"," ",Object(n.jsx)("a",{href:"https://github.com/alexandrtovmach/react-figma-plugin-ds",children:"react-figma-plugin-ds"})," ","for design system components."," ",Object(n.jsx)("a",{href:"https://github.com/Donaldcwl/browser-image-compression",children:"browser-image-compression"})," ","and ",Object(n.jsx)("a",{href:"https://github.com/Stuk/jszip",children:"jszip"})," helped me export assets."]}),Object(n.jsx)("p",{children:"Finally, I was ready to share the plugin with the Roku design community. I created a quick overview video to show what the plugin could do."}),Object(n.jsx)("video",{controls:!0,preload:"none",poster:"".concat(k,"1.png"),children:Object(n.jsx)("source",{src:"".concat(k,"Demo.mp4"),type:"video/mp4"})}),Object(n.jsxs)("p",{children:["Puffin 1.0 launched in August 2023. The next step is to gather user feedback to determine changes to make in the next version. I would like to explore stronger image compression techniques such as"," ",Object(n.jsx)("a",{href:"https://en.wikipedia.org/wiki/Quantization_(image_processing)",children:"image quantization"}),"."]})]})},T=m("Oso"),N={title:"Remote backlight interface",subTitle:"Controlling TV remote hardware",coverImageSrc:"".concat(T,"/1.png"),coverPosition:"center top",passwordRequired:!0,overview:{problem:"Roku designers needed to control the LED backlight color and behaviors of an experimental TV remote for user testing.",goal:"Develop a web interface that allows designers to control TV remote settings during experimentation and user testing.",outcome:"Moderators used my interface during user testing sessions to control the remote and gathered useful feedback about the design.",role:"I was the sole designer and software developer on this project. I partnered with a hardware engineer to send signals between the web interface and the remote.",technologies:"React, Web Serial API, Arduino",dates:"September - October 2022"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("img",{src:"".concat(T,"2.png"),alt:"Backlit remotes"}),Object(n.jsx)("p",{className:"caption",children:"Backlit remote prototypes"}),Object(n.jsx)("p",{children:"The next generation of Roku remotes will be backlit, meaning that when a user interacts with the remote it will light up. This will make the buttons easier to see, especially in dark conditions. Roku designers needed to determine the best LED color for the remote. They also needed to find the best activation method for the light. The options were touch (holding remote), proximity (hand is near the remote), or accelerometer (moving remote). Designers also needed to be able to configure how long the light would stay on, and how long the light should fade out."}),Object(n.jsx)("p",{children:"The best way to make decisions about remote backlighting was through user testing. The designers requested to customize remote configurations, and have shortcuts to show different configurations during testing. This is where I came in to design and develop an interface the designers could use."}),Object(n.jsxs)("p",{children:["A hardware engineer on my team had built several remotes with an"," ",Object(n.jsx)("a",{href:"https://www.arduino.cc/",children:"Arduino"})," that could change the remote's settings. It was possible to have a website communicate with the remote's Arduino using the"," ",Object(n.jsx)("a",{href:"https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API",children:"Web Serial API"}),". The challenge was that I had never used the Web Serial API and I had only a few weeks to design and build this project."]}),Object(n.jsxs)("p",{children:["I got to work right away. I leveraged several of Roku's web design system components to build the interface faster. There were several custom components I needed to build. I created a color picker that could handle both regular RGB colors as well as"," ",Object(n.jsx)("a",{href:"https://giggster.com/guide/color-temperature-chart/",children:"color temperatures"}),". Most of the remote button lights would be a shade of warm white. Certain buttons that launched channels would use a brand color, such as red for Netflix.."]}),Object(n.jsx)("img",{src:"".concat(T,"3.png"),alt:"Web interface"}),Object(n.jsx)("p",{className:"caption",children:"Settings used to control the remote"}),Object(n.jsx)("p",{children:"In addition to the main control dashboard I gave designers the ability to create preset configurations. Presets were important because the user testing moderator needed to be able to show users different variations during the session. I set it up so they could apply many settings at once with the click of a button."}),Object(n.jsx)("img",{src:"".concat(T,"4.png"),alt:"Preset creation"}),Object(n.jsx)("p",{className:"caption",children:"Creating a preset configuration"}),Object(n.jsx)("p",{children:"User testing sessions showed that the reaction to backlit remotes was positive. Preference for the light activation method (touch, proximity, accelerometer) was mixed."}),Object(n.jsx)("p",{children:"I learned a lot about communicating with Arduinos in this project. If I had more time to work on it, I would have iterated on the design and asked for more feedback. The interface was quite complicated to use due to the number of settings and customization needed, but could have room for improvement on simplicity. However, I would call delivering a complicated prototype on a tight timeline a win."})]})},R=m("UXE"),P={title:"Roku UXE Team Site",subTitle:"Showcasing tools & prototypes",coverImageSrc:"".concat(R,"/1.png"),coverPosition:"center top",passwordRequired:!1,overview:{problem:"The Roku UX Engineering team needed a place to share their tools, prototypes, and roadmap.",goal:"Create a beautiful website that informs other designers about UX Engineering.",outcome:"The Roku design community was able to learn more about our team's work and how to connect with us.",role:"I was the sole designer and full stack engineer on this project.",technologies:"React, Node.js, AWS Dynamo DB, AWS S3, Jira API",dates:"September 2022 - August 2023"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"The Roku UX Engineering team needed a way to increase our visibility to the rest of the design organization. A basic Confluence page would not do! This website needed to include our internal tools, prototypes, team roadmap, and FAQs about working with our team."}),Object(n.jsx)("p",{children:"When I started designing this project, I knew I wanted to use striking colors and lots of Roku purple. I was particularly inspired by an ad campaign Roku had done recently. I loved the rounded shapes and gradients and wanted to incorporate them in my designs. My goal was for the website to be attractive to designers and show off our UX Engineering front-end skills."}),Object(n.jsx)("img",{src:"".concat(R,"2.jpg"),alt:"Roku billboard ad"}),Object(n.jsx)("p",{children:"The first section of the site shows a list of our internal tools. Each tool has an accompanying video or image with links to its site. The tab arrangement on the left allows you to browse the details for each tool."}),Object(n.jsx)("img",{src:"".concat(R,"1.png"),alt:"Internal tools"}),Object(n.jsx)("p",{children:"The next section shows the prototypes the team has worked on. The list style mimics the experience of browsing content on a Roku device. You can search for prototypes, which is helpful because there are more than 60 prototypes to view."}),Object(n.jsx)("img",{src:"".concat(R,"3.png"),alt:"Viewing prototypes"}),Object(n.jsx)("p",{children:"Each prototype can open in a modal. There you can see more project details and interact with prototype in an iframe."}),Object(n.jsx)("img",{src:"".concat(R,"4.png"),alt:"Prototypes detail view"}),Object(n.jsx)("p",{children:"I also added a hidden form where UX Engineers could add more prototypes to the list. The data is saved to AWS Dynamo DB and images are uploaded to AWS S3."}),Object(n.jsx)("img",{src:"".concat(R,"5.png"),alt:"Add prototype"}),Object(n.jsxs)("p",{children:["The roadmap section displayed a calendar view of the projects the team is working on. This view syncs with Jira using the"," ",Object(n.jsx)("a",{href:"https://developer.atlassian.com/server/jira/platform/rest-apis/",children:"Jira API"}),", so roadmap updates are automatic."]}),Object(n.jsx)("img",{src:"".concat(R,"6.png"),alt:"Project roadmap"}),Object(n.jsx)("p",{children:"The FAQ section lets other designers know how best to work with our team. It includes two quizzes to assess whether a coded prototype is necessary and approximately how long a prototype will take to develop."}),Object(n.jsx)("img",{src:"".concat(R,"7.png"),alt:"FAQ section"}),Object(n.jsx)("img",{src:"".concat(R,"8.png"),alt:"Prototype quiz"})]})},S=m("BrandRefresh"),F={title:"Roku OS Brand Refresh",subTitle:"Prototype of design updates",coverImageSrc:"".concat(S,"/1.png"),coverPosition:"center top",passwordRequired:!0,overview:{problem:"The Roku Visual Design team needed to showcase their vision of a brand refresh initiative to executives.",goal:"Prototype design updates across the entire Roku OS.",outcome:"I presented the prototype to Roku's VP of Design.",role:"I was the lead engineer on this project and worked with another UX Engineer.",technologies:"Vue.js",dates:"July - August 2023"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"In fall 2023, Roku plans to release a brand refresh for the Roku platform. The goals of this refresh are to have better visual appeal and help users understand the Roku brand. A teammate and I worked with visual designers to create a prototype that showcases design changes on every screen of the platform."}),Object(n.jsx)("img",{src:"".concat(S,"1.png"),alt:"Roku home screen"}),Object(n.jsx)("p",{className:"caption",children:"Roku home screen with brand refresh updates"}),Object(n.jsx)("p",{children:"This was a complicated undertaking given the breadth of changes and a quick timeline. We ran two-week sprints for this project and prioritized the features that needed to be fully functional. I took charge of creating, prioritizing, and assigning Jira tickets for each feature."}),Object(n.jsx)("p",{children:"Theming was an important aspect of the brand refresh and included black, purple, and red variations. We used CSS variables to dynamically change color styles. The prototype included a configuration page that allowed designers to change the theme."}),Object(n.jsx)("img",{src:"".concat(S,"2.png"),alt:"Prototype configuration"}),Object(n.jsx)("p",{className:"caption",children:"Prototype configuration screen"}),Object(n.jsx)("p",{children:'Many screens within the prototype needed to be fully functional. Some of the screens I created included "What to Watch" (content suggestions), a sports page, and The Roku Channel (Roku\'s streaming service).'}),Object(n.jsx)("img",{src:"".concat(S,"3.png"),alt:"What to Watch page"}),Object(n.jsx)("p",{className:"caption",children:'"What to Watch" content suggestions'}),Object(n.jsx)("img",{src:"".concat(S,"4.png"),alt:"Sports page"}),Object(n.jsx)("p",{className:"caption",children:"NFL Sports page"}),Object(n.jsx)("img",{src:"".concat(S,"5.png"),alt:"The Roku Channel"}),Object(n.jsx)("p",{className:"caption",children:"The Roku Channel (Roku's streaming service)"}),Object(n.jsx)("p",{children:"This project concluded with a presentation to Roku's VP of Design. I demoed the prototype so the executives could get a realistic experience of what the changes would be like in product."})]})},A=m("NBCUX"),D=[{title:"NBCUX Self-Service",subTitle:"Figma plugin for NBC Universal",coverImageSrc:"".concat(A,"/1.png"),coverPosition:"top",passwordRequired:!1,overview:{problem:"NBC Universal has many internal enterprise tools and not enough designers to support them. Product managers need to create their own layouts in Figma with minimal support from the design team.",goal:"Make Figma easy to use for non-designers by creating a plugin that generates layouts using design system components.",outcome:"The first version of the plugin was released to users in January 2024.",role:"I was the lead engineer on this project and worked with another UX Engineer and two designers. This was a short-term contract role.",technologies:"Figma Plugin API, TypeScript, React, Material UI, GitHub Actions, Jest",dates:"October - December 2023"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"NBC Universal uses more than 100 internal enterprise tools. Their design team has fewer than 10 designers to support these tools, leaving a design gap. Product managers try to fill this gap but find Figma hard to use for design work. Additionally, they are often limited to using only FigJam due to licensing costs. My team developed a Figma plugin to allow non-designers to self-serve simple layouts."}),Object(n.jsx)("p",{children:"We focused on generating a commonly used page template with a data table as the MVP for this tool. Users can fill out a form to generate a table page and specify the components they want to use in the layout. They can customize how many columns the table has and what the data type of each column should be. They can also add components such as tabs, a search bar, breadcrumb navigation, and more."}),Object(n.jsx)("img",{src:"".concat(A,"/2.png"),alt:"Creating a table"}),Object(n.jsx)("p",{className:"caption",children:"Creating a table"}),Object(n.jsx)("p",{children:"When a template is created, the plugin adds documentation next to it. It provides a short explanation of how to use the plugin and where to get help. The documentation itself is a published Figma component the plugin imports. This way, designers can update the documentation without developer support."}),Object(n.jsx)("img",{src:"".concat(A,"/3.png"),alt:"Documentation"}),Object(n.jsx)("p",{children:"Users can update template layouts as needed. The update process uses the current selection to populate the form data in the UI."}),Object(n.jsx)("img",{src:"".concat(A,"/4.png"),alt:"Updating a table"}),Object(n.jsx)("p",{className:"caption",children:"Updating a table"}),Object(n.jsx)("p",{children:"I created a short demo video of the plugin to share our MVP with new users."}),Object(n.jsx)("video",{controls:!0,preload:"none",poster:"".concat(A,"1.png"),children:Object(n.jsx)("source",{src:"".concat(A,"Demo.mp4"),type:"video/mp4"})}),Object(n.jsxs)("p",{children:["As the plugin gains more users, it's helpful to have analytics to tell us how they use the tool. We want to know who the users are, if they're using Figma or FigJam, what they are creating and how often. It's also important to know if they run into errors. I chose"," ",Object(n.jsx)("a",{href:"https://mixpanel.com/",children:"Mixpanel"})," to handle our analytics because it's easy to use and integrates well with Figma plugins."]}),Object(n.jsx)("img",{src:"".concat(A,"/Mixpanel.png"),alt:"Mixpanel Analytics Dashboard"}),Object(n.jsxs)("p",{className:"caption",children:[Object(n.jsx)("a",{href:"https://mixpanel.com/public/6fPuCsME2BR7Ra7NECbdgx",children:"Mixpanel Analytics dashboard"})," ","with preliminary data."]}),Object(n.jsxs)("p",{children:["This plugin was built with React, TypeScript, and Material UI components. I wanted to make the code as easy as possible for any future developers. Due to my short-term contract, I wouldn't be there to onboard them in the future. I focused on creating great documentation, commenting code, and writing unit tests. I chose"," ",Object(n.jsx)("a",{href:"https://www.notion.so/",children:"Notion"})," to host documentation on the code base and our publication process. I wrote thorough"," ",Object(n.jsx)("a",{href:"https://jsdoc.app/",children:"JSDoc"})," comments for all the functions in our codebase. I used ",Object(n.jsx)("a",{href:"https://jestjs.io/",children:"Jest"})," to write 45 unit tests that automatically ran on pull requests."]}),Object(n.jsxs)("p",{children:["A pain point of creating Figma plugins is that the publishing process is manual and you have to upload code in the Figma editor. To work around this I used ",Object(n.jsx)("a",{href:"https://github.com/parrot-global/figcd",children:"figcd"}),", a command-line Figma plugin publishing tool. I combined it with GitHub Actions to deploy releases, which took a multi-step process to one click of a button."]}),Object(n.jsx)("p",{children:"The NBCUX Self-Service Figma plugin launched in January 2024. Development work is now paused while more support and funding is gathered for the plugin. In the future, the plugin will include more libraries (Angular, React, and Salesforce), templates, and components."})]})},O,I,F,N,P,x,w,j],C=o(30),E=(o(22),function(e){var t=e.authenticate,o=void 0===t?function(){}:t,a=Object(s.useState)(""),i=Object(c.a)(a,2),r=i[0],l=i[1],d=Object(s.useState)(!1),h=Object(c.a)(d,2),u=h[0],g=h[1];return Object(n.jsxs)("div",{className:"passwordContainer",children:[Object(n.jsx)(p.f,{className:"passwordIcon"}),Object(n.jsxs)("p",{className:"body2",children:["Sorry, the information in this project is confidential and requires a password. Please email"," ",Object(n.jsx)("span",{className:"email",children:"amtruttmann@gmail.com"})," for access."]}),Object(n.jsxs)("div",{className:"formContainer",children:[Object(n.jsxs)("form",{className:"passwordForm",onSubmit:function(e){g(!o(r)),e.preventDefault()},children:[Object(n.jsx)("input",{type:"text",autoComplete:"username",style:{display:"none"}}),Object(n.jsx)("input",{id:"passwordInput",value:r,type:"password",placeholder:"Password","aria-label":"Password",className:"passwordInput",onChange:function(e){return l(e.target.value)},name:"password",autoComplete:"current-password"}),Object(n.jsx)("input",{type:"submit",value:"Access",className:"passwordButton"})]}),Object(n.jsxs)("div",{className:"passwordError",style:u?{visibility:"visible"}:{visibility:"hidden"},children:[Object(n.jsx)(p.b,{className:"passwordWarning"}),Object(n.jsx)("p",{className:"body3",children:"Whoops, looks like this password is invalid"})]})]})]})}),U=(o(23),function(e){var t=e.project,o=e.open,a=e.closeModal,i=e.authenticated,r=e.authenticate,c=void 0===r?function(){}:r;Object(s.useEffect)((function(){i&&l()}),[i]);var l=function(){var e=document.getElementsByTagName("video");Array.from(e).forEach((function(e){e.preload="auto",e.style.opacity="1"}))};return Object(n.jsx)(C.a,{in:o,timeout:400,unmountOnExit:!0,onEntered:function(){l();var e=document.getElementById("passwordInput");e&&e.focus()},children:Object(n.jsx)("div",{className:"modalContainer",children:Object(n.jsxs)("div",{className:"projectContent",children:[Object(n.jsx)("button",{className:"closeButton",onClick:a,"aria-label":"Close modal",children:Object(n.jsx)(p.i,{className:"closeIcon"})}),Object(n.jsxs)("div",{className:"projectHeader",children:[Object(n.jsx)("h1",{children:t.title}),Object(n.jsx)("h3",{children:t.subTitle})]}),t.passwordRequired&&!i?Object(n.jsx)(E,{authenticate:c}):Object(n.jsxs)(n.Fragment,{children:[Object(n.jsxs)("div",{className:"overview",children:[Object(n.jsx)("h2",{className:"sectionTitle",children:"Overview"}),Object.keys(t.overview).map((function(e){return Object(n.jsxs)("p",{children:[Object(n.jsx)("b",{children:"".concat(e,": ")}),t.overview[e]]},e)})),t.links&&t.links.length>=1&&Object(n.jsxs)("p",{children:[Object(n.jsx)("b",{children:"Links: "}),t.links.map((function(e,o){return Object(n.jsxs)("span",{children:[Object(n.jsx)("a",{href:e.url,children:e.title},e.title),o\r\n

Hi, I'm Alayna

\r\n

\r\n I'm a Senior Design Engineer with several years of experience in\r\n web-based prototyping and creating design tools at NBC, Roku, and\r\n Intuit.\r\n

\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n \r\n );\r\n}\r\n\r\nexport default Header;\r\n","import \"./ProjectTile.scss\";\r\n\r\nconst ProjectTile = ({\r\n project,\r\n setSelectedProject = () => {},\r\n setModalOpen = () => {},\r\n}) => {\r\n return (\r\n {\r\n setSelectedProject(project);\r\n setModalOpen(true);\r\n e.stopPropagation();\r\n }}\r\n >\r\n \r\n
\r\n

{project.title}

\r\n

{project.subTitle}

\r\n
\r\n \r\n );\r\n};\r\n\r\nexport default ProjectTile;\r\n","const getImgPrefix = (imgFolder) => {\r\n return `${process.env.PUBLIC_URL}images/${imgFolder}/`;\r\n};\r\nexport default getImgPrefix;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"IEPShell\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst IEPShell = {\r\n title: \"Intuit Expert Portal\",\r\n subTitle: \"Proof of concept for design updates\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"top left\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"The portal Intuit customer service agents use needed updates to better align with the Intuit Design System and make usability improvements.\",\r\n goal: \"Create a React prototype with these design updates to be passed off to production engineers.\",\r\n outcome:\r\n \"Production engineers were able to reuse my code, which accelerated their release process.\",\r\n role: \"I was the sole developer for this project and partnered with a product design team to create this prototype.\",\r\n technologies: \"React, Styled Components\",\r\n dates: \"November - December 2020\",\r\n },\r\n links: [],\r\n content: (\r\n <>\r\n

\r\n For this project, I worked with the Intuit Export Portal design team.\r\n This portal is used by Intuit customer service agents (a.k.a. Intuit\r\n Experts) to assist customers. In this portal Experts can see customer\r\n data while on a call and also manage personal work information such as\r\n their schedule and notes.\r\n

\r\n

\r\n The focus for this prototype was updating the \"shell\" of the product -\r\n the left, top, and right navigation elements. The design team wanted to\r\n refresh the components and visual design to align with Intuit Design\r\n Systems. They also rearranged some of the navigation content based on\r\n feedback from their users. The inner content was not finalized for this\r\n phase of prototyping so a responsive column layout was used as a\r\n placeholder.\r\n

\r\n \"The\r\n\r\n

\r\n The functionality required for this prototype was to be able to click\r\n through the left navigation items and open and close the right drawer.\r\n The \"Engagements\" screen needed to show a header and tabs with client\r\n information. Navigation elements also had to behave responsively on\r\n smaller screens.\r\n

\r\n \r\n

\r\n This video shows the entire flow of the prototype.\r\n

\r\n\r\n

\r\n The most challenging development aspect of this project was the\r\n responsive design of the top navigation. This header included dropdowns,\r\n links, and other information that needed to be accessible on smaller\r\n screens. These navigation items needed to collapse into an overflow\r\n menu.\r\n

\r\n \r\n

\r\n Opening the milestone dropdown when the header is not in an overflow\r\n state.\r\n

\r\n\r\n \r\n

\r\n Accessing the milestone dropdown in an overflow state.\r\n

\r\n\r\n

\r\n Another responsive aspect of this screen was the overflow behavior for\r\n tabs. Tabs needed an arrow to show that there were more tabs hidden.\r\n When clicked, this arrow needed to scroll the tabs by a set pixel value.\r\n

\r\n \"Tabs\r\n\r\n

\r\n After this prototype had been finalized with the design team, I handed\r\n off the prototype and the code to the development team. They were able\r\n to reuse my work in their production code, which sped up the process to\r\n implement these changes.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default IEPShell;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"Pyro\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst Pyro = {\r\n title: \"Pyro\",\r\n subTitle: \"Prototyping tool for Intuit designers\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"center\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"Intuit designers need a way to quickly create high-fidelity prototypes with interactive components and real user data.\",\r\n goal: \"Develop an intuitive drag-and-drop interface that leverages Intuit Design System components and supports custom data and logic.\",\r\n outcome:\r\n \"Pyro 1.0 was released in November 2020. I continued to maintain the tool and release feature updates.\",\r\n role: \"I worked on a team of five Design Technologists. I wore many hats including developing the product, designing new features, leading user testing sessions, and prioritizing our Jira board.\",\r\n technologies: \"React, Firebase\",\r\n dates: \"February 2020 - April 2022\",\r\n },\r\n links: [],\r\n content: (\r\n <>\r\n

\r\n Pyro is a custom prototyping tool built by Intuit Design Technologists\r\n for Intuit designers. It allows anyone to create prototypes using Intuit\r\n Design System components, user data, and logic without writing any code.\r\n I have been working on this project since February 2020 improving the\r\n editor and creating features that cater to QuickBooks design needs.\r\n

\r\n\r\n \r\n

\r\n This is the demo video for the initial release of Pyro. Video editing\r\n credits go to my awesome colleagues Heather & Lynda.\r\n

\r\n\r\n

\r\n Pyro leverages Craft.js with React\r\n to create drag and drop functionality in the editor. The prototype data\r\n syncs to a Firebase backend.\r\n Users can grab components from the left-side panel and drag them into\r\n the editor. When a component is selected, you can edit its properties in\r\n the right-side panel. These components are either custom components\r\n built for Pyro or they are imported from Intuit's design system.\r\n

\r\n \"Pyro\r\n

\r\n In addition to changing the style of components, you can also set an \"on\r\n click\" action for the component or conditionally show or hide it. This\r\n allows users to build complex prototypes with many pages and branching\r\n flows. This feature is particularly important for TurboTax designers who\r\n often need to create flows with a series of questions.\r\n

\r\n \"Close\r\n

\r\n Many QuickBooks designers need to incorporate real user data into\r\n customer testing sessions to help the customer feel like the prototype\r\n is real. Customer data often comes in the form of a list of\r\n transactions, and typically a Design Technologist would build a custom\r\n React prototype to display this data. We added an editable table\r\n component to Pyro that allows designers to upload user data as a CSV,\r\n saving us all time!\r\n

\r\n \"Table\r\n

\r\n Once Pyro was close to being ready for release, a teammate and I\r\n conducted ten user testing sessions with Intuit designers. We wanted to\r\n learn if there were any major usability issues blocking the release and\r\n get feedback on what features should be added to Pyro. The reaction from\r\n our participants was very positive and they were excited to use Pyro\r\n

\r\n

\r\n The main issues that came out of testing were:\r\n
\r\n 1. The onboarding flow was too long and there was more information than\r\n users could process.\r\n
\r\n 2. Users expected to be able to undo and redo changes. (At the time of\r\n testing, this feature was still in development)\r\n
\r\n 3. Adding a new page to the prototype was not intuitive.\r\n

\r\n \"Testing\r\n\r\n

\r\n The majority of the issues from user testing were addressed and Pyro\r\n released to Intuit designers in November 2020.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default Pyro;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"Toolkit\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst Toolkit = {\r\n title: \"QB Designer Toolkit\",\r\n subTitle: \"Figma plugin for Intuit designers\",\r\n coverImageSrc: `${imgPrefix}/Cover.png`,\r\n coverPosition: \"center\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"How can we leverage Figma plugins to improve Intuit designers' workflows?\",\r\n goal: \"Create a Figma plugin that empowers designers to easily add motion, content, and theming to their work.\",\r\n outcome:\r\n \"The first version of the plugin was released in April 2021, and over time gained more than 238 users. I continued to maintain the plugin and release feature updates.\",\r\n role: \"I was the lead for this tool. I maintained the design and code of this plugin and promoted it to our users.\",\r\n technologies: \"Figma Plugin API, TypeScript, React\",\r\n dates: \"February 2021 - April 2022\",\r\n },\r\n content: (\r\n <>\r\n

Version 1

\r\n

\r\n During the fall of 2020, Intuit designers made the switch to using{\" \"}\r\n Figma as their primary design tool.\r\n Figma supports adding{\" \"}\r\n \r\n plugins\r\n \r\n , which are apps you can install to add functionality and improve your\r\n workflow. After experimenting with Figma plugins in a hackathon, I was\r\n eager to develop the first Figma plugin for Intuit designers.\r\n

\r\n

\r\n A design hurdle I wanted to tackle was supporting theming, particularly\r\n dark mode. Dark mode has been a work in progress for Intuit design for\r\n some time and is currently an experimental beta setting for QuickBooks.\r\n Dark mode is a priority because it is a feature our users expect, and\r\n also has accessibility benefits such as better contrast and reduced eye\r\n strain. As this feature becomes more used, we need to make sure that\r\n designs will work for both light and dark modes.\r\n

\r\n \"Dark\r\n

\r\n I created a simple UI that would allow users to toggle both layers and\r\n pages between light and dark mode. I also included a color inspector\r\n that would display the fill and border colors for a selected layer and\r\n show their light and dark mode pairings.\r\n

\r\n \"Plugin\r\n

\r\n Inspecting a dark mode design to see the color pairings.\r\n

\r\n

\r\n Once I had a solid design, I moved on to developing the functionality\r\n using TypeScript and{\" \"}\r\n Sass. The plugin analyzes a layer's\r\n fill and border colors, finding the appropriate contextual color\r\n pairing, and then changing the layer's colors to the new theme.This\r\n automatic process is completed in a matter of seconds, which saves\r\n designers hours of work in manually changing colors.\r\n

\r\n \r\n

\r\n This quick demo of Dark Mode that appeared within a QB Designer Toolkit\r\n instructional video I created.\r\n

\r\n

\r\n Design Technologists on my team also created two other plugins that\r\n focus on motion and content. We combined all three plugins to make it\r\n easy for designers to access all of our tools at once. I led the merge\r\n effort and refactored our code to use the same visual style and coding\r\n standards.\r\n

\r\n

\r\n Version 1 of this plugin was released to the QuickBooks design community\r\n in April 2021. The plugin now has now been installed by 238 users,\r\n roughly 2/3 of our total designers.\r\n

\r\n\r\n
\r\n

Version 2

\r\n

\r\n After the successful launch of the plugin, I paused development for\r\n several months to collect analytics and user feedback. In December 2021,\r\n I created a plan to add more functionality to the plugin. I wanted to\r\n target three key areas:\r\n

\r\n

Opportunity #1: Refresh the plugin design

\r\n

\r\n The first version of this plugin had features that were designed by\r\n separate teams and lacked a common visual language. The design of the\r\n plugin did not support multiple modes of navigation. It was likely that\r\n we would need the flexibility to develop more complex UIs in the future.\r\n

\r\n

Opportunity #2: Contextualize analytics data

\r\n

\r\n Adding analytics to a Figma plugin can be a bit of a challenge. Since it\r\n is contained within an iFrame, there is no access to certain information\r\n most analytics tools need. For the first launch, I developed a simple\r\n click counting system for the buttons in the plugin. While this did help\r\n me understand which buttons were being used the most, it lacked the\r\n contextual data. Which user clicked, and in what file, and at what time?\r\n I needed this information to make data-driven decisions about the future\r\n of the plugin.\r\n

\r\n

Opportunity #3: Add a requested feature

\r\n

\r\n Currently, the most used feature within the plugin is the content\r\n generator. My team did user research to get feedback on this feature and\r\n found that a common ask from designers was a way to generate numbers.\r\n

\r\n

Tackling opportunities

\r\n

\r\n I started by redesigning the plugin to create a common visual language.\r\n I chose to use{\" \"}\r\n \r\n UI2, Figma's Design System\r\n {\" \"}\r\n as the basis for my design. Using Figma's components and styles helps\r\n the plugin blend into Figma's UI and seem like a more natural extension\r\n of its capabilities. I also changed the plugin navigation system to\r\n include a flyout menu. Moving page navigation into the flyout menu gave\r\n each feature room for its own navigational elements.\r\n

\r\n \"Redesign\r\n

\r\n Selection of redesigned screens before (left) and after (right)\r\n

\r\n \"Cover\r\n

Redesigned cover art for installation page

\r\n

\r\n My next step was to add analytics. I chose to use{\" \"}\r\n Mixpanel because of its powerful\r\n capabilities and compatibility with Figma plugins. Now, when a user\r\n clicks a button I know their name, the file they are using, and their\r\n overall activity. I can track monthly active users, view a list of Figma\r\n files the plugin is being used in, and see which buttons are clicked the\r\n most. This will help me know which features of the plugin are most\r\n valuable and should be invested in. I now know who the top users of the\r\n plugin are and can ask them for feedback.\r\n

\r\n \r\n

\r\n \r\n Mixpanel Analytics dashboard\r\n {\" \"}\r\n with three weeks of data\r\n

\r\n

\r\n Next, I worked on the random number generation feature. QuickBooks\r\n designers often create data tables with transactional information such\r\n as dates, percentages, and currency values so I wanted to include all of\r\n these options in this feature. This feature has the flexibility to add\r\n one number or a range of numbers, with options to sort the range. This\r\n will reduce the work a designer has to do filling out a table from\r\n minutes to seconds.\r\n

\r\n \"Random\r\n

\r\n The UI allows users to customize the format of numbers, currencies, and\r\n dates\r\n

\r\n

\r\n Version 2 was released in January 2022, and the plugin continues to be\r\n widely used amongst Intuit designers.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default Toolkit;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"Flow\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst Flow = {\r\n title: \"Flow\",\r\n subTitle: \"Prototyping tool for web & TV\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"center\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"Roku designers need a way to easily view their prototypes on TVs for user testing.\",\r\n goal: \"Create an easy to use web platform that exports prototypes to a Roku TV channel.\",\r\n outcome:\r\n \"The fist version of Flow was released in August 2022. I continued to maintain the tool and release feature updates.\",\r\n role: \"I was the sole designer and full stack engineer on this project.\",\r\n technologies: \"React, Node.js, Express, AWS Dynamo DB, AWS S3\",\r\n dates: \"April 2022 - September 2023\",\r\n },\r\n content: (\r\n <>\r\n

\r\n Prototyping is at the heart of the Roku UX Engineering team's focus, but\r\n we don't always have time to make every prototype designers request.\r\n Creating an internal prototyping tool allows designers to self-serve\r\n simple prototypes, freeing up UX Engineers to code complex experiences.\r\n Flow empowers designers to create and view their prototypes on a TV and\r\n share them with in-person or remote users for testing.\r\n

\r\n\r\n

\r\n The first step I took to make Flow was to plan out the user experience.\r\n The experience is split between creating a prototype on the web and\r\n viewing a prototype on a Roku channel.\r\n

\r\n\r\n \"User\r\n\r\n

\r\n I started designing the experience using Roku's web design system\r\n components. I iterated on the design using feedback gathered from my\r\n team. The primary web views are:\r\n

    \r\n
  1. Login
  2. \r\n
  3. View all prototypes
  4. \r\n
  5. Edit prototype
  6. \r\n
  7. Preview prototype
  8. \r\n
\r\n

\r\n\r\n \"Flow\r\n

\r\n View all of your prototypes on the Flow home page\r\n

\r\n\r\n

\r\n I started developing the website first. I used React to build the UI and\r\n a Node.js to send data to Dynamo DB and store images in a S3 bucket. I\r\n leveraged the open source{\" \"}\r\n React Flow library as the editor\r\n for my interactive diagrams.\r\n

\r\n \"Flow\r\n

Editing a prototype

\r\n\r\n

\r\n I built a web preview so users can try out the prototype on the web and\r\n fix issues before viewing on the TV.\r\n

\r\n \"Prototype\r\n

Previewing a prototype

\r\n\r\n

\r\n The next phase of my development work was creating the Roku channel. I\r\n used an internal technology that functions like to React but works on TV\r\n channels. It is similar to the externally available{\" \"}\r\n Roku SDK.\r\n

\r\n \"Roku\r\n

\r\n Entering a prototype code on the installed channel\r\n

\r\n\r\n

\r\n Flow 1.0 was released in August 2022. I created a demo video to\r\n introduce users to Flow.\r\n

\r\n\r\n \r\n\r\n

\r\n As more designers have used the tool I added new features based on their\r\n use cases. These features include fade transitions, long-pressing remote\r\n buttons, allowing videos, screen reader support, and more. Eventually, I\r\n wanted to make a Figma plugin that can export images into Flow to\r\n accelerate the design process.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default Flow;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"Puffin\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst Puffin = {\r\n title: \"Puffin Bulk Generator\",\r\n subTitle: \"Figma plugin for Roku designers\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"center\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"Bulk generating assets is an arduous task for designers, and handoff to engineering is a manual, non-standardized process.\",\r\n goal: \"Take the tedious work out of bulk generating assets. Automate work that is currently done with copy/paste, while letting designers have control over tweaking the details. Make it easy to hand off assets to engineers.\",\r\n outcome: \"I released the first version of the plugin in August 2023.\",\r\n role: \"I was the sole designer and developer on this project.\",\r\n technologies: \"Figma Plugin API, TypeScript, React\",\r\n dates: \"April - August 2023\",\r\n },\r\n content: (\r\n <>\r\n

\r\n A common problem in the Roku design community is that generating tile\r\n images is a long process involving a lot of copying and pasting. Tile\r\n assets can be a list of TV or movie categories, sports games, local\r\n news, and more. Designers need to verify text translations for each\r\n tile, which can involve generating 100+ tiles at a time. Tile images\r\n must be compressed and follow a naming convention before handing off to\r\n engineers.\r\n

\r\n\r\n \"Example\r\n

Example of tiles Roku designers generate

\r\n\r\n

\r\n My first thought when hearing about these issues was that a Figma plugin\r\n could be a perfect fit to automate many of these tasks. The advantage to\r\n using Figma is that designers can keep their work in one tool, and have\r\n the functionality they need to customize assets.\r\n

\r\n\r\n

\r\n When starting my design process I chose to use{\" \"}\r\n \r\n UI2, Figma's Design System\r\n \r\n . I learned from previous experience building plugins that they felt\r\n more integrated with Figma when they used the same design system. I\r\n chose to swap Figma's traditional blue accent with an electric purple to\r\n give it a Roku-themed flair.\r\n

\r\n\r\n

\r\n The first challenge was understanding the process designers go through\r\n to customize each tile. I wanted to make bulk generation a \"one-click\"\r\n experience but found it didn't work with the designer's workflows. Each\r\n tile has to be customized - the color changed or an icon repositioned.\r\n It didn't make sense to generate 100 tiles all at once if the designer\r\n would have to go back and tweak each tile. I shifted my mindset to\r\n thinking of it as \"applying a transformation\" to tiles in a multi-step\r\n process.\r\n

\r\n\r\n \"User\r\n

Planning the tile generation flow

\r\n\r\n

\r\n The next step was to design the export experience. My design\r\n stakeholders requested that I limit the export customizations to\r\n simplify the process. With that direction, I added settings for image\r\n resolutions, formats, and folder naming. Puffin takes care of\r\n standardizing the naming of each layer behind the scenes.\r\n

\r\n\r\n

\r\n Another request from the stakeholders was to add a shortcut for creating\r\n Figma components. This feature makes it easy to generate a blank\r\n component with aspect ratio variants since Roku tiles all use the same\r\n set of aspect ratios.\r\n

\r\n\r\n \"Puffin\r\n

Finalized designs

\r\n\r\n

\r\n My previous experience building Figma plugins accelerated the\r\n development process. I used a handy{\" \"}\r\n \r\n React Figma plugin template\r\n {\" \"}\r\n to start the project. I leveraged{\" \"}\r\n \r\n react-figma-plugin-ds\r\n {\" \"}\r\n for design system components.{\" \"}\r\n \r\n browser-image-compression\r\n {\" \"}\r\n and jszip helped me export\r\n assets.\r\n

\r\n\r\n

\r\n Finally, I was ready to share the plugin with the Roku design community.\r\n I created a quick overview video to show what the plugin could do.\r\n

\r\n\r\n \r\n\r\n

\r\n Puffin 1.0 launched in August 2023. The next step is to gather user\r\n feedback to determine changes to make in the next version. I would like\r\n to explore stronger image compression techniques such as{\" \"}\r\n \r\n image quantization\r\n \r\n .\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default Puffin;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"Oso\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst Oso = {\r\n title: \"Remote backlight interface\",\r\n subTitle: \"Controlling TV remote hardware\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"center top\",\r\n passwordRequired: true,\r\n overview: {\r\n problem:\r\n \"Roku designers needed to control the LED backlight color and behaviors of an experimental TV remote for user testing.\",\r\n goal: \"Develop a web interface that allows designers to control TV remote settings during experimentation and user testing.\",\r\n outcome:\r\n \"Moderators used my interface during user testing sessions to control the remote and gathered useful feedback about the design.\",\r\n role: \"I was the sole designer and software developer on this project. I partnered with a hardware engineer to send signals between the web interface and the remote.\",\r\n technologies: \"React, Web Serial API, Arduino\",\r\n dates: \"September - October 2022\",\r\n },\r\n content: (\r\n <>\r\n \"Backlit\r\n

Backlit remote prototypes

\r\n\r\n

\r\n The next generation of Roku remotes will be backlit, meaning that when a\r\n user interacts with the remote it will light up. This will make the\r\n buttons easier to see, especially in dark conditions. Roku designers\r\n needed to determine the best LED color for the remote. They also needed\r\n to find the best activation method for the light. The options were touch\r\n (holding remote), proximity (hand is near the remote), or accelerometer\r\n (moving remote). Designers also needed to be able to configure how long\r\n the light would stay on, and how long the light should fade out.\r\n

\r\n

\r\n The best way to make decisions about remote backlighting was through\r\n user testing. The designers requested to customize remote\r\n configurations, and have shortcuts to show different configurations\r\n during testing. This is where I came in to design and develop an\r\n interface the designers could use.\r\n

\r\n\r\n

\r\n A hardware engineer on my team had built several remotes with an{\" \"}\r\n Arduino that could change the\r\n remote's settings. It was possible to have a website communicate with\r\n the remote's Arduino using the{\" \"}\r\n \r\n Web Serial API\r\n \r\n . The challenge was that I had never used the Web Serial API and I had\r\n only a few weeks to design and build this project.\r\n

\r\n\r\n

\r\n I got to work right away. I leveraged several of Roku's web design\r\n system components to build the interface faster. There were several\r\n custom components I needed to build. I created a color picker that could\r\n handle both regular RGB colors as well as{\" \"}\r\n \r\n color temperatures\r\n \r\n . Most of the remote button lights would be a shade of warm white.\r\n Certain buttons that launched channels would use a brand color, such as\r\n red for Netflix..\r\n

\r\n\r\n \"Web\r\n

Settings used to control the remote

\r\n\r\n

\r\n In addition to the main control dashboard I gave designers the ability\r\n to create preset configurations. Presets were important because the user\r\n testing moderator needed to be able to show users different variations\r\n during the session. I set it up so they could apply many settings at\r\n once with the click of a button.\r\n

\r\n\r\n \"Preset\r\n

Creating a preset configuration

\r\n\r\n

\r\n User testing sessions showed that the reaction to backlit remotes was\r\n positive. Preference for the light activation method (touch, proximity,\r\n accelerometer) was mixed.\r\n

\r\n\r\n

\r\n I learned a lot about communicating with Arduinos in this project. If I\r\n had more time to work on it, I would have iterated on the design and\r\n asked for more feedback. The interface was quite complicated to use due\r\n to the number of settings and customization needed, but could have room\r\n for improvement on simplicity. However, I would call delivering a\r\n complicated prototype on a tight timeline a win.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default Oso;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"UXE\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst UXE = {\r\n title: \"Roku UXE Team Site\",\r\n subTitle: \"Showcasing tools & prototypes\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"center top\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"The Roku UX Engineering team needed a place to share their tools, prototypes, and roadmap.\",\r\n goal: \"Create a beautiful website that informs other designers about UX Engineering.\",\r\n outcome:\r\n \"The Roku design community was able to learn more about our team's work and how to connect with us.\",\r\n role: \"I was the sole designer and full stack engineer on this project.\",\r\n technologies: \"React, Node.js, AWS Dynamo DB, AWS S3, Jira API\",\r\n dates: \"September 2022 - August 2023\",\r\n },\r\n content: (\r\n <>\r\n

\r\n The Roku UX Engineering team needed a way to increase our visibility to\r\n the rest of the design organization. A basic Confluence page would not\r\n do! This website needed to include our internal tools, prototypes, team\r\n roadmap, and FAQs about working with our team.\r\n

\r\n\r\n

\r\n When I started designing this project, I knew I wanted to use striking\r\n colors and lots of Roku purple. I was particularly inspired by an ad\r\n campaign Roku had done recently. I loved the rounded shapes and\r\n gradients and wanted to incorporate them in my designs. My goal was for\r\n the website to be attractive to designers and show off our UX\r\n Engineering front-end skills.\r\n

\r\n \"Roku\r\n\r\n

\r\n The first section of the site shows a list of our internal tools. Each\r\n tool has an accompanying video or image with links to its site. The tab\r\n arrangement on the left allows you to browse the details for each tool.\r\n

\r\n \"Internal\r\n\r\n

\r\n The next section shows the prototypes the team has worked on. The list\r\n style mimics the experience of browsing content on a Roku device. You\r\n can search for prototypes, which is helpful because there are more than\r\n 60 prototypes to view.\r\n

\r\n \"Viewing\r\n\r\n

\r\n Each prototype can open in a modal. There you can see more project\r\n details and interact with prototype in an iframe.\r\n

\r\n \"Prototypes\r\n\r\n

\r\n I also added a hidden form where UX Engineers could add more prototypes\r\n to the list. The data is saved to AWS Dynamo DB and images are uploaded\r\n to AWS S3.\r\n

\r\n \"Add\r\n\r\n

\r\n The roadmap section displayed a calendar view of the projects the team\r\n is working on. This view syncs with Jira using the{\" \"}\r\n \r\n Jira API\r\n \r\n , so roadmap updates are automatic.\r\n

\r\n \"Project\r\n\r\n

\r\n The FAQ section lets other designers know how best to work with our\r\n team. It includes two quizzes to assess whether a coded prototype is\r\n necessary and approximately how long a prototype will take to develop.\r\n

\r\n \"FAQ\r\n \"Prototype\r\n \r\n ),\r\n};\r\n\r\nexport default UXE;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"BrandRefresh\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst BrandRefresh = {\r\n title: \"Roku OS Brand Refresh\",\r\n subTitle: \"Prototype of design updates\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"center top\",\r\n passwordRequired: true,\r\n overview: {\r\n problem:\r\n \"The Roku Visual Design team needed to showcase their vision of a brand refresh initiative to executives.\",\r\n goal: \"Prototype design updates across the entire Roku OS.\",\r\n outcome: \"I presented the prototype to Roku's VP of Design.\",\r\n role: \"I was the lead engineer on this project and worked with another UX Engineer.\",\r\n technologies: \"Vue.js\",\r\n dates: \"July - August 2023\",\r\n },\r\n content: (\r\n <>\r\n

\r\n In fall 2023, Roku plans to release a brand refresh for the Roku\r\n platform. The goals of this refresh are to have better visual appeal and\r\n help users understand the Roku brand. A teammate and I worked with\r\n visual designers to create a prototype that showcases design changes on\r\n every screen of the platform.\r\n

\r\n\r\n \"Roku\r\n

Roku home screen with brand refresh updates

\r\n\r\n

\r\n This was a complicated undertaking given the breadth of changes and a\r\n quick timeline. We ran two-week sprints for this project and prioritized\r\n the features that needed to be fully functional. I took charge of\r\n creating, prioritizing, and assigning Jira tickets for each feature.\r\n

\r\n\r\n

\r\n Theming was an important aspect of the brand refresh and included black,\r\n purple, and red variations. We used CSS variables to dynamically change\r\n color styles. The prototype included a configuration page that allowed\r\n designers to change the theme.\r\n

\r\n\r\n \"Prototype\r\n

Prototype configuration screen

\r\n\r\n

\r\n Many screens within the prototype needed to be fully functional. Some of\r\n the screens I created included \"What to Watch\" (content suggestions), a\r\n sports page, and The Roku Channel (Roku's streaming service).\r\n

\r\n\r\n \"What\r\n

\"What to Watch\" content suggestions

\r\n\r\n \"Sports\r\n

NFL Sports page

\r\n\r\n \"The\r\n

The Roku Channel (Roku's streaming service)

\r\n\r\n

\r\n This project concluded with a presentation to Roku's VP of Design. I\r\n demoed the prototype so the executives could get a realistic experience\r\n of what the changes would be like in product.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default BrandRefresh;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"NBCUX\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst NBCUX = {\r\n title: \"NBCUX Self-Service\",\r\n subTitle: \"Figma plugin for NBC Universal\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"top\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"NBC Universal has many internal enterprise tools and not enough designers to support them. Product managers need to create their own layouts in Figma with minimal support from the design team.\",\r\n goal: \"Make Figma easy to use for non-designers by creating a plugin that generates layouts using design system components.\",\r\n outcome:\r\n \"The first version of the plugin was released to users in January 2024.\",\r\n role: \"I was the lead engineer on this project and worked with another UX Engineer and two designers. This was a short-term contract role.\",\r\n technologies:\r\n \"Figma Plugin API, TypeScript, React, Material UI, GitHub Actions, Jest\",\r\n dates: \"October - December 2023\",\r\n },\r\n content: (\r\n <>\r\n

\r\n NBC Universal uses more than 100 internal enterprise tools. Their design\r\n team has fewer than 10 designers to support these tools, leaving a\r\n design gap. Product managers try to fill this gap but find Figma hard to\r\n use for design work. Additionally, they are often limited to using only\r\n FigJam due to licensing costs. My team developed a Figma plugin to allow\r\n non-designers to self-serve simple layouts.\r\n

\r\n\r\n

\r\n We focused on generating a commonly used page template with a data table\r\n as the MVP for this tool. Users can fill out a form to generate a table\r\n page and specify the components they want to use in the layout. They can\r\n customize how many columns the table has and what the data type of each\r\n column should be. They can also add components such as tabs, a search\r\n bar, breadcrumb navigation, and more.\r\n

\r\n\r\n \"Creating\r\n

Creating a table

\r\n\r\n

\r\n When a template is created, the plugin adds documentation next to it. It\r\n provides a short explanation of how to use the plugin and where to get\r\n help. The documentation itself is a published Figma component the plugin\r\n imports. This way, designers can update the documentation without\r\n developer support.\r\n

\r\n\r\n \"Documentation\"\r\n\r\n

\r\n Users can update template layouts as needed. The update process uses the\r\n current selection to populate the form data in the UI.\r\n

\r\n\r\n \"Updating\r\n

Updating a table

\r\n\r\n

\r\n I created a short demo video of the plugin to share our MVP with new\r\n users.\r\n

\r\n\r\n \r\n\r\n

\r\n As the plugin gains more users, it's helpful to have analytics to tell\r\n us how they use the tool. We want to know who the users are, if they're\r\n using Figma or FigJam, what they are creating and how often. It's also\r\n important to know if they run into errors. I chose{\" \"}\r\n Mixpanel to handle our analytics\r\n because it's easy to use and integrates well with Figma plugins.\r\n

\r\n\r\n \r\n

\r\n \r\n Mixpanel Analytics dashboard\r\n {\" \"}\r\n with preliminary data.\r\n

\r\n\r\n

\r\n This plugin was built with React, TypeScript, and Material UI\r\n components. I wanted to make the code as easy as possible for any future\r\n developers. Due to my short-term contract, I wouldn't be there to\r\n onboard them in the future. I focused on creating great documentation,\r\n commenting code, and writing unit tests. I chose{\" \"}\r\n Notion to host documentation on the\r\n code base and our publication process. I wrote thorough{\" \"}\r\n JSDoc comments for all the functions in\r\n our codebase. I used Jest to write 45\r\n unit tests that automatically ran on pull requests.\r\n

\r\n\r\n

\r\n A pain point of creating Figma plugins is that the publishing process is\r\n manual and you have to upload code in the Figma editor. To work around\r\n this I used figcd,\r\n a command-line Figma plugin publishing tool. I combined it with GitHub\r\n Actions to deploy releases, which took a multi-step process to one click\r\n of a button.\r\n

\r\n\r\n

\r\n The NBCUX Self-Service Figma plugin launched in January 2024.\r\n Development work is now paused while more support and funding is\r\n gathered for the plugin. In the future, the plugin will include more\r\n libraries (Angular, React, and Salesforce), templates, and components.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default NBCUX;\r\n","import IEPShell from \"./IEPShell\";\r\nimport Pyro from \"./Pyro\";\r\nimport Toolkit from \"./Toolkit\";\r\nimport Flow from \"./Flow\";\r\nimport Puffin from \"./Puffin\";\r\nimport Oso from \"./Oso\";\r\nimport UXE from \"./UXE\";\r\nimport BrandRefresh from \"./BrandRefresh\";\r\nimport NBCUX from \"./NBCUX\";\r\n\r\nconst ProjectsList = [\r\n NBCUX,\r\n Flow,\r\n Puffin,\r\n BrandRefresh,\r\n Oso,\r\n UXE,\r\n Toolkit,\r\n Pyro,\r\n IEPShell,\r\n];\r\n\r\nexport default ProjectsList;\r\n","import React, { useState } from \"react\";\r\nimport { FaLock, FaExclamationCircle } from \"react-icons/fa\";\r\nimport \"./PasswordProtector.scss\";\r\n\r\nconst PasswordProtector = ({ authenticate = () => {} }) => {\r\n const [userInput, setUserInput] = useState(\"\");\r\n const [showError, setShowError] = useState(false);\r\n\r\n return (\r\n
\r\n \r\n

\r\n Sorry, the information in this project is confidential and requires a\r\n password. Please email{\" \"}\r\n amtruttmann@gmail.com for access.\r\n

\r\n\r\n
\r\n {\r\n setShowError(!authenticate(userInput));\r\n e.preventDefault();\r\n }}\r\n >\r\n \r\n setUserInput(e.target.value)}\r\n name=\"password\"\r\n autoComplete=\"current-password\"\r\n />\r\n \r\n \r\n \r\n \r\n

Whoops, looks like this password is invalid

\r\n
\r\n
\r\n \r\n );\r\n};\r\n\r\nexport default PasswordProtector;\r\n","import React, { useEffect } from \"react\";\r\nimport { CSSTransition } from \"react-transition-group\";\r\nimport { FaTimesCircle } from \"react-icons/fa\";\r\nimport PasswordProtector from \"./PasswordProtector/PasswordProtector\";\r\nimport \"./ProjectModal.scss\";\r\n\r\nconst ProjectModal = ({\r\n project,\r\n open,\r\n closeModal,\r\n authenticated,\r\n authenticate = () => {},\r\n}) => {\r\n useEffect(() => {\r\n if (authenticated) loadVideos();\r\n }, [authenticated]);\r\n\r\n /* \r\n Videos are set to not preload so the loading doesn't affect the CSS transitions\r\n as the modal animates in. After the modal is done animating, load the videos so\r\n they are ready to play. Use a CSS transition with opacity so it the unloaded video \r\n doesn't show, only the loaded video.\r\n */\r\n const loadVideos = () => {\r\n const videos = document.getElementsByTagName(\"video\");\r\n Array.from(videos).forEach((video) => {\r\n video.preload = \"auto\";\r\n video.style.opacity = \"1\";\r\n });\r\n };\r\n\r\n const modalAnimationFinish = () => {\r\n loadVideos();\r\n\r\n // Focus password input\r\n const passwordInput = document.getElementById(\"passwordInput\");\r\n if (passwordInput) passwordInput.focus();\r\n };\r\n\r\n return (\r\n \r\n
\r\n
\r\n \r\n \r\n \r\n
\r\n

{project.title}

\r\n

{project.subTitle}

\r\n
\r\n {project.passwordRequired && !authenticated ? (\r\n \r\n ) : (\r\n <>\r\n
\r\n

Overview

\r\n {Object.keys(project.overview).map((infoItem) => (\r\n

\r\n {`${infoItem}: `}\r\n {project.overview[infoItem]}\r\n

\r\n ))}\r\n {project.links && project.links.length >= 1 && (\r\n

\r\n Links: \r\n {project.links.map((link, index) => (\r\n \r\n \r\n {link.title}\r\n \r\n {index < project.links.length - 1 && \" | \"}\r\n \r\n ))}\r\n

\r\n )}\r\n
\r\n\r\n
\r\n\r\n {project.content}\r\n \r\n )}\r\n
\r\n
\r\n \r\n );\r\n};\r\n\r\nexport default ProjectModal;\r\n","import React, { useState, useEffect } from \"react\";\r\nimport Container from \"react-bootstrap/Container\";\r\nimport Row from \"react-bootstrap/Row\";\r\nimport Col from \"react-bootstrap/Col\";\r\nimport { FaSun, FaMoon } from \"react-icons/fa\";\r\nimport Header from \"./Header/Header\";\r\nimport ProjectTile from \"./ProjectTile/ProjectTile\";\r\nimport ProjectsList from \"./Projects\";\r\nimport ProjectModal from \"./ProjectModal/ProjectModal\";\r\nimport \"bootstrap/dist/css/bootstrap.min.css\";\r\nimport \"./App.scss\";\r\n\r\nconst password = \"helloworld\";\r\n\r\nfunction App() {\r\n const [authenticated, setAuthenticated] = useState(false);\r\n const [darkTheme, setDarkTheme] = useState(false);\r\n const [modalOpen, setModalOpen] = useState(false);\r\n const [selectedProject, setSelectedProject] = useState(ProjectsList[0]);\r\n\r\n useEffect(() => {\r\n const currentTheme = localStorage.getItem(\"theme\") ?? null;\r\n\r\n // Check for saved color theme\r\n if (currentTheme) {\r\n setDarkTheme(currentTheme === \"dark\");\r\n }\r\n // Check for users preferred color scheme\r\n else if (\r\n window.matchMedia &&\r\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches\r\n ) {\r\n setDarkTheme(true);\r\n }\r\n }, []);\r\n\r\n useEffect(() => {\r\n const theme = darkTheme ? \"dark\" : \"light\";\r\n localStorage.setItem(\"theme\", theme);\r\n document.documentElement.style.setProperty(\"color-scheme\", theme);\r\n document.documentElement.setAttribute(\"data-theme\", theme);\r\n }, [darkTheme]);\r\n\r\n return (\r\n
\r\n
\r\n setDarkTheme(!darkTheme)}\r\n aria-label=\"Change theme\"\r\n title=\"Change theme\"\r\n >\r\n {darkTheme ? : }\r\n \r\n\r\n \r\n \r\n
\r\n \r\n \r\n {ProjectsList.map((project) => (\r\n \r\n setModalOpen(true)}\r\n authenticated={authenticated}\r\n authenticate={(userInput) => {\r\n const isAuthenticated = userInput === password;\r\n setAuthenticated(isAuthenticated);\r\n return isAuthenticated;\r\n }}\r\n />\r\n \r\n ))}\r\n \r\n \r\n\r\n
\r\n

\r\n I designed and built this website from scratch! Check out the code\r\n on{\" \"}\r\n \r\n GitHub\r\n \r\n .\r\n

\r\n
\r\n
\r\n\r\n setModalOpen(false)}\r\n authenticated={authenticated}\r\n authenticate={(userInput) => {\r\n const isAuthenticated = userInput === password;\r\n setAuthenticated(isAuthenticated);\r\n return isAuthenticated;\r\n }}\r\n />\r\n
\r\n );\r\n}\r\n\r\nexport default App;\r\n","import React from 'react';\r\nimport ReactDOM from 'react-dom';\r\nimport App from './App';\r\n\r\nReactDOM.render(\r\n \r\n \r\n ,\r\n document.getElementById('root')\r\n);\r\n"],"sourceRoot":""} \ No newline at end of file diff --git a/static/js/main.4bfc845b.chunk.js b/static/js/main.4bfc845b.chunk.js deleted file mode 100644 index a3d8ff2..0000000 --- a/static/js/main.4bfc845b.chunk.js +++ /dev/null @@ -1,2 +0,0 @@ -(this["webpackJsonpalayna-portfolio"]=this["webpackJsonpalayna-portfolio"]||[]).push([[0],{20:function(e,t,o){},21:function(e,t,o){},22:function(e,t,o){},23:function(e,t,o){},27:function(e,t,o){},28:function(e,t,o){"use strict";o.r(t);var n=o(0),s=o(1),a=o.n(s),i=o(8),r=o.n(i),c=o(5),l=o(12),d=o(10),h=o(13),p=o(2);o(20);var u=function(){return Object(n.jsxs)("header",{className:"header",children:[Object(n.jsx)("h1",{children:"Hi, I'm Alayna"}),Object(n.jsx)("p",{children:"I'm a Senior UX Engineer with more than five years of experience in web-based prototyping and creating internal design tools at NBC Universal, Roku, and Intuit. I'm currently looking for a new role - reach out to amtruttmann@gmail.com if there's a fit!"}),Object(n.jsxs)("div",{className:"linkIcons",children:[Object(n.jsx)("a",{href:"https://www.linkedin.com/in/atruttmann/","aria-label":"LinkedIn",title:"LinkedIn",children:Object(n.jsx)(p.e,{})}),Object(n.jsx)("a",{href:"download/Resume-Alayna-Truttmann.pdf","aria-label":"Resume",title:"Resume",children:Object(n.jsx)(p.d,{})}),Object(n.jsx)("a",{href:"mailto:amtruttmann@gmail.com","aria-label":"Email",title:"Email",children:Object(n.jsx)(p.a,{})}),Object(n.jsx)("a",{href:"https://github.com/atruttmann","aria-label":"GitHub",title:"GitHub",children:Object(n.jsx)(p.c,{})})]})]})},g=(o(21),function(e){var t,o=e.project,s=e.setSelectedProject,a=void 0===s?function(){}:s,i=e.setModalOpen,r=void 0===i?function(){}:i;return Object(n.jsxs)("div",{className:"projectTile",onClick:function(e){a(o),r(!0),e.stopPropagation()},children:[Object(n.jsx)("div",{className:"projectImage",style:{backgroundImage:"url(".concat(o.coverImageSrc,")"),backgroundPosition:null!==(t=o.coverPosition)&&void 0!==t?t:"center"},alt:"Cover for ".concat(o.title)}),Object(n.jsxs)("div",{className:"projectLabel",children:[Object(n.jsx)("h2",{children:o.title}),Object(n.jsx)("p",{className:"body2",children:o.subTitle})]})]})}),m=function(e){return"".concat("","images/").concat(e,"/")},b=m("IEPShell"),j={title:"Intuit Expert Portal",subTitle:"Proof of concept for design updates",coverImageSrc:"".concat(b,"/1.png"),coverPosition:"top left",passwordRequired:!1,overview:{problem:"The portal Intuit customer service agents use needed updates to better align with the Intuit Design System and make usability improvements.",goal:"Create a React prototype with these design updates to be passed off to production engineers.",outcome:"Production engineers were able to reuse my code, which accelerated their release process.",role:"I was the sole developer for this project and partnered with a product design team to create this prototype.",technologies:"React, Styled Components",dates:"November - December 2020"},links:[],content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"For this project, I worked with the Intuit Export Portal design team. This portal is used by Intuit customer service agents (a.k.a. Intuit Experts) to assist customers. In this portal Experts can see customer data while on a call and also manage personal work information such as their schedule and notes."}),Object(n.jsx)("p",{children:'The focus for this prototype was updating the "shell" of the product - the left, top, and right navigation elements. The design team wanted to refresh the components and visual design to align with Intuit Design Systems. They also rearranged some of the navigation content based on feedback from their users. The inner content was not finalized for this phase of prototyping so a responsive column layout was used as a placeholder.'}),Object(n.jsx)("img",{src:"".concat(b,"1.png"),alt:"The Intuit Export Portal Shell"}),Object(n.jsx)("p",{children:'The functionality required for this prototype was to be able to click through the left navigation items and open and close the right drawer. The "Engagements" screen needed to show a header and tabs with client information. Navigation elements also had to behave responsively on smaller screens.'}),Object(n.jsx)("video",{controls:!0,muted:!0,preload:"none",children:Object(n.jsx)("source",{src:"".concat(b,"Demo.mp4"),type:"video/mp4"})}),Object(n.jsx)("p",{className:"caption",children:"This video shows the entire flow of the prototype."}),Object(n.jsx)("p",{children:"The most challenging development aspect of this project was the responsive design of the top navigation. This header included dropdowns, links, and other information that needed to be accessible on smaller screens. These navigation items needed to collapse into an overflow menu."}),Object(n.jsx)("img",{src:"".concat(b,"2.png"),alt:"Milestone dropdown when header is not in overflow."}),Object(n.jsx)("p",{className:"caption",children:"Opening the milestone dropdown when the header is not in an overflow state."}),Object(n.jsx)("img",{src:"".concat(b,"3.png"),alt:"Milestone dropdown when header is overflowing."}),Object(n.jsx)("p",{className:"caption",children:"Accessing the milestone dropdown in an overflow state."}),Object(n.jsx)("p",{children:"Another responsive aspect of this screen was the overflow behavior for tabs. Tabs needed an arrow to show that there were more tabs hidden. When clicked, this arrow needed to scroll the tabs by a set pixel value."}),Object(n.jsx)("img",{src:"".concat(b,"4.png"),alt:"Tabs in an overflow state"}),Object(n.jsx)("p",{children:"After this prototype had been finalized with the design team, I handed off the prototype and the code to the development team. They were able to reuse my work in their production code, which sped up the process to implement these changes."})]})},f=m("Pyro"),w={title:"Pyro",subTitle:"Prototyping tool for Intuit designers",coverImageSrc:"".concat(f,"/1.png"),coverPosition:"center",passwordRequired:!1,overview:{problem:"Intuit designers need a way to quickly create high-fidelity prototypes with interactive components and real user data.",goal:"Develop an intuitive drag-and-drop interface that leverages Intuit Design System components and supports custom data and logic.",outcome:"Pyro 1.0 was released in November 2020. I continued to maintain the tool and release feature updates.",role:"I worked on a team of five Design Technologists. I wore many hats including developing the product, designing new features, leading user testing sessions, and prioritizing our Jira board.",technologies:"React, Firebase",dates:"February 2020 - April 2022"},links:[],content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"Pyro is a custom prototyping tool built by Intuit Design Technologists for Intuit designers. It allows anyone to create prototypes using Intuit Design System components, user data, and logic without writing any code. I have been working on this project since February 2020 improving the editor and creating features that cater to QuickBooks design needs."}),Object(n.jsx)("video",{controls:!0,preload:"none",children:Object(n.jsx)("source",{src:"".concat(f,"Demo.mp4"),type:"video/mp4"})}),Object(n.jsx)("p",{className:"caption",children:"This is the demo video for the initial release of Pyro. Video editing credits go to my awesome colleagues Heather & Lynda."}),Object(n.jsxs)("p",{children:["Pyro leverages ",Object(n.jsx)("a",{href:"https://craft.js.org/",children:"Craft.js"})," with React to create drag and drop functionality in the editor. The prototype data syncs to a ",Object(n.jsx)("a",{href:"https://firebase.google.com/",children:"Firebase"})," backend. Users can grab components from the left-side panel and drag them into the editor. When a component is selected, you can edit its properties in the right-side panel. These components are either custom components built for Pyro or they are imported from Intuit's design system."]}),Object(n.jsx)("img",{src:"".concat(f,"2.png"),alt:"Pyro editor"}),Object(n.jsx)("p",{children:'In addition to changing the style of components, you can also set an "on click" action for the component or conditionally show or hide it. This allows users to build complex prototypes with many pages and branching flows. This feature is particularly important for TurboTax designers who often need to create flows with a series of questions.'}),Object(n.jsx)("img",{src:"".concat(f,"3.png"),alt:"Close up of component editing"}),Object(n.jsx)("p",{children:"Many QuickBooks designers need to incorporate real user data into customer testing sessions to help the customer feel like the prototype is real. Customer data often comes in the form of a list of transactions, and typically a Design Technologist would build a custom React prototype to display this data. We added an editable table component to Pyro that allows designers to upload user data as a CSV, saving us all time!"}),Object(n.jsx)("img",{src:"".concat(f,"4.png"),alt:"Table component"}),Object(n.jsx)("p",{children:"Once Pyro was close to being ready for release, a teammate and I conducted ten user testing sessions with Intuit designers. We wanted to learn if there were any major usability issues blocking the release and get feedback on what features should be added to Pyro. The reaction from our participants was very positive and they were excited to use Pyro"}),Object(n.jsxs)("p",{children:["The main issues that came out of testing were:",Object(n.jsx)("br",{}),"1. The onboarding flow was too long and there was more information than users could process.",Object(n.jsx)("br",{}),"2. Users expected to be able to undo and redo changes. (At the time of testing, this feature was still in development)",Object(n.jsx)("br",{}),"3. Adding a new page to the prototype was not intuitive."]}),Object(n.jsx)("img",{src:"".concat(f,"5.png"),alt:"Testing results"}),Object(n.jsx)("p",{children:"The majority of the issues from user testing were addressed and Pyro released to Intuit designers in November 2020."})]})},y=m("Toolkit"),x={title:"QB Designer Toolkit",subTitle:"Figma plugin for Intuit designers",coverImageSrc:"".concat(y,"/Cover.png"),coverPosition:"center",passwordRequired:!1,overview:{problem:"How can we leverage Figma plugins to improve Intuit designers' workflows?",goal:"Create a Figma plugin that empowers designers to easily add motion, content, and theming to their work.",outcome:"The first version of the plugin was released in April 2021, and over time gained more than 238 users. I continued to maintain the plugin and release feature updates.",role:"I was the lead for this tool. I maintained the design and code of this plugin and promoted it to our users.",technologies:"Figma Plugin API, TypeScript, React",dates:"February 2021 - April 2022"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("h2",{class:"sectionTitle",children:"Version 1"}),Object(n.jsxs)("p",{children:["During the fall of 2020, Intuit designers made the switch to using"," ",Object(n.jsx)("a",{href:"https://www.figma.com/",children:"Figma"})," as their primary design tool. Figma supports adding"," ",Object(n.jsx)("a",{href:"https://www.figma.com/community/plugins?tab=plugins",children:"plugins"}),", which are apps you can install to add functionality and improve your workflow. After experimenting with Figma plugins in a hackathon, I was eager to develop the first Figma plugin for Intuit designers."]}),Object(n.jsx)("p",{children:"A design hurdle I wanted to tackle was supporting theming, particularly dark mode. Dark mode has been a work in progress for Intuit design for some time and is currently an experimental beta setting for QuickBooks. Dark mode is a priority because it is a feature our users expect, and also has accessibility benefits such as better contrast and reduced eye strain. As this feature becomes more used, we need to make sure that designs will work for both light and dark modes."}),Object(n.jsx)("img",{src:"".concat(y,"1.gif"),alt:"Dark mode demo"}),Object(n.jsx)("p",{children:"I created a simple UI that would allow users to toggle both layers and pages between light and dark mode. I also included a color inspector that would display the fill and border colors for a selected layer and show their light and dark mode pairings."}),Object(n.jsx)("img",{src:"".concat(y,"2.png"),alt:"Plugin interface"}),Object(n.jsx)("p",{className:"caption",children:"Inspecting a dark mode design to see the color pairings."}),Object(n.jsxs)("p",{children:["Once I had a solid design, I moved on to developing the functionality using ",Object(n.jsx)("a",{href:"https://www.typescriptlang.org/",children:"TypeScript"})," and"," ",Object(n.jsx)("a",{href:"https://sass-lang.com/",children:"Sass"}),". The plugin analyzes a layer's fill and border colors, finding the appropriate contextual color pairing, and then changing the layer's colors to the new theme.This automatic process is completed in a matter of seconds, which saves designers hours of work in manually changing colors."]}),Object(n.jsx)("video",{controls:!0,preload:"none",children:Object(n.jsx)("source",{src:"".concat(y,"Demo.mp4"),type:"video/mp4"})}),Object(n.jsx)("p",{className:"caption",children:"This quick demo of Dark Mode that appeared within a QB Designer Toolkit instructional video I created."}),Object(n.jsx)("p",{children:"Design Technologists on my team also created two other plugins that focus on motion and content. We combined all three plugins to make it easy for designers to access all of our tools at once. I led the merge effort and refactored our code to use the same visual style and coding standards."}),Object(n.jsx)("p",{children:"Version 1 of this plugin was released to the QuickBooks design community in April 2021. The plugin now has now been installed by 238 users, roughly 2/3 of our total designers."}),Object(n.jsx)("hr",{className:"contentDivider"}),Object(n.jsx)("h2",{class:"sectionTitle",children:"Version 2"}),Object(n.jsx)("p",{children:"After the successful launch of the plugin, I paused development for several months to collect analytics and user feedback. In December 2021, I created a plan to add more functionality to the plugin. I wanted to target three key areas:"}),Object(n.jsx)("h4",{children:"Opportunity #1: Refresh the plugin design"}),Object(n.jsx)("p",{children:"The first version of this plugin had features that were designed by separate teams and lacked a common visual language. The design of the plugin did not support multiple modes of navigation. It was likely that we would need the flexibility to develop more complex UIs in the future."}),Object(n.jsx)("h4",{children:"Opportunity #2: Contextualize analytics data"}),Object(n.jsx)("p",{children:"Adding analytics to a Figma plugin can be a bit of a challenge. Since it is contained within an iFrame, there is no access to certain information most analytics tools need. For the first launch, I developed a simple click counting system for the buttons in the plugin. While this did help me understand which buttons were being used the most, it lacked the contextual data. Which user clicked, and in what file, and at what time? I needed this information to make data-driven decisions about the future of the plugin."}),Object(n.jsx)("h4",{children:"Opportunity #3: Add a requested feature"}),Object(n.jsx)("p",{children:"Currently, the most used feature within the plugin is the content generator. My team did user research to get feedback on this feature and found that a common ask from designers was a way to generate numbers."}),Object(n.jsx)("h4",{children:"Tackling opportunities"}),Object(n.jsxs)("p",{children:["I started by redesigning the plugin to create a common visual language. I chose to use"," ",Object(n.jsx)("a",{href:"https://www.figma.com/community/file/928108847914589057",children:"UI2, Figma's Design System"})," ","as the basis for my design. Using Figma's components and styles helps the plugin blend into Figma's UI and seem like a more natural extension of its capabilities. I also changed the plugin navigation system to include a flyout menu. Moving page navigation into the flyout menu gave each feature room for its own navigational elements."]}),Object(n.jsx)("img",{src:"".concat(y,"/Redesign.png"),alt:"Redesign before and after"}),Object(n.jsx)("p",{className:"caption",children:"Selection of redesigned screens before (left) and after (right)"}),Object(n.jsx)("img",{src:"".concat(y,"/Cover.png"),alt:"Cover art"}),Object(n.jsx)("p",{className:"caption",children:"Redesigned cover art for installation page"}),Object(n.jsxs)("p",{children:["My next step was to add analytics. I chose to use"," ",Object(n.jsx)("a",{href:"https://mixpanel.com/",children:"Mixpanel"})," because of its powerful capabilities and compatibility with Figma plugins. Now, when a user clicks a button I know their name, the file they are using, and their overall activity. I can track monthly active users, view a list of Figma files the plugin is being used in, and see which buttons are clicked the most. This will help me know which features of the plugin are most valuable and should be invested in. I now know who the top users of the plugin are and can ask them for feedback."]}),Object(n.jsx)("img",{src:"".concat(y,"/Mixpanel.png"),alt:"Mixpanel Analytics Dashboard"}),Object(n.jsxs)("p",{className:"caption",children:[Object(n.jsx)("a",{href:"https://mixpanel.com/public/7veU4Lv7JycMp3Ene9z4hu",children:"Mixpanel Analytics dashboard"})," ","with three weeks of data"]}),Object(n.jsx)("p",{children:"Next, I worked on the random number generation feature. QuickBooks designers often create data tables with transactional information such as dates, percentages, and currency values so I wanted to include all of these options in this feature. This feature has the flexibility to add one number or a range of numbers, with options to sort the range. This will reduce the work a designer has to do filling out a table from minutes to seconds."}),Object(n.jsx)("img",{src:"".concat(y,"/Number.png"),alt:"Random number generator"}),Object(n.jsx)("p",{className:"caption",children:"The UI allows users to customize the format of numbers, currencies, and dates"}),Object(n.jsx)("p",{children:"Version 2 was released in January 2022, and the plugin continues to be widely used amongst Intuit designers."})]})},v=m("Flow"),O={title:"Flow",subTitle:"Prototyping tool for web & TV",coverImageSrc:"".concat(v,"/1.png"),coverPosition:"center",passwordRequired:!1,overview:{problem:"Roku designers need a way to easily view their prototypes on TVs for user testing.",goal:"Create an easy to use web platform that exports prototypes to a Roku TV channel.",outcome:"The fist version of Flow was released in August 2022. I continued to maintain the tool and release feature updates.",role:"I was the sole designer and full stack engineer on this project.",technologies:"React, Node.js, Express, AWS Dynamo DB, AWS S3",dates:"April 2022 - September 2023"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"Prototyping is at the heart of the Roku UX Engineering team's focus, but we don't always have time to make every prototype designers request. Creating an internal prototyping tool allows designers to self-serve simple prototypes, freeing up UX Engineers to code complex experiences. Flow empowers designers to create and view their prototypes on a TV and share them with in-person or remote users for testing."}),Object(n.jsx)("p",{children:"The first step I took to make Flow was to plan out the user experience. The experience is split between creating a prototype on the web and viewing a prototype on a Roku channel."}),Object(n.jsx)("img",{src:"".concat(v,"2.png"),alt:"User experience diagram"}),Object(n.jsxs)("p",{children:["I started designing the experience using Roku's web design system components. I iterated on the design using feedback gathered from my team. The primary web views are:",Object(n.jsxs)("ol",{children:[Object(n.jsx)("li",{children:"Login"}),Object(n.jsx)("li",{children:"View all prototypes"}),Object(n.jsx)("li",{children:"Edit prototype"}),Object(n.jsx)("li",{children:"Preview prototype"})]})]}),Object(n.jsx)("img",{src:"".concat(v,"3.png"),alt:"Flow home page"}),Object(n.jsx)("p",{className:"caption",children:"View all of your prototypes on the Flow home page"}),Object(n.jsxs)("p",{children:["I started developing the website first. I used React to build the UI and a Node.js to send data to Dynamo DB and store images in a S3 bucket. I leveraged the open source"," ",Object(n.jsx)("a",{href:"https://reactflow.dev/",children:"React Flow"})," library as the editor for my interactive diagrams."]}),Object(n.jsx)("img",{src:"".concat(v,"4.png"),alt:"Flow editor"}),Object(n.jsx)("p",{className:"caption",children:"Editing a prototype"}),Object(n.jsx)("p",{children:"I built a web preview so users can try out the prototype on the web and fix issues before viewing on the TV."}),Object(n.jsx)("img",{src:"".concat(v,"5.png"),alt:"Prototype preview"}),Object(n.jsx)("p",{className:"caption",children:"Previewing a prototype"}),Object(n.jsxs)("p",{children:["The next phase of my development work was creating the Roku channel. I used an internal technology that functions like to React but works on TV channels. It is similar to the externally available"," ",Object(n.jsx)("a",{href:"https://developer.roku.com/develop",children:"Roku SDK"}),"."]}),Object(n.jsx)("img",{src:"".concat(v,"6.png"),alt:"Roku channel home view"}),Object(n.jsx)("p",{className:"caption",children:"Entering a prototype code on the installed channel"}),Object(n.jsx)("p",{children:"Flow 1.0 was released in August 2022. I created a demo video to introduce users to Flow."}),Object(n.jsx)("video",{controls:!0,preload:"none",poster:"".concat(v,"1.png"),children:Object(n.jsx)("source",{src:"".concat(v,"Demo.mp4"),type:"video/mp4"})}),Object(n.jsx)("p",{children:"As more designers have used the tool I added new features based on their use cases. These features include fade transitions, long-pressing remote buttons, allowing videos, screen reader support, and more. Eventually, I wanted to make a Figma plugin that can export images into Flow to accelerate the design process."})]})},k=m("Puffin"),I={title:"Puffin Bulk Generator",subTitle:"Figma plugin for Roku designers",coverImageSrc:"".concat(k,"/1.png"),coverPosition:"center",passwordRequired:!1,overview:{problem:"Bulk generating assets is an arduous task for designers, and handoff to engineering is a manual, non-standardized process.",goal:"Take the tedious work out of bulk generating assets. Automate work that is currently done with copy/paste, while letting designers have control over tweaking the details. Make it easy to hand off assets to engineers.",outcome:"I released the first version of the plugin in August 2023.",role:"I was the sole designer and developer on this project.",technologies:"Figma Plugin API, TypeScript, React",dates:"April - August 2023"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"A common problem in the Roku design community is that generating tile images is a long process involving a lot of copying and pasting. Tile assets can be a list of TV or movie categories, sports games, local news, and more. Designers need to verify text translations for each tile, which can involve generating 100+ tiles at a time. Tile images must be compressed and follow a naming convention before handing off to engineers."}),Object(n.jsx)("img",{src:"".concat(k,"2.png"),alt:"Example tiles"}),Object(n.jsx)("p",{className:"caption",children:"Example of tiles Roku designers generate"}),Object(n.jsx)("p",{children:"My first thought when hearing about these issues was that a Figma plugin could be a perfect fit to automate many of these tasks. The advantage to using Figma is that designers can keep their work in one tool, and have the functionality they need to customize assets."}),Object(n.jsxs)("p",{children:["When starting my design process I chose to use"," ",Object(n.jsx)("a",{href:"https://www.figma.com/community/file/928108847914589057",children:"UI2, Figma's Design System"}),". I learned from previous experience building plugins that they felt more integrated with Figma when they used the same design system. I chose to swap Figma's traditional blue accent with an electric purple to give it a Roku-themed flair."]}),Object(n.jsx)("p",{children:'The first challenge was understanding the process designers go through to customize each tile. I wanted to make bulk generation a "one-click" experience but found it didn\'t work with the designer\'s workflows. Each tile has to be customized - the color changed or an icon repositioned. It didn\'t make sense to generate 100 tiles all at once if the designer would have to go back and tweak each tile. I shifted my mindset to thinking of it as "applying a transformation" to tiles in a multi-step process.'}),Object(n.jsx)("img",{src:"".concat(k,"3.png"),alt:"User flow"}),Object(n.jsx)("p",{className:"caption",children:"Planning the tile generation flow"}),Object(n.jsx)("p",{children:"The next step was to design the export experience. My design stakeholders requested that I limit the export customizations to simplify the process. With that direction, I added settings for image resolutions, formats, and folder naming. Puffin takes care of standardizing the naming of each layer behind the scenes."}),Object(n.jsx)("p",{children:"Another request from the stakeholders was to add a shortcut for creating Figma components. This feature makes it easy to generate a blank component with aspect ratio variants since Roku tiles all use the same set of aspect ratios."}),Object(n.jsx)("img",{src:"".concat(k,"4.png"),alt:"Puffin screens"}),Object(n.jsx)("p",{className:"caption",children:"Finalized designs"}),Object(n.jsxs)("p",{children:["My previous experience building Figma plugins accelerated the development process. I used a handy"," ",Object(n.jsx)("a",{href:"https://github.com/nirsky/figma-plugin-react-template",children:"React Figma plugin template"})," ","to start the project. I leveraged"," ",Object(n.jsx)("a",{href:"https://github.com/alexandrtovmach/react-figma-plugin-ds",children:"react-figma-plugin-ds"})," ","for design system components."," ",Object(n.jsx)("a",{href:"https://github.com/Donaldcwl/browser-image-compression",children:"browser-image-compression"})," ","and ",Object(n.jsx)("a",{href:"https://github.com/Stuk/jszip",children:"jszip"})," helped me export assets."]}),Object(n.jsx)("p",{children:"Finally, I was ready to share the plugin with the Roku design community. I created a quick overview video to show what the plugin could do."}),Object(n.jsx)("video",{controls:!0,preload:"none",poster:"".concat(k,"1.png"),children:Object(n.jsx)("source",{src:"".concat(k,"Demo.mp4"),type:"video/mp4"})}),Object(n.jsxs)("p",{children:["Puffin 1.0 launched in August 2023. The next step is to gather user feedback to determine changes to make in the next version. I would like to explore stronger image compression techniques such as"," ",Object(n.jsx)("a",{href:"https://en.wikipedia.org/wiki/Quantization_(image_processing)",children:"image quantization"}),"."]})]})},T=m("Oso"),N={title:"Remote backlight interface",subTitle:"Controlling TV remote hardware",coverImageSrc:"".concat(T,"/1.png"),coverPosition:"center top",passwordRequired:!0,overview:{problem:"Roku designers needed to control the LED backlight color and behaviors of an experimental TV remote for user testing.",goal:"Develop a web interface that allows designers to control TV remote settings during experimentation and user testing.",outcome:"Moderators used my interface during user testing sessions to control the remote and gathered useful feedback about the design.",role:"I was the sole designer and software developer on this project. I partnered with a hardware engineer to send signals between the web interface and the remote.",technologies:"React, Web Serial API, Arduino",dates:"September - October 2022"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("img",{src:"".concat(T,"2.png"),alt:"Backlit remotes"}),Object(n.jsx)("p",{className:"caption",children:"Backlit remote prototypes"}),Object(n.jsx)("p",{children:"The next generation of Roku remotes will be backlit, meaning that when a user interacts with the remote it will light up. This will make the buttons easier to see, especially in dark conditions. Roku designers needed to determine the best LED color for the remote. They also needed to find the best activation method for the light. The options were touch (holding remote), proximity (hand is near the remote), or accelerometer (moving remote). Designers also needed to be able to configure how long the light would stay on, and how long the light should fade out."}),Object(n.jsx)("p",{children:"The best way to make decisions about remote backlighting was through user testing. The designers requested to customize remote configurations, and have shortcuts to show different configurations during testing. This is where I came in to design and develop an interface the designers could use."}),Object(n.jsxs)("p",{children:["A hardware engineer on my team had built several remotes with an"," ",Object(n.jsx)("a",{href:"https://www.arduino.cc/",children:"Arduino"})," that could change the remote's settings. It was possible to have a website communicate with the remote's Arduino using the"," ",Object(n.jsx)("a",{href:"https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API",children:"Web Serial API"}),". The challenge was that I had never used the Web Serial API and I had only a few weeks to design and build this project."]}),Object(n.jsxs)("p",{children:["I got to work right away. I leveraged several of Roku's web design system components to build the interface faster. There were several custom components I needed to build. I created a color picker that could handle both regular RGB colors as well as"," ",Object(n.jsx)("a",{href:"https://giggster.com/guide/color-temperature-chart/",children:"color temperatures"}),". Most of the remote button lights would be a shade of warm white. Certain buttons that launched channels would use a brand color, such as red for Netflix.."]}),Object(n.jsx)("img",{src:"".concat(T,"3.png"),alt:"Web interface"}),Object(n.jsx)("p",{className:"caption",children:"Settings used to control the remote"}),Object(n.jsx)("p",{children:"In addition to the main control dashboard I gave designers the ability to create preset configurations. Presets were important because the user testing moderator needed to be able to show users different variations during the session. I set it up so they could apply many settings at once with the click of a button."}),Object(n.jsx)("img",{src:"".concat(T,"4.png"),alt:"Preset creation"}),Object(n.jsx)("p",{className:"caption",children:"Creating a preset configuration"}),Object(n.jsx)("p",{children:"User testing sessions showed that the reaction to backlit remotes was positive. Preference for the light activation method (touch, proximity, accelerometer) was mixed."}),Object(n.jsx)("p",{children:"I learned a lot about communicating with Arduinos in this project. If I had more time to work on it, I would have iterated on the design and asked for more feedback. The interface was quite complicated to use due to the number of settings and customization needed, but could have room for improvement on simplicity. However, I would call delivering a complicated prototype on a tight timeline a win."})]})},R=m("UXE"),P={title:"Roku UXE Team Site",subTitle:"Showcasing tools & prototypes",coverImageSrc:"".concat(R,"/1.png"),coverPosition:"center top",passwordRequired:!1,overview:{problem:"The Roku UX Engineering team needed a place to share their tools, prototypes, and roadmap.",goal:"Create a beautiful website that informs other designers about UX Engineering.",outcome:"The Roku design community was able to learn more about our team's work and how to connect with us.",role:"I was the sole designer and full stack engineer on this project.",technologies:"React, Node.js, AWS Dynamo DB, AWS S3, Jira API",dates:"September 2022 - August 2023"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"The Roku UX Engineering team needed a way to increase our visibility to the rest of the design organization. A basic Confluence page would not do! This website needed to include our internal tools, prototypes, team roadmap, and FAQs about working with our team."}),Object(n.jsx)("p",{children:"When I started designing this project, I knew I wanted to use striking colors and lots of Roku purple. I was particularly inspired by an ad campaign Roku had done recently. I loved the rounded shapes and gradients and wanted to incorporate them in my designs. My goal was for the website to be attractive to designers and show off our UX Engineering front-end skills."}),Object(n.jsx)("img",{src:"".concat(R,"2.jpg"),alt:"Roku billboard ad"}),Object(n.jsx)("p",{children:"The first section of the site shows a list of our internal tools. Each tool has an accompanying video or image with links to its site. The tab arrangement on the left allows you to browse the details for each tool."}),Object(n.jsx)("img",{src:"".concat(R,"1.png"),alt:"Internal tools"}),Object(n.jsx)("p",{children:"The next section shows the prototypes the team has worked on. The list style mimics the experience of browsing content on a Roku device. You can search for prototypes, which is helpful because there are more than 60 prototypes to view."}),Object(n.jsx)("img",{src:"".concat(R,"3.png"),alt:"Viewing prototypes"}),Object(n.jsx)("p",{children:"Each prototype can open in a modal. There you can see more project details and interact with prototype in an iframe."}),Object(n.jsx)("img",{src:"".concat(R,"4.png"),alt:"Prototypes detail view"}),Object(n.jsx)("p",{children:"I also added a hidden form where UX Engineers could add more prototypes to the list. The data is saved to AWS Dynamo DB and images are uploaded to AWS S3."}),Object(n.jsx)("img",{src:"".concat(R,"5.png"),alt:"Add prototype"}),Object(n.jsxs)("p",{children:["The roadmap section displayed a calendar view of the projects the team is working on. This view syncs with Jira using the"," ",Object(n.jsx)("a",{href:"https://developer.atlassian.com/server/jira/platform/rest-apis/",children:"Jira API"}),", so roadmap updates are automatic."]}),Object(n.jsx)("img",{src:"".concat(R,"6.png"),alt:"Project roadmap"}),Object(n.jsx)("p",{children:"The FAQ section lets other designers know how best to work with our team. It includes two quizzes to assess whether a coded prototype is necessary and approximately how long a prototype will take to develop."}),Object(n.jsx)("img",{src:"".concat(R,"7.png"),alt:"FAQ section"}),Object(n.jsx)("img",{src:"".concat(R,"8.png"),alt:"Prototype quiz"})]})},S=m("BrandRefresh"),F={title:"Roku OS Brand Refresh",subTitle:"Prototype of design updates",coverImageSrc:"".concat(S,"/1.png"),coverPosition:"center top",passwordRequired:!0,overview:{problem:"The Roku Visual Design team needed to showcase their vision of a brand refresh initiative to executives.",goal:"Prototype design updates across the entire Roku OS.",outcome:"I presented the prototype to Roku's VP of Design.",role:"I was the lead engineer on this project and worked with another UX Engineer.",technologies:"Vue.js",dates:"July - August 2023"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"In fall 2023, Roku plans to release a brand refresh for the Roku platform. The goals of this refresh are to have better visual appeal and help users understand the Roku brand. A teammate and I worked with visual designers to create a prototype that showcases design changes on every screen of the platform."}),Object(n.jsx)("img",{src:"".concat(S,"1.png"),alt:"Roku home screen"}),Object(n.jsx)("p",{className:"caption",children:"Roku home screen with brand refresh updates"}),Object(n.jsx)("p",{children:"This was a complicated undertaking given the breadth of changes and a quick timeline. We ran two-week sprints for this project and prioritized the features that needed to be fully functional. I took charge of creating, prioritizing, and assigning Jira tickets for each feature."}),Object(n.jsx)("p",{children:"Theming was an important aspect of the brand refresh and included black, purple, and red variations. We used CSS variables to dynamically change color styles. The prototype included a configuration page that allowed designers to change the theme."}),Object(n.jsx)("img",{src:"".concat(S,"2.png"),alt:"Prototype configuration"}),Object(n.jsx)("p",{className:"caption",children:"Prototype configuration screen"}),Object(n.jsx)("p",{children:'Many screens within the prototype needed to be fully functional. Some of the screens I created included "What to Watch" (content suggestions), a sports page, and The Roku Channel (Roku\'s streaming service).'}),Object(n.jsx)("img",{src:"".concat(S,"3.png"),alt:"What to Watch page"}),Object(n.jsx)("p",{className:"caption",children:'"What to Watch" content suggestions'}),Object(n.jsx)("img",{src:"".concat(S,"4.png"),alt:"Sports page"}),Object(n.jsx)("p",{className:"caption",children:"NFL Sports page"}),Object(n.jsx)("img",{src:"".concat(S,"5.png"),alt:"The Roku Channel"}),Object(n.jsx)("p",{className:"caption",children:"The Roku Channel (Roku's streaming service)"}),Object(n.jsx)("p",{children:"This project concluded with a presentation to Roku's VP of Design. I demoed the prototype so the executives could get a realistic experience of what the changes would be like in product."})]})},A=m("NBCUX"),D=[{title:"NBCUX Self-Service",subTitle:"Figma plugin for NBC Universal",coverImageSrc:"".concat(A,"/1.png"),coverPosition:"top",passwordRequired:!1,overview:{problem:"NBC Universal has many internal enterprise tools and not enough designers to support them. Product managers need to create their own layouts in Figma with minimal support from the design team.",goal:"Make Figma easy to use for non-designers by creating a plugin that generates layouts using design system components.",outcome:"The first version of the plugin was released to users in January 2024.",role:"I was the lead engineer on this project and worked with another UX Engineer and two designers. This was a short-term contract role.",technologies:"Figma Plugin API, TypeScript, React, Material UI, GitHub Actions, Jest",dates:"October - December 2023"},content:Object(n.jsxs)(n.Fragment,{children:[Object(n.jsx)("p",{children:"NBC Universal uses more than 100 internal enterprise tools. Their design team has fewer than 10 designers to support these tools, leaving a design gap. Product managers try to fill this gap but find Figma hard to use for design work. Additionally, they are often limited to using only FigJam due to licensing costs. My team developed a Figma plugin to allow non-designers to self-serve simple layouts."}),Object(n.jsx)("p",{children:"We focused on generating a commonly used page template with a data table as the MVP for this tool. Users can fill out a form to generate a table page and specify the components they want to use in the layout. They can customize how many columns the table has and what the data type of each column should be. They can also add components such as tabs, a search bar, breadcrumb navigation, and more."}),Object(n.jsx)("img",{src:"".concat(A,"/2.png"),alt:"Creating a table"}),Object(n.jsx)("p",{className:"caption",children:"Creating a table"}),Object(n.jsx)("p",{children:"When a template is created, the plugin adds documentation next to it. It provides a short explanation of how to use the plugin and where to get help. The documentation itself is a published Figma component the plugin imports. This way, designers can update the documentation without developer support."}),Object(n.jsx)("img",{src:"".concat(A,"/3.png"),alt:"Documentation"}),Object(n.jsx)("p",{children:"Users can update template layouts as needed. The update process uses the current selection to populate the form data in the UI."}),Object(n.jsx)("img",{src:"".concat(A,"/4.png"),alt:"Updating a table"}),Object(n.jsx)("p",{className:"caption",children:"Updating a table"}),Object(n.jsx)("p",{children:"I created a short demo video of the plugin to share our MVP with new users."}),Object(n.jsx)("video",{controls:!0,preload:"none",poster:"".concat(A,"1.png"),children:Object(n.jsx)("source",{src:"".concat(A,"Demo.mp4"),type:"video/mp4"})}),Object(n.jsxs)("p",{children:["As the plugin gains more users, it's helpful to have analytics to tell us how they use the tool. We want to know who the users are, if they're using Figma or FigJam, what they are creating and how often. It's also important to know if they run into errors. I chose"," ",Object(n.jsx)("a",{href:"https://mixpanel.com/",children:"Mixpanel"})," to handle our analytics because it's easy to use and integrates well with Figma plugins."]}),Object(n.jsx)("img",{src:"".concat(A,"/Mixpanel.png"),alt:"Mixpanel Analytics Dashboard"}),Object(n.jsxs)("p",{className:"caption",children:[Object(n.jsx)("a",{href:"https://mixpanel.com/public/6fPuCsME2BR7Ra7NECbdgx",children:"Mixpanel Analytics dashboard"})," ","with preliminary data."]}),Object(n.jsxs)("p",{children:["This plugin was built with React, TypeScript, and Material UI components. I wanted to make the code as easy as possible for any future developers. Due to my short-term contract, I wouldn't be there to onboard them in the future. I focused on creating great documentation, commenting code, and writing unit tests. I chose"," ",Object(n.jsx)("a",{href:"https://www.notion.so/",children:"Notion"})," to host documentation on the code base and our publication process. I wrote thorough"," ",Object(n.jsx)("a",{href:"https://jsdoc.app/",children:"JSDoc"})," comments for all the functions in our codebase. I used ",Object(n.jsx)("a",{href:"https://jestjs.io/",children:"Jest"})," to write 45 unit tests that automatically ran on pull requests."]}),Object(n.jsxs)("p",{children:["A pain point of creating Figma plugins is that the publishing process is manual and you have to upload code in the Figma editor. To work around this I used ",Object(n.jsx)("a",{href:"https://github.com/parrot-global/figcd",children:"figcd"}),", a command-line Figma plugin publishing tool. I combined it with GitHub Actions to deploy releases, which took a multi-step process to one click of a button."]}),Object(n.jsx)("p",{children:"The NBCUX Self-Service Figma plugin launched in January 2024. Development work is now paused while more support and funding is gathered for the plugin. In the future, the plugin will include more libraries (Angular, React, and Salesforce), templates, and components."})]})},O,I,F,N,P,x,w,j],C=o(30),E=(o(22),function(e){var t=e.authenticate,o=void 0===t?function(){}:t,a=Object(s.useState)(""),i=Object(c.a)(a,2),r=i[0],l=i[1],d=Object(s.useState)(!1),h=Object(c.a)(d,2),u=h[0],g=h[1];return Object(n.jsxs)("div",{className:"passwordContainer",children:[Object(n.jsx)(p.f,{className:"passwordIcon"}),Object(n.jsxs)("p",{className:"body2",children:["Sorry, the information in this project is confidential and requires a password. Please email"," ",Object(n.jsx)("span",{className:"email",children:"amtruttmann@gmail.com"})," for access."]}),Object(n.jsxs)("div",{className:"formContainer",children:[Object(n.jsxs)("form",{className:"passwordForm",onSubmit:function(e){g(!o(r)),e.preventDefault()},children:[Object(n.jsx)("input",{type:"text",autoComplete:"username",style:{display:"none"}}),Object(n.jsx)("input",{id:"passwordInput",value:r,type:"password",placeholder:"Password","aria-label":"Password",className:"passwordInput",onChange:function(e){return l(e.target.value)},name:"password",autoComplete:"current-password"}),Object(n.jsx)("input",{type:"submit",value:"Access",className:"passwordButton"})]}),Object(n.jsxs)("div",{className:"passwordError",style:u?{visibility:"visible"}:{visibility:"hidden"},children:[Object(n.jsx)(p.b,{className:"passwordWarning"}),Object(n.jsx)("p",{className:"body3",children:"Whoops, looks like this password is invalid"})]})]})]})}),U=(o(23),function(e){var t=e.project,o=e.open,a=e.closeModal,i=e.authenticated,r=e.authenticate,c=void 0===r?function(){}:r;Object(s.useEffect)((function(){i&&l()}),[i]);var l=function(){var e=document.getElementsByTagName("video");Array.from(e).forEach((function(e){e.preload="auto",e.style.opacity="1"}))};return Object(n.jsx)(C.a,{in:o,timeout:400,unmountOnExit:!0,onEntered:function(){l();var e=document.getElementById("passwordInput");e&&e.focus()},children:Object(n.jsx)("div",{className:"modalContainer",children:Object(n.jsxs)("div",{className:"projectContent",children:[Object(n.jsx)("button",{className:"closeButton",onClick:a,"aria-label":"Close modal",children:Object(n.jsx)(p.i,{className:"closeIcon"})}),Object(n.jsxs)("div",{className:"projectHeader",children:[Object(n.jsx)("h1",{children:t.title}),Object(n.jsx)("h3",{children:t.subTitle})]}),t.passwordRequired&&!i?Object(n.jsx)(E,{authenticate:c}):Object(n.jsxs)(n.Fragment,{children:[Object(n.jsxs)("div",{className:"overview",children:[Object(n.jsx)("h2",{className:"sectionTitle",children:"Overview"}),Object.keys(t.overview).map((function(e){return Object(n.jsxs)("p",{children:[Object(n.jsx)("b",{children:"".concat(e,": ")}),t.overview[e]]},e)})),t.links&&t.links.length>=1&&Object(n.jsxs)("p",{children:[Object(n.jsx)("b",{children:"Links: "}),t.links.map((function(e,o){return Object(n.jsxs)("span",{children:[Object(n.jsx)("a",{href:e.url,children:e.title},e.title),o\r\n

Hi, I'm Alayna

\r\n

\r\n I'm a Senior UX Engineer with more than five years of experience in\r\n web-based prototyping and creating internal design tools at NBC\r\n Universal, Roku, and Intuit. I'm currently looking for a new role -\r\n reach out to amtruttmann@gmail.com if there's a fit!\r\n

\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n \r\n );\r\n}\r\n\r\nexport default Header;\r\n","import \"./ProjectTile.scss\";\r\n\r\nconst ProjectTile = ({\r\n project,\r\n setSelectedProject = () => {},\r\n setModalOpen = () => {},\r\n}) => {\r\n return (\r\n {\r\n setSelectedProject(project);\r\n setModalOpen(true);\r\n e.stopPropagation();\r\n }}\r\n >\r\n \r\n
\r\n

{project.title}

\r\n

{project.subTitle}

\r\n
\r\n \r\n );\r\n};\r\n\r\nexport default ProjectTile;\r\n","const getImgPrefix = (imgFolder) => {\r\n return `${process.env.PUBLIC_URL}images/${imgFolder}/`;\r\n};\r\nexport default getImgPrefix;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"IEPShell\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst IEPShell = {\r\n title: \"Intuit Expert Portal\",\r\n subTitle: \"Proof of concept for design updates\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"top left\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"The portal Intuit customer service agents use needed updates to better align with the Intuit Design System and make usability improvements.\",\r\n goal: \"Create a React prototype with these design updates to be passed off to production engineers.\",\r\n outcome:\r\n \"Production engineers were able to reuse my code, which accelerated their release process.\",\r\n role: \"I was the sole developer for this project and partnered with a product design team to create this prototype.\",\r\n technologies: \"React, Styled Components\",\r\n dates: \"November - December 2020\",\r\n },\r\n links: [],\r\n content: (\r\n <>\r\n

\r\n For this project, I worked with the Intuit Export Portal design team.\r\n This portal is used by Intuit customer service agents (a.k.a. Intuit\r\n Experts) to assist customers. In this portal Experts can see customer\r\n data while on a call and also manage personal work information such as\r\n their schedule and notes.\r\n

\r\n

\r\n The focus for this prototype was updating the \"shell\" of the product -\r\n the left, top, and right navigation elements. The design team wanted to\r\n refresh the components and visual design to align with Intuit Design\r\n Systems. They also rearranged some of the navigation content based on\r\n feedback from their users. The inner content was not finalized for this\r\n phase of prototyping so a responsive column layout was used as a\r\n placeholder.\r\n

\r\n \"The\r\n\r\n

\r\n The functionality required for this prototype was to be able to click\r\n through the left navigation items and open and close the right drawer.\r\n The \"Engagements\" screen needed to show a header and tabs with client\r\n information. Navigation elements also had to behave responsively on\r\n smaller screens.\r\n

\r\n \r\n

\r\n This video shows the entire flow of the prototype.\r\n

\r\n\r\n

\r\n The most challenging development aspect of this project was the\r\n responsive design of the top navigation. This header included dropdowns,\r\n links, and other information that needed to be accessible on smaller\r\n screens. These navigation items needed to collapse into an overflow\r\n menu.\r\n

\r\n \r\n

\r\n Opening the milestone dropdown when the header is not in an overflow\r\n state.\r\n

\r\n\r\n \r\n

\r\n Accessing the milestone dropdown in an overflow state.\r\n

\r\n\r\n

\r\n Another responsive aspect of this screen was the overflow behavior for\r\n tabs. Tabs needed an arrow to show that there were more tabs hidden.\r\n When clicked, this arrow needed to scroll the tabs by a set pixel value.\r\n

\r\n \"Tabs\r\n\r\n

\r\n After this prototype had been finalized with the design team, I handed\r\n off the prototype and the code to the development team. They were able\r\n to reuse my work in their production code, which sped up the process to\r\n implement these changes.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default IEPShell;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"Pyro\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst Pyro = {\r\n title: \"Pyro\",\r\n subTitle: \"Prototyping tool for Intuit designers\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"center\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"Intuit designers need a way to quickly create high-fidelity prototypes with interactive components and real user data.\",\r\n goal: \"Develop an intuitive drag-and-drop interface that leverages Intuit Design System components and supports custom data and logic.\",\r\n outcome:\r\n \"Pyro 1.0 was released in November 2020. I continued to maintain the tool and release feature updates.\",\r\n role: \"I worked on a team of five Design Technologists. I wore many hats including developing the product, designing new features, leading user testing sessions, and prioritizing our Jira board.\",\r\n technologies: \"React, Firebase\",\r\n dates: \"February 2020 - April 2022\",\r\n },\r\n links: [],\r\n content: (\r\n <>\r\n

\r\n Pyro is a custom prototyping tool built by Intuit Design Technologists\r\n for Intuit designers. It allows anyone to create prototypes using Intuit\r\n Design System components, user data, and logic without writing any code.\r\n I have been working on this project since February 2020 improving the\r\n editor and creating features that cater to QuickBooks design needs.\r\n

\r\n\r\n \r\n

\r\n This is the demo video for the initial release of Pyro. Video editing\r\n credits go to my awesome colleagues Heather & Lynda.\r\n

\r\n\r\n

\r\n Pyro leverages Craft.js with React\r\n to create drag and drop functionality in the editor. The prototype data\r\n syncs to a Firebase backend.\r\n Users can grab components from the left-side panel and drag them into\r\n the editor. When a component is selected, you can edit its properties in\r\n the right-side panel. These components are either custom components\r\n built for Pyro or they are imported from Intuit's design system.\r\n

\r\n \"Pyro\r\n

\r\n In addition to changing the style of components, you can also set an \"on\r\n click\" action for the component or conditionally show or hide it. This\r\n allows users to build complex prototypes with many pages and branching\r\n flows. This feature is particularly important for TurboTax designers who\r\n often need to create flows with a series of questions.\r\n

\r\n \"Close\r\n

\r\n Many QuickBooks designers need to incorporate real user data into\r\n customer testing sessions to help the customer feel like the prototype\r\n is real. Customer data often comes in the form of a list of\r\n transactions, and typically a Design Technologist would build a custom\r\n React prototype to display this data. We added an editable table\r\n component to Pyro that allows designers to upload user data as a CSV,\r\n saving us all time!\r\n

\r\n \"Table\r\n

\r\n Once Pyro was close to being ready for release, a teammate and I\r\n conducted ten user testing sessions with Intuit designers. We wanted to\r\n learn if there were any major usability issues blocking the release and\r\n get feedback on what features should be added to Pyro. The reaction from\r\n our participants was very positive and they were excited to use Pyro\r\n

\r\n

\r\n The main issues that came out of testing were:\r\n
\r\n 1. The onboarding flow was too long and there was more information than\r\n users could process.\r\n
\r\n 2. Users expected to be able to undo and redo changes. (At the time of\r\n testing, this feature was still in development)\r\n
\r\n 3. Adding a new page to the prototype was not intuitive.\r\n

\r\n \"Testing\r\n\r\n

\r\n The majority of the issues from user testing were addressed and Pyro\r\n released to Intuit designers in November 2020.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default Pyro;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"Toolkit\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst Toolkit = {\r\n title: \"QB Designer Toolkit\",\r\n subTitle: \"Figma plugin for Intuit designers\",\r\n coverImageSrc: `${imgPrefix}/Cover.png`,\r\n coverPosition: \"center\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"How can we leverage Figma plugins to improve Intuit designers' workflows?\",\r\n goal: \"Create a Figma plugin that empowers designers to easily add motion, content, and theming to their work.\",\r\n outcome:\r\n \"The first version of the plugin was released in April 2021, and over time gained more than 238 users. I continued to maintain the plugin and release feature updates.\",\r\n role: \"I was the lead for this tool. I maintained the design and code of this plugin and promoted it to our users.\",\r\n technologies: \"Figma Plugin API, TypeScript, React\",\r\n dates: \"February 2021 - April 2022\",\r\n },\r\n content: (\r\n <>\r\n

Version 1

\r\n

\r\n During the fall of 2020, Intuit designers made the switch to using{\" \"}\r\n Figma as their primary design tool.\r\n Figma supports adding{\" \"}\r\n \r\n plugins\r\n \r\n , which are apps you can install to add functionality and improve your\r\n workflow. After experimenting with Figma plugins in a hackathon, I was\r\n eager to develop the first Figma plugin for Intuit designers.\r\n

\r\n

\r\n A design hurdle I wanted to tackle was supporting theming, particularly\r\n dark mode. Dark mode has been a work in progress for Intuit design for\r\n some time and is currently an experimental beta setting for QuickBooks.\r\n Dark mode is a priority because it is a feature our users expect, and\r\n also has accessibility benefits such as better contrast and reduced eye\r\n strain. As this feature becomes more used, we need to make sure that\r\n designs will work for both light and dark modes.\r\n

\r\n \"Dark\r\n

\r\n I created a simple UI that would allow users to toggle both layers and\r\n pages between light and dark mode. I also included a color inspector\r\n that would display the fill and border colors for a selected layer and\r\n show their light and dark mode pairings.\r\n

\r\n \"Plugin\r\n

\r\n Inspecting a dark mode design to see the color pairings.\r\n

\r\n

\r\n Once I had a solid design, I moved on to developing the functionality\r\n using TypeScript and{\" \"}\r\n Sass. The plugin analyzes a layer's\r\n fill and border colors, finding the appropriate contextual color\r\n pairing, and then changing the layer's colors to the new theme.This\r\n automatic process is completed in a matter of seconds, which saves\r\n designers hours of work in manually changing colors.\r\n

\r\n \r\n

\r\n This quick demo of Dark Mode that appeared within a QB Designer Toolkit\r\n instructional video I created.\r\n

\r\n

\r\n Design Technologists on my team also created two other plugins that\r\n focus on motion and content. We combined all three plugins to make it\r\n easy for designers to access all of our tools at once. I led the merge\r\n effort and refactored our code to use the same visual style and coding\r\n standards.\r\n

\r\n

\r\n Version 1 of this plugin was released to the QuickBooks design community\r\n in April 2021. The plugin now has now been installed by 238 users,\r\n roughly 2/3 of our total designers.\r\n

\r\n\r\n
\r\n

Version 2

\r\n

\r\n After the successful launch of the plugin, I paused development for\r\n several months to collect analytics and user feedback. In December 2021,\r\n I created a plan to add more functionality to the plugin. I wanted to\r\n target three key areas:\r\n

\r\n

Opportunity #1: Refresh the plugin design

\r\n

\r\n The first version of this plugin had features that were designed by\r\n separate teams and lacked a common visual language. The design of the\r\n plugin did not support multiple modes of navigation. It was likely that\r\n we would need the flexibility to develop more complex UIs in the future.\r\n

\r\n

Opportunity #2: Contextualize analytics data

\r\n

\r\n Adding analytics to a Figma plugin can be a bit of a challenge. Since it\r\n is contained within an iFrame, there is no access to certain information\r\n most analytics tools need. For the first launch, I developed a simple\r\n click counting system for the buttons in the plugin. While this did help\r\n me understand which buttons were being used the most, it lacked the\r\n contextual data. Which user clicked, and in what file, and at what time?\r\n I needed this information to make data-driven decisions about the future\r\n of the plugin.\r\n

\r\n

Opportunity #3: Add a requested feature

\r\n

\r\n Currently, the most used feature within the plugin is the content\r\n generator. My team did user research to get feedback on this feature and\r\n found that a common ask from designers was a way to generate numbers.\r\n

\r\n

Tackling opportunities

\r\n

\r\n I started by redesigning the plugin to create a common visual language.\r\n I chose to use{\" \"}\r\n \r\n UI2, Figma's Design System\r\n {\" \"}\r\n as the basis for my design. Using Figma's components and styles helps\r\n the plugin blend into Figma's UI and seem like a more natural extension\r\n of its capabilities. I also changed the plugin navigation system to\r\n include a flyout menu. Moving page navigation into the flyout menu gave\r\n each feature room for its own navigational elements.\r\n

\r\n \"Redesign\r\n

\r\n Selection of redesigned screens before (left) and after (right)\r\n

\r\n \"Cover\r\n

Redesigned cover art for installation page

\r\n

\r\n My next step was to add analytics. I chose to use{\" \"}\r\n Mixpanel because of its powerful\r\n capabilities and compatibility with Figma plugins. Now, when a user\r\n clicks a button I know their name, the file they are using, and their\r\n overall activity. I can track monthly active users, view a list of Figma\r\n files the plugin is being used in, and see which buttons are clicked the\r\n most. This will help me know which features of the plugin are most\r\n valuable and should be invested in. I now know who the top users of the\r\n plugin are and can ask them for feedback.\r\n

\r\n \r\n

\r\n \r\n Mixpanel Analytics dashboard\r\n {\" \"}\r\n with three weeks of data\r\n

\r\n

\r\n Next, I worked on the random number generation feature. QuickBooks\r\n designers often create data tables with transactional information such\r\n as dates, percentages, and currency values so I wanted to include all of\r\n these options in this feature. This feature has the flexibility to add\r\n one number or a range of numbers, with options to sort the range. This\r\n will reduce the work a designer has to do filling out a table from\r\n minutes to seconds.\r\n

\r\n \"Random\r\n

\r\n The UI allows users to customize the format of numbers, currencies, and\r\n dates\r\n

\r\n

\r\n Version 2 was released in January 2022, and the plugin continues to be\r\n widely used amongst Intuit designers.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default Toolkit;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"Flow\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst Flow = {\r\n title: \"Flow\",\r\n subTitle: \"Prototyping tool for web & TV\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"center\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"Roku designers need a way to easily view their prototypes on TVs for user testing.\",\r\n goal: \"Create an easy to use web platform that exports prototypes to a Roku TV channel.\",\r\n outcome:\r\n \"The fist version of Flow was released in August 2022. I continued to maintain the tool and release feature updates.\",\r\n role: \"I was the sole designer and full stack engineer on this project.\",\r\n technologies: \"React, Node.js, Express, AWS Dynamo DB, AWS S3\",\r\n dates: \"April 2022 - September 2023\",\r\n },\r\n content: (\r\n <>\r\n

\r\n Prototyping is at the heart of the Roku UX Engineering team's focus, but\r\n we don't always have time to make every prototype designers request.\r\n Creating an internal prototyping tool allows designers to self-serve\r\n simple prototypes, freeing up UX Engineers to code complex experiences.\r\n Flow empowers designers to create and view their prototypes on a TV and\r\n share them with in-person or remote users for testing.\r\n

\r\n\r\n

\r\n The first step I took to make Flow was to plan out the user experience.\r\n The experience is split between creating a prototype on the web and\r\n viewing a prototype on a Roku channel.\r\n

\r\n\r\n \"User\r\n\r\n

\r\n I started designing the experience using Roku's web design system\r\n components. I iterated on the design using feedback gathered from my\r\n team. The primary web views are:\r\n

    \r\n
  1. Login
  2. \r\n
  3. View all prototypes
  4. \r\n
  5. Edit prototype
  6. \r\n
  7. Preview prototype
  8. \r\n
\r\n

\r\n\r\n \"Flow\r\n

\r\n View all of your prototypes on the Flow home page\r\n

\r\n\r\n

\r\n I started developing the website first. I used React to build the UI and\r\n a Node.js to send data to Dynamo DB and store images in a S3 bucket. I\r\n leveraged the open source{\" \"}\r\n React Flow library as the editor\r\n for my interactive diagrams.\r\n

\r\n \"Flow\r\n

Editing a prototype

\r\n\r\n

\r\n I built a web preview so users can try out the prototype on the web and\r\n fix issues before viewing on the TV.\r\n

\r\n \"Prototype\r\n

Previewing a prototype

\r\n\r\n

\r\n The next phase of my development work was creating the Roku channel. I\r\n used an internal technology that functions like to React but works on TV\r\n channels. It is similar to the externally available{\" \"}\r\n Roku SDK.\r\n

\r\n \"Roku\r\n

\r\n Entering a prototype code on the installed channel\r\n

\r\n\r\n

\r\n Flow 1.0 was released in August 2022. I created a demo video to\r\n introduce users to Flow.\r\n

\r\n\r\n \r\n\r\n

\r\n As more designers have used the tool I added new features based on their\r\n use cases. These features include fade transitions, long-pressing remote\r\n buttons, allowing videos, screen reader support, and more. Eventually, I\r\n wanted to make a Figma plugin that can export images into Flow to\r\n accelerate the design process.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default Flow;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"Puffin\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst Puffin = {\r\n title: \"Puffin Bulk Generator\",\r\n subTitle: \"Figma plugin for Roku designers\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"center\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"Bulk generating assets is an arduous task for designers, and handoff to engineering is a manual, non-standardized process.\",\r\n goal: \"Take the tedious work out of bulk generating assets. Automate work that is currently done with copy/paste, while letting designers have control over tweaking the details. Make it easy to hand off assets to engineers.\",\r\n outcome: \"I released the first version of the plugin in August 2023.\",\r\n role: \"I was the sole designer and developer on this project.\",\r\n technologies: \"Figma Plugin API, TypeScript, React\",\r\n dates: \"April - August 2023\",\r\n },\r\n content: (\r\n <>\r\n

\r\n A common problem in the Roku design community is that generating tile\r\n images is a long process involving a lot of copying and pasting. Tile\r\n assets can be a list of TV or movie categories, sports games, local\r\n news, and more. Designers need to verify text translations for each\r\n tile, which can involve generating 100+ tiles at a time. Tile images\r\n must be compressed and follow a naming convention before handing off to\r\n engineers.\r\n

\r\n\r\n \"Example\r\n

Example of tiles Roku designers generate

\r\n\r\n

\r\n My first thought when hearing about these issues was that a Figma plugin\r\n could be a perfect fit to automate many of these tasks. The advantage to\r\n using Figma is that designers can keep their work in one tool, and have\r\n the functionality they need to customize assets.\r\n

\r\n\r\n

\r\n When starting my design process I chose to use{\" \"}\r\n \r\n UI2, Figma's Design System\r\n \r\n . I learned from previous experience building plugins that they felt\r\n more integrated with Figma when they used the same design system. I\r\n chose to swap Figma's traditional blue accent with an electric purple to\r\n give it a Roku-themed flair.\r\n

\r\n\r\n

\r\n The first challenge was understanding the process designers go through\r\n to customize each tile. I wanted to make bulk generation a \"one-click\"\r\n experience but found it didn't work with the designer's workflows. Each\r\n tile has to be customized - the color changed or an icon repositioned.\r\n It didn't make sense to generate 100 tiles all at once if the designer\r\n would have to go back and tweak each tile. I shifted my mindset to\r\n thinking of it as \"applying a transformation\" to tiles in a multi-step\r\n process.\r\n

\r\n\r\n \"User\r\n

Planning the tile generation flow

\r\n\r\n

\r\n The next step was to design the export experience. My design\r\n stakeholders requested that I limit the export customizations to\r\n simplify the process. With that direction, I added settings for image\r\n resolutions, formats, and folder naming. Puffin takes care of\r\n standardizing the naming of each layer behind the scenes.\r\n

\r\n\r\n

\r\n Another request from the stakeholders was to add a shortcut for creating\r\n Figma components. This feature makes it easy to generate a blank\r\n component with aspect ratio variants since Roku tiles all use the same\r\n set of aspect ratios.\r\n

\r\n\r\n \"Puffin\r\n

Finalized designs

\r\n\r\n

\r\n My previous experience building Figma plugins accelerated the\r\n development process. I used a handy{\" \"}\r\n \r\n React Figma plugin template\r\n {\" \"}\r\n to start the project. I leveraged{\" \"}\r\n \r\n react-figma-plugin-ds\r\n {\" \"}\r\n for design system components.{\" \"}\r\n \r\n browser-image-compression\r\n {\" \"}\r\n and jszip helped me export\r\n assets.\r\n

\r\n\r\n

\r\n Finally, I was ready to share the plugin with the Roku design community.\r\n I created a quick overview video to show what the plugin could do.\r\n

\r\n\r\n \r\n\r\n

\r\n Puffin 1.0 launched in August 2023. The next step is to gather user\r\n feedback to determine changes to make in the next version. I would like\r\n to explore stronger image compression techniques such as{\" \"}\r\n \r\n image quantization\r\n \r\n .\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default Puffin;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"Oso\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst Oso = {\r\n title: \"Remote backlight interface\",\r\n subTitle: \"Controlling TV remote hardware\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"center top\",\r\n passwordRequired: true,\r\n overview: {\r\n problem:\r\n \"Roku designers needed to control the LED backlight color and behaviors of an experimental TV remote for user testing.\",\r\n goal: \"Develop a web interface that allows designers to control TV remote settings during experimentation and user testing.\",\r\n outcome:\r\n \"Moderators used my interface during user testing sessions to control the remote and gathered useful feedback about the design.\",\r\n role: \"I was the sole designer and software developer on this project. I partnered with a hardware engineer to send signals between the web interface and the remote.\",\r\n technologies: \"React, Web Serial API, Arduino\",\r\n dates: \"September - October 2022\",\r\n },\r\n content: (\r\n <>\r\n \"Backlit\r\n

Backlit remote prototypes

\r\n\r\n

\r\n The next generation of Roku remotes will be backlit, meaning that when a\r\n user interacts with the remote it will light up. This will make the\r\n buttons easier to see, especially in dark conditions. Roku designers\r\n needed to determine the best LED color for the remote. They also needed\r\n to find the best activation method for the light. The options were touch\r\n (holding remote), proximity (hand is near the remote), or accelerometer\r\n (moving remote). Designers also needed to be able to configure how long\r\n the light would stay on, and how long the light should fade out.\r\n

\r\n

\r\n The best way to make decisions about remote backlighting was through\r\n user testing. The designers requested to customize remote\r\n configurations, and have shortcuts to show different configurations\r\n during testing. This is where I came in to design and develop an\r\n interface the designers could use.\r\n

\r\n\r\n

\r\n A hardware engineer on my team had built several remotes with an{\" \"}\r\n Arduino that could change the\r\n remote's settings. It was possible to have a website communicate with\r\n the remote's Arduino using the{\" \"}\r\n \r\n Web Serial API\r\n \r\n . The challenge was that I had never used the Web Serial API and I had\r\n only a few weeks to design and build this project.\r\n

\r\n\r\n

\r\n I got to work right away. I leveraged several of Roku's web design\r\n system components to build the interface faster. There were several\r\n custom components I needed to build. I created a color picker that could\r\n handle both regular RGB colors as well as{\" \"}\r\n \r\n color temperatures\r\n \r\n . Most of the remote button lights would be a shade of warm white.\r\n Certain buttons that launched channels would use a brand color, such as\r\n red for Netflix..\r\n

\r\n\r\n \"Web\r\n

Settings used to control the remote

\r\n\r\n

\r\n In addition to the main control dashboard I gave designers the ability\r\n to create preset configurations. Presets were important because the user\r\n testing moderator needed to be able to show users different variations\r\n during the session. I set it up so they could apply many settings at\r\n once with the click of a button.\r\n

\r\n\r\n \"Preset\r\n

Creating a preset configuration

\r\n\r\n

\r\n User testing sessions showed that the reaction to backlit remotes was\r\n positive. Preference for the light activation method (touch, proximity,\r\n accelerometer) was mixed.\r\n

\r\n\r\n

\r\n I learned a lot about communicating with Arduinos in this project. If I\r\n had more time to work on it, I would have iterated on the design and\r\n asked for more feedback. The interface was quite complicated to use due\r\n to the number of settings and customization needed, but could have room\r\n for improvement on simplicity. However, I would call delivering a\r\n complicated prototype on a tight timeline a win.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default Oso;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"UXE\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst UXE = {\r\n title: \"Roku UXE Team Site\",\r\n subTitle: \"Showcasing tools & prototypes\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"center top\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"The Roku UX Engineering team needed a place to share their tools, prototypes, and roadmap.\",\r\n goal: \"Create a beautiful website that informs other designers about UX Engineering.\",\r\n outcome:\r\n \"The Roku design community was able to learn more about our team's work and how to connect with us.\",\r\n role: \"I was the sole designer and full stack engineer on this project.\",\r\n technologies: \"React, Node.js, AWS Dynamo DB, AWS S3, Jira API\",\r\n dates: \"September 2022 - August 2023\",\r\n },\r\n content: (\r\n <>\r\n

\r\n The Roku UX Engineering team needed a way to increase our visibility to\r\n the rest of the design organization. A basic Confluence page would not\r\n do! This website needed to include our internal tools, prototypes, team\r\n roadmap, and FAQs about working with our team.\r\n

\r\n\r\n

\r\n When I started designing this project, I knew I wanted to use striking\r\n colors and lots of Roku purple. I was particularly inspired by an ad\r\n campaign Roku had done recently. I loved the rounded shapes and\r\n gradients and wanted to incorporate them in my designs. My goal was for\r\n the website to be attractive to designers and show off our UX\r\n Engineering front-end skills.\r\n

\r\n \"Roku\r\n\r\n

\r\n The first section of the site shows a list of our internal tools. Each\r\n tool has an accompanying video or image with links to its site. The tab\r\n arrangement on the left allows you to browse the details for each tool.\r\n

\r\n \"Internal\r\n\r\n

\r\n The next section shows the prototypes the team has worked on. The list\r\n style mimics the experience of browsing content on a Roku device. You\r\n can search for prototypes, which is helpful because there are more than\r\n 60 prototypes to view.\r\n

\r\n \"Viewing\r\n\r\n

\r\n Each prototype can open in a modal. There you can see more project\r\n details and interact with prototype in an iframe.\r\n

\r\n \"Prototypes\r\n\r\n

\r\n I also added a hidden form where UX Engineers could add more prototypes\r\n to the list. The data is saved to AWS Dynamo DB and images are uploaded\r\n to AWS S3.\r\n

\r\n \"Add\r\n\r\n

\r\n The roadmap section displayed a calendar view of the projects the team\r\n is working on. This view syncs with Jira using the{\" \"}\r\n \r\n Jira API\r\n \r\n , so roadmap updates are automatic.\r\n

\r\n \"Project\r\n\r\n

\r\n The FAQ section lets other designers know how best to work with our\r\n team. It includes two quizzes to assess whether a coded prototype is\r\n necessary and approximately how long a prototype will take to develop.\r\n

\r\n \"FAQ\r\n \"Prototype\r\n \r\n ),\r\n};\r\n\r\nexport default UXE;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"BrandRefresh\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst BrandRefresh = {\r\n title: \"Roku OS Brand Refresh\",\r\n subTitle: \"Prototype of design updates\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"center top\",\r\n passwordRequired: true,\r\n overview: {\r\n problem:\r\n \"The Roku Visual Design team needed to showcase their vision of a brand refresh initiative to executives.\",\r\n goal: \"Prototype design updates across the entire Roku OS.\",\r\n outcome: \"I presented the prototype to Roku's VP of Design.\",\r\n role: \"I was the lead engineer on this project and worked with another UX Engineer.\",\r\n technologies: \"Vue.js\",\r\n dates: \"July - August 2023\",\r\n },\r\n content: (\r\n <>\r\n

\r\n In fall 2023, Roku plans to release a brand refresh for the Roku\r\n platform. The goals of this refresh are to have better visual appeal and\r\n help users understand the Roku brand. A teammate and I worked with\r\n visual designers to create a prototype that showcases design changes on\r\n every screen of the platform.\r\n

\r\n\r\n \"Roku\r\n

Roku home screen with brand refresh updates

\r\n\r\n

\r\n This was a complicated undertaking given the breadth of changes and a\r\n quick timeline. We ran two-week sprints for this project and prioritized\r\n the features that needed to be fully functional. I took charge of\r\n creating, prioritizing, and assigning Jira tickets for each feature.\r\n

\r\n\r\n

\r\n Theming was an important aspect of the brand refresh and included black,\r\n purple, and red variations. We used CSS variables to dynamically change\r\n color styles. The prototype included a configuration page that allowed\r\n designers to change the theme.\r\n

\r\n\r\n \"Prototype\r\n

Prototype configuration screen

\r\n\r\n

\r\n Many screens within the prototype needed to be fully functional. Some of\r\n the screens I created included \"What to Watch\" (content suggestions), a\r\n sports page, and The Roku Channel (Roku's streaming service).\r\n

\r\n\r\n \"What\r\n

\"What to Watch\" content suggestions

\r\n\r\n \"Sports\r\n

NFL Sports page

\r\n\r\n \"The\r\n

The Roku Channel (Roku's streaming service)

\r\n\r\n

\r\n This project concluded with a presentation to Roku's VP of Design. I\r\n demoed the prototype so the executives could get a realistic experience\r\n of what the changes would be like in product.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default BrandRefresh;\r\n","import getImgPrefix from \"../util/getImgPrefix\";\r\n\r\nconst imgFolder = \"NBCUX\";\r\nconst imgPrefix = getImgPrefix(imgFolder);\r\n\r\nconst NBCUX = {\r\n title: \"NBCUX Self-Service\",\r\n subTitle: \"Figma plugin for NBC Universal\",\r\n coverImageSrc: `${imgPrefix}/1.png`,\r\n coverPosition: \"top\",\r\n passwordRequired: false,\r\n overview: {\r\n problem:\r\n \"NBC Universal has many internal enterprise tools and not enough designers to support them. Product managers need to create their own layouts in Figma with minimal support from the design team.\",\r\n goal: \"Make Figma easy to use for non-designers by creating a plugin that generates layouts using design system components.\",\r\n outcome:\r\n \"The first version of the plugin was released to users in January 2024.\",\r\n role: \"I was the lead engineer on this project and worked with another UX Engineer and two designers. This was a short-term contract role.\",\r\n technologies:\r\n \"Figma Plugin API, TypeScript, React, Material UI, GitHub Actions, Jest\",\r\n dates: \"October - December 2023\",\r\n },\r\n content: (\r\n <>\r\n

\r\n NBC Universal uses more than 100 internal enterprise tools. Their design\r\n team has fewer than 10 designers to support these tools, leaving a\r\n design gap. Product managers try to fill this gap but find Figma hard to\r\n use for design work. Additionally, they are often limited to using only\r\n FigJam due to licensing costs. My team developed a Figma plugin to allow\r\n non-designers to self-serve simple layouts.\r\n

\r\n\r\n

\r\n We focused on generating a commonly used page template with a data table\r\n as the MVP for this tool. Users can fill out a form to generate a table\r\n page and specify the components they want to use in the layout. They can\r\n customize how many columns the table has and what the data type of each\r\n column should be. They can also add components such as tabs, a search\r\n bar, breadcrumb navigation, and more.\r\n

\r\n\r\n \"Creating\r\n

Creating a table

\r\n\r\n

\r\n When a template is created, the plugin adds documentation next to it. It\r\n provides a short explanation of how to use the plugin and where to get\r\n help. The documentation itself is a published Figma component the plugin\r\n imports. This way, designers can update the documentation without\r\n developer support.\r\n

\r\n\r\n \"Documentation\"\r\n\r\n

\r\n Users can update template layouts as needed. The update process uses the\r\n current selection to populate the form data in the UI.\r\n

\r\n\r\n \"Updating\r\n

Updating a table

\r\n\r\n

\r\n I created a short demo video of the plugin to share our MVP with new\r\n users.\r\n

\r\n\r\n \r\n\r\n

\r\n As the plugin gains more users, it's helpful to have analytics to tell\r\n us how they use the tool. We want to know who the users are, if they're\r\n using Figma or FigJam, what they are creating and how often. It's also\r\n important to know if they run into errors. I chose{\" \"}\r\n Mixpanel to handle our analytics\r\n because it's easy to use and integrates well with Figma plugins.\r\n

\r\n\r\n \r\n

\r\n \r\n Mixpanel Analytics dashboard\r\n {\" \"}\r\n with preliminary data.\r\n

\r\n\r\n

\r\n This plugin was built with React, TypeScript, and Material UI\r\n components. I wanted to make the code as easy as possible for any future\r\n developers. Due to my short-term contract, I wouldn't be there to\r\n onboard them in the future. I focused on creating great documentation,\r\n commenting code, and writing unit tests. I chose{\" \"}\r\n Notion to host documentation on the\r\n code base and our publication process. I wrote thorough{\" \"}\r\n JSDoc comments for all the functions in\r\n our codebase. I used Jest to write 45\r\n unit tests that automatically ran on pull requests.\r\n

\r\n\r\n

\r\n A pain point of creating Figma plugins is that the publishing process is\r\n manual and you have to upload code in the Figma editor. To work around\r\n this I used figcd,\r\n a command-line Figma plugin publishing tool. I combined it with GitHub\r\n Actions to deploy releases, which took a multi-step process to one click\r\n of a button.\r\n

\r\n\r\n

\r\n The NBCUX Self-Service Figma plugin launched in January 2024.\r\n Development work is now paused while more support and funding is\r\n gathered for the plugin. In the future, the plugin will include more\r\n libraries (Angular, React, and Salesforce), templates, and components.\r\n

\r\n \r\n ),\r\n};\r\n\r\nexport default NBCUX;\r\n","import IEPShell from \"./IEPShell\";\r\nimport Pyro from \"./Pyro\";\r\nimport Toolkit from \"./Toolkit\";\r\nimport Flow from \"./Flow\";\r\nimport Puffin from \"./Puffin\";\r\nimport Oso from \"./Oso\";\r\nimport UXE from \"./UXE\";\r\nimport BrandRefresh from \"./BrandRefresh\";\r\nimport NBCUX from \"./NBCUX\";\r\n\r\nconst ProjectsList = [\r\n NBCUX,\r\n Flow,\r\n Puffin,\r\n BrandRefresh,\r\n Oso,\r\n UXE,\r\n Toolkit,\r\n Pyro,\r\n IEPShell,\r\n];\r\n\r\nexport default ProjectsList;\r\n","import React, { useState } from \"react\";\r\nimport { FaLock, FaExclamationCircle } from \"react-icons/fa\";\r\nimport \"./PasswordProtector.scss\";\r\n\r\nconst PasswordProtector = ({ authenticate = () => {} }) => {\r\n const [userInput, setUserInput] = useState(\"\");\r\n const [showError, setShowError] = useState(false);\r\n\r\n return (\r\n
\r\n \r\n

\r\n Sorry, the information in this project is confidential and requires a\r\n password. Please email{\" \"}\r\n amtruttmann@gmail.com for access.\r\n

\r\n\r\n
\r\n {\r\n setShowError(!authenticate(userInput));\r\n e.preventDefault();\r\n }}\r\n >\r\n \r\n setUserInput(e.target.value)}\r\n name=\"password\"\r\n autoComplete=\"current-password\"\r\n />\r\n \r\n \r\n \r\n \r\n

Whoops, looks like this password is invalid

\r\n
\r\n
\r\n \r\n );\r\n};\r\n\r\nexport default PasswordProtector;\r\n","import React, { useEffect } from \"react\";\r\nimport { CSSTransition } from \"react-transition-group\";\r\nimport { FaTimesCircle } from \"react-icons/fa\";\r\nimport PasswordProtector from \"./PasswordProtector/PasswordProtector\";\r\nimport \"./ProjectModal.scss\";\r\n\r\nconst ProjectModal = ({\r\n project,\r\n open,\r\n closeModal,\r\n authenticated,\r\n authenticate = () => {},\r\n}) => {\r\n useEffect(() => {\r\n if (authenticated) loadVideos();\r\n }, [authenticated]);\r\n\r\n /* \r\n Videos are set to not preload so the loading doesn't affect the CSS transitions\r\n as the modal animates in. After the modal is done animating, load the videos so\r\n they are ready to play. Use a CSS transition with opacity so it the unloaded video \r\n doesn't show, only the loaded video.\r\n */\r\n const loadVideos = () => {\r\n const videos = document.getElementsByTagName(\"video\");\r\n Array.from(videos).forEach((video) => {\r\n video.preload = \"auto\";\r\n video.style.opacity = \"1\";\r\n });\r\n };\r\n\r\n const modalAnimationFinish = () => {\r\n loadVideos();\r\n\r\n // Focus password input\r\n const passwordInput = document.getElementById(\"passwordInput\");\r\n if (passwordInput) passwordInput.focus();\r\n };\r\n\r\n return (\r\n \r\n
\r\n
\r\n \r\n \r\n \r\n
\r\n

{project.title}

\r\n

{project.subTitle}

\r\n
\r\n {project.passwordRequired && !authenticated ? (\r\n \r\n ) : (\r\n <>\r\n
\r\n

Overview

\r\n {Object.keys(project.overview).map((infoItem) => (\r\n

\r\n {`${infoItem}: `}\r\n {project.overview[infoItem]}\r\n

\r\n ))}\r\n {project.links && project.links.length >= 1 && (\r\n

\r\n Links: \r\n {project.links.map((link, index) => (\r\n \r\n \r\n {link.title}\r\n \r\n {index < project.links.length - 1 && \" | \"}\r\n \r\n ))}\r\n

\r\n )}\r\n
\r\n\r\n
\r\n\r\n {project.content}\r\n \r\n )}\r\n
\r\n
\r\n \r\n );\r\n};\r\n\r\nexport default ProjectModal;\r\n","import React, { useState, useEffect } from \"react\";\r\nimport Container from \"react-bootstrap/Container\";\r\nimport Row from \"react-bootstrap/Row\";\r\nimport Col from \"react-bootstrap/Col\";\r\nimport { FaSun, FaMoon } from \"react-icons/fa\";\r\nimport Header from \"./Header/Header\";\r\nimport ProjectTile from \"./ProjectTile/ProjectTile\";\r\nimport ProjectsList from \"./Projects\";\r\nimport ProjectModal from \"./ProjectModal/ProjectModal\";\r\nimport \"bootstrap/dist/css/bootstrap.min.css\";\r\nimport \"./App.scss\";\r\n\r\nconst password = \"helloworld\";\r\n\r\nfunction App() {\r\n const [authenticated, setAuthenticated] = useState(false);\r\n const [darkTheme, setDarkTheme] = useState(false);\r\n const [modalOpen, setModalOpen] = useState(false);\r\n const [selectedProject, setSelectedProject] = useState(ProjectsList[0]);\r\n\r\n useEffect(() => {\r\n const currentTheme = localStorage.getItem(\"theme\") ?? null;\r\n\r\n // Check for saved color theme\r\n if (currentTheme) {\r\n setDarkTheme(currentTheme === \"dark\");\r\n }\r\n // Check for users preferred color scheme\r\n else if (\r\n window.matchMedia &&\r\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches\r\n ) {\r\n setDarkTheme(true);\r\n }\r\n }, []);\r\n\r\n useEffect(() => {\r\n const theme = darkTheme ? \"dark\" : \"light\";\r\n localStorage.setItem(\"theme\", theme);\r\n document.documentElement.style.setProperty(\"color-scheme\", theme);\r\n document.documentElement.setAttribute(\"data-theme\", theme);\r\n }, [darkTheme]);\r\n\r\n return (\r\n
\r\n
\r\n setDarkTheme(!darkTheme)}\r\n aria-label=\"Change theme\"\r\n title=\"Change theme\"\r\n >\r\n {darkTheme ? : }\r\n \r\n\r\n \r\n \r\n
\r\n \r\n \r\n {ProjectsList.map((project) => (\r\n \r\n setModalOpen(true)}\r\n authenticated={authenticated}\r\n authenticate={(userInput) => {\r\n const isAuthenticated = userInput === password;\r\n setAuthenticated(isAuthenticated);\r\n return isAuthenticated;\r\n }}\r\n />\r\n \r\n ))}\r\n \r\n \r\n\r\n
\r\n

\r\n I designed and built this website from scratch! Check out the code\r\n on{\" \"}\r\n \r\n GitHub\r\n \r\n .\r\n

\r\n
\r\n
\r\n\r\n setModalOpen(false)}\r\n authenticated={authenticated}\r\n authenticate={(userInput) => {\r\n const isAuthenticated = userInput === password;\r\n setAuthenticated(isAuthenticated);\r\n return isAuthenticated;\r\n }}\r\n />\r\n
\r\n );\r\n}\r\n\r\nexport default App;\r\n","import React from 'react';\r\nimport ReactDOM from 'react-dom';\r\nimport App from './App';\r\n\r\nReactDOM.render(\r\n \r\n \r\n ,\r\n document.getElementById('root')\r\n);\r\n"],"sourceRoot":""} \ No newline at end of file