diff --git a/assets/scss/base/components.scss b/assets/scss/base/components.scss index 32a8f9b..a9ed106 100644 --- a/assets/scss/base/components.scss +++ b/assets/scss/base/components.scss @@ -1,6 +1,6 @@ .button { background: #0060ff; - color: white; + color: white !important; width: max-content; font-size: clamp(1em,2vw,1em); padding: 0.75em; @@ -9,4 +9,5 @@ text-decoration: none; border-radius: 100em; font-weight: 700; + } \ No newline at end of file diff --git a/assets/scss/base/links.scss b/assets/scss/base/links.scss new file mode 100644 index 0000000..9d113b2 --- /dev/null +++ b/assets/scss/base/links.scss @@ -0,0 +1,32 @@ +a:link, +a:visited { + color: inherit; + text-decoration-color: #0060ff; + transition: + text-decoration-thickness 200ms ease-out, + text-underline-offset 200ms ease-out, + text-decoration-color 200ms ease-out, + color 200ms ease-out + ; +} + +a:hover, a:focus, a:active { + text-decoration-color: #0060ff; +} + +a:link {text-underline-offset: -0.075em;} +a:hover {text-underline-offset: -0.375em;} + +@supports (text-decoration-thickness: 1em) { + a:link { + text-decoration-thickness: 0.125em; + text-underline-offset: 0.125em; + text-decoration-skip-ink: none; + } + + a:hover { + color: white; + text-decoration-thickness: 1.125em; + text-underline-offset: -0.875em; + } +} \ No newline at end of file diff --git a/assets/scss/base/page.scss b/assets/scss/base/page.scss index d1fe5a0..5fe2649 100644 --- a/assets/scss/base/page.scss +++ b/assets/scss/base/page.scss @@ -27,7 +27,7 @@ .page-summary {margin: 1em 0;} .page-cover {width: 100%;} - h1,h2,h3,h4,h5,h6 {line-height: 1.2; margin-bottom: 1rem;} + h1,h2,h3,h4,h5,h6 {line-height: 1.2; margin-bottom: 1rem; margin-top: 2rem;} p { line-height: 1.4; margin-bottom: 1em; @@ -40,10 +40,8 @@ h6 {font-size: 1em} blockquote { - font-size: 1.5em; - @media (min-width: 600px) {font-size: 2em} + font-size: 1em; margin: 1em 0; - font-family: serif; border-left: 0.25rem solid black; padding-left: 0.5em; } @@ -105,6 +103,9 @@ li {margin-bottom: 0; margin-left: 0;} li li {margin-left: 1em;} a { + background: unset; + text-decoration-thickness: unset; + text-underline-offset: unset; color: inherit; text-decoration: none; transition: color 0.2s ease-in-out; diff --git a/assets/scss/index/index.scss b/assets/scss/index/index.scss new file mode 100644 index 0000000..31fa092 --- /dev/null +++ b/assets/scss/index/index.scss @@ -0,0 +1,5 @@ +#contact { + .section-heading {font-weight: 700;} + p {line-height: 1.4; max-width: 65ch; margin: 0 auto;} + .cta {margin: 6em auto 0;} +} \ No newline at end of file diff --git a/assets/scss/main.scss b/assets/scss/main.scss index 09aff81..90b9687 100644 --- a/assets/scss/main.scss +++ b/assets/scss/main.scss @@ -1,9 +1,11 @@ @import "base/reset.scss"; +@import "base/links.scss"; @import "base/sections.scss"; @import "base/page.scss"; @import "base/list.scss"; @import "base/components.scss"; +@import "index/index.scss"; @import "index/hero.scss"; @import "index/cards.scss"; @import "index/testimonials.scss"; diff --git a/content/code/_index.md b/content/code/_index.md index 74687ae..4503683 100644 --- a/content/code/_index.md +++ b/content/code/_index.md @@ -1,3 +1,3 @@ --- -title: "Coding projects" +title: "Code written" --- \ No newline at end of file diff --git a/content/code/certbot-namecheap/index.md b/content/code/certbot-namecheap/index.md new file mode 100644 index 0000000..8086a5b --- /dev/null +++ b/content/code/certbot-namecheap/index.md @@ -0,0 +1,52 @@ +--- +title: "Certbot DNS-01 hook for Namecheap" +summary: "A manual auth hook for Certbot, implementing the DNS-01 challenge with Namecheap as the provider. Written in Python." +date: "2019-10-30" +tags: ["namecheap", "certbot", "dns-01", "python", "letsencrypt"] +cover: "/images/namecheap.jpg" +--- + +## The problem + +I want to obtain Let's Encrypt certificates with wildcard subdomains, but to do so, one must prove that they own the entire domain and not just a particular subdomain. This means that the standard HTTP challenges are not enough. + +I was tired of manually doing DNS-01 challenges through Namecheap's dashboard, which involved a laborious process of logging in, navigating to the domain I wanted to obtain certificates for, copying and pasting a special challenge code into a TXT record, waiting for the change to propagate, and repeating this for every single domain I owned, every three months. + +## The research + +Certbot plugins are available for some DNS providers, but Namecheap is not one of those. I was faced with two options: one, move to a different supported provider and transfer all of my domains. Or, two: write a script that could run as a validation hook and thus automate the whole thing. + +> Certbot allows for the specification of pre and post validation hooks when run in manual mode. The flags to specify these scripts are --manual-auth-hook and --manual-cleanup-hook respectively and can be used as follows [...] +> This will run the authenticator.sh script, attempt the validation, and then run the cleanup.sh script. Additionally certbot will pass relevant environment variables to these scripts [...] +> +> -- https://certbot.eff.org/docs/using.html#pre-and-post-validation-hooks + +Namecheap also [provides an API](https://www.namecheap.com/support/api/intro/) to anyone who has spent at least $50 within the last 2 years, [among other alternative requirements](https://www.namecheap.com/support/knowledgebase/article.aspx/9739/63/api--faq/#c). + +## The solution + +I wrote the manual auth hook found here: https://github.com/trwnh/namecheap + +The hook I wrote uses Python, Requests, and BeautifulSoup. Since I just wanted the hook to work, I didn't bother over-engineering this -- this script has some limitations: + +- Namecheap's API uses SLD and TLD instead of a singular domain variable. My script uses an extremely naive approach and simply splits the domain at the dot. I think this might not matter, but I haven't tested further because I don't own any domains with multipart TLDs. +- None of the API calls are paginated. This means the script only works on accounts with up to 20 domains. I own less than 20 domains, so this is not an issue for me. Also, the methods that require pagination are currently unused in the main function. +- No error checking or handling was implemented. Theoretically, the script could fail if the domain is expired or locked, but I trust myself to keep all my domains valid and registered. +- API username, key, etc. are hardcoded instead of being read from some external file. This script was written for my personal use, so I didn't bother going further. + +### Logic + +The pseudocode for this script is as follows: + +1. Get CERTBOT_DOMAIN from Certbot. +2. Get the host records for this domain. +3. Get the CERTBOT_VALIDATION code. +4. Create a TXT host record using this challenge code. +5. Add this host record to the existing host records. +6. Upload the host records back to Namecheap. + +The source code can be examined at https://github.com/trwnh/namecheap/blob/master/auth -- I made sure to leave docstrings and use clear function names, so hopefully this is good, readable code. + +## The result + +It works! Now, I can automate my certificate renewals instead of doing it all manually every three months. Maybe one day, I'll even write a cleanup script to delete the TXT challenge records... :) \ No newline at end of file diff --git a/content/code/liberapay-pleroma/index.md b/content/code/liberapay-pleroma/index.md new file mode 100644 index 0000000..4cbe0f9 --- /dev/null +++ b/content/code/liberapay-pleroma/index.md @@ -0,0 +1,23 @@ +--- +title: "Add Pleroma support to Liberapay" +summary: "Pleroma is compatible with the Mastodon API, but uses a different URL format. This pull request creates a Pleroma identity provider within Liberapay that takes this difference into account." +date: "2019-11-13" +tags: ["mastodon", "api", "pleroma", "liberapay", "contribution", "pull request", "github"] +cover: "/images/liberapay-pleroma.jpg" +--- + +pleroma is compatible with mastodon api. +only difference is url format: + +- mastodon uses /@username +- pleroma uses /username + +implementation notes: i've made it a separate provider than mastodon even though it implements the mastodon api for the following reasons: + +- while there is no technical difference between auth flows or relevant apis, this may not be true in the future (although there are no plans afaik to change it in pleroma) +- renaming "mastodon" to "fediverse" is incorrect because not all fediverse servers implement the mastodon api +- labeling a pleroma as a "mastodon" account is technically incorrect +- given the above points, it was easier to just copy and paste and not deal with the potential for service explosion at this time + + +https://github.com/liberapay/liberapay.com/pull/1618 \ No newline at end of file diff --git a/content/code/mastomods/index.md b/content/code/mastomods/index.md new file mode 100644 index 0000000..98a14c0 --- /dev/null +++ b/content/code/mastomods/index.md @@ -0,0 +1,13 @@ +--- +title: "MastoMods" +summary: "CSS tweaks and modifications for Mastodon, a libre micro-blogging social server similar to Twitter." +date: "2019-02-18" +tags: ["mastomods", "mastodon", "css", "userstyles", "tweaks"] +cover: "/images/mastomods.jpg" +--- + +This work is heavily based on (and an extension of) my earlier work on Mastodon Flat CSS, and its child theme Linernotes Mastodon Themes. I grew tired of having to maintain what was essentially the same code in multiple different places, so this repo was created to be a more modular way of managing code snippets after I learned enough about how importing works. + +linernotes_dark is an admin-installable theme that was commissioned for linernotes.club. Because the base MFC theme is adaptable, it is not too difficult to build your own theme on top of it. See the source code for comments and documentation. + +https://github.com/trwnh/mastomods \ No newline at end of file diff --git a/content/code/obs-edit-transform/index.md b/content/code/obs-edit-transform/index.md new file mode 100644 index 0000000..c44646c --- /dev/null +++ b/content/code/obs-edit-transform/index.md @@ -0,0 +1,11 @@ +--- +title: "OBS Studio: Ctrl+E to Edit Transform" +summary: "I added a shortcut to edit transforms on a source in OBS Studio." +date: "2017-04-30" +tags: ["obs", "open broadcaster software", "obs studio", "keyboard shortcut", "edit transform", "pull request", "github"] +cover: "/images/obs-transform.jpg" +--- + +Editing was very easy in OBS Classic, and I could not find the option for stretching a source to bounds in OBS Studio, so the "Edit Transform" dialogue should be more user-facing. Giving it a keyboard shortcut denotes that it is important enough to have a shortcut, as opposed to the myriad options with no shortcut. + +https://github.com/obsproject/obs-studio/pull/894 \ No newline at end of file diff --git a/content/code/photobucketgrabber/index.md b/content/code/photobucketgrabber/index.md new file mode 100644 index 0000000..cdf1ed1 --- /dev/null +++ b/content/code/photobucketgrabber/index.md @@ -0,0 +1,26 @@ +--- +title: "PhotoBucketGrabber" +summary: "Download all your photos from PhotoBucket using this Python script." +date: "2019-03-17" +tags: ["python", "photobucket", "automation", "scripting", "archive", "export", "download"] +cover: "/images/photobucketgrabber.jpg" +--- + +## The problem + +I had an old PhotoBucket account that I wanted to archive and delete. However, it would take an extremely long time to manually save each photo and recreate any albums' folder structure. + +## The research + +PhotoBucket allows copying direct image links, and it also allows copying multiple links at the same time. This is used to obtain a list of all photo direct URLs, which can be saved in a text file and read by a script. + +Additionally, direct URLs maintain the folder and file names, so the path structure can be saved directly using Python's Path library. + +## The solution + +See my source code here: https://github.com/trwnh/PhotoBucketGrabber + +Limitations: + +- The prefix to be stripped is hardcoded, because it will always be the same for an account. +- There is no way to programmatically obtain a list of all photos or albums from Photobucket, as far as I know, so the selection step is still manual. I think for a one-time task, this is probably not an issue, as it would take more time to attempt to code a solution than it would to simply select all photos from the web dashboard. \ No newline at end of file diff --git a/content/code/salatime/index.md b/content/code/salatime/index.md new file mode 100644 index 0000000..35ded8d --- /dev/null +++ b/content/code/salatime/index.md @@ -0,0 +1,28 @@ +--- +title: "salatime" +summary: "Basic terminal script to print out daily prayer times for Birmingham, AL." +date: "2019-05-09" +tags: ["python", "web scraping", "scraping", "beautifulsoup", "salat", "prayer", "time"] +cover: "/images/salatime.jpg" +--- + +## The problem + +While working in a terminal environment, I would like to know what the current active prayer is, how much time I have left to pray, time to next prayer, and other such information. + +## The research + +The Birmingham Islamic Society provides tables of prayer times by month. This table can be scraped with Python Requests, then parsed with BeautifulSoup. + +## The solution + +Source code is available at https://github.com/trwnh/salatime + +Pseudocode is as follows: + +1. Get the current month, day, hour, and minute. +2. Get the page for the current month. +3. Get the times table out of this page. +4. Parse this table for a list of prayer times. +5. Parse this list for the current day's prayer times. +6. Calculate and print information. \ No newline at end of file diff --git a/content/work/_index.md b/content/work/_index.md index cad0d1f..51c58ae 100644 --- a/content/work/_index.md +++ b/content/work/_index.md @@ -1,3 +1,3 @@ --- -title: "Work I've done" +title: "Work done" --- \ No newline at end of file diff --git a/layouts/index.html b/layouts/index.html index f08f9bd..c9443c7 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -110,9 +110,9 @@
-

let's talk.

-

if you'd like me to do something for you, then the best way to get in touch with me is to email a@trwnh.com with your proposal. i am currently open for hiring as well.

- view resume +

let's talk.

+

if you'd like me to do something for you, then the best way to get in touch with me is to email a@trwnh.com with your proposal. i am currently open for hiring as well.

+ view resume
diff --git a/resources/_gen/assets/scss/scss/main.scss_48b060fe05b0a273d182ef83c0605941.content b/resources/_gen/assets/scss/scss/main.scss_48b060fe05b0a273d182ef83c0605941.content index a645d8c..d668d10 100644 --- a/resources/_gen/assets/scss/scss/main.scss_48b060fe05b0a273d182ef83c0605941.content +++ b/resources/_gen/assets/scss/scss/main.scss_48b060fe05b0a273d182ef83c0605941.content @@ -1 +1 @@ -html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}html{font-family:-apple-system,BlinkMacSystemFont,segoe ui,Roboto,Oxygen,Ubuntu,Cantarell,open sans,helvetica neue,sans-serif;scroll-behavior:smooth}body{display:flex;flex-flow:column;min-height:100vh;position:relative}main{flex-grow:1}.section{padding:2em 0}@media(min-width:600px){.section{padding:3em 0}}@media(min-width:62em){.section{padding:4em 0}}.container{width:100%;max-width:960px;margin:0 auto;padding:0 1em}.section-heading{font-size:1.2em;font-weight:400;line-height:1.2;text-align:center;max-width:18ch;margin:0 auto;margin-bottom:4em;padding-bottom:1em;border-bottom:2px solid rgba(0,0,0,.25)}.page-title{font-size:2.5em}.page{font-size:1em;max-width:960px;margin:0 auto}@media(min-width:600px){.page{font-size:1.25em}}@media(min-width:960px){.page{display:grid;grid-template-columns:minmax(45ch,65ch)1fr;grid-template-rows:auto auto;grid-template-areas:"header header" "content meta"}}.page .section{padding:1em 0;grid-area:content}.page .page-header{padding:2em 0;grid-area:header}.page .meta{grid-area:meta}.page .page-summary{margin:1em 0}.page .page-cover{width:100%}.page h1,.page h2,.page h3,.page h4,.page h5,.page h6{line-height:1.2;margin-bottom:1rem}.page p{line-height:1.4;margin-bottom:1em}.page h1{font-size:2.49em}.page h2{font-size:2.07em}.page h3{font-size:1.728em}.page h4{font-size:1.44em}.page h5{font-size:1.2em}.page h6{font-size:1em}.page blockquote{font-size:1.5em;margin:1em 0;font-family:serif;border-left:.25rem solid #000;padding-left:.5em}@media(min-width:600px){.page blockquote{font-size:2em}}.page pre{font-family:monospace;background:#333;color:#fff;padding:1em;line-height:1.4;overflow-x:scroll;margin-bottom:1em}.page ul{list-style:disc;margin:1em 0}.page li{margin-bottom:1em;line-height:1.4;margin-left:1em}.page ol{list-style:decimal;margin:1em 0}.page dl{margin:1em 0;line-height:1.4}.page dt{font-weight:700}.page dd{margin-left:1em}.page em{font-style:italic}.page strong{font-weight:700}.page sup{position:relative;font-size:.8em}.page sup a{position:relative;top:-.5em}.page table{text-align:center}.page table thead{font-weight:700}.page table th,.page table td{border:1px solid #000;padding:.5em}.tags{display:flex;flex-flow:row wrap;gap:.25em}.tags li{list-style:disc;border-radius:4px;margin-left:1em;margin-bottom:0}.tags li a{color:inherit}.meta .container{height:100%}#TableOfContents{position:sticky;top:2rem;max-width:max-content;font-size:.75em;margin:2em 0}#TableOfContents ul{list-style:none;margin:0}#TableOfContents li{margin-bottom:0;margin-left:0}#TableOfContents li li{margin-left:1em}#TableOfContents a{color:inherit;text-decoration:none;transition:color .2s ease-in-out}#TableOfContents a:hover{color:#0060ff;text-decoration:underline}.list .container{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:3em}.list .list-item{color:inherit;text-decoration:none;transition:color .2s ease-in-out}.list .list-item__image{width:100%;height:auto}.list .list-item__title{font-size:1.5em;margin:.5em 0}.list .list-item__summary{margin-bottom:.5em;line-height:1.4}.list .list-item__date{display:block;margin-bottom:1em}.list .list-item__readmore{margin-bottom:1em;display:block}.button{background:#0060ff;color:#fff;width:max-content;font-size:clamp(1em,2vw,1em);padding:.75em;display:flex;justify-content:center;text-decoration:none;border-radius:100em;font-weight:700}.hero .container{display:grid;grid-template-columns:1fr;grid-template-rows:auto auto auto;grid-template-areas:"img" "txt" "cta";gap:1em}@media(min-width:600px){.hero .container{grid-template-columns:1fr 1fr;grid-template-rows:auto auto;grid-template-areas:"txt img" "cta img"}}.hero img{grid-area:img;width:100%;max-height:160px}@media(min-width:600px){.hero img{max-height:unset}}.hero .cta{grid-area:cta;align-self:start}.hero .headline{grid-area:txt;font-size:clamp(1.4em,7vw,3em);line-height:1.2;align-self:end}@media(min-width:600px){.hero .headline{max-width:11ch}}.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(15em,1fr));gap:2em}.card{background:#fff}.card img{width:100%;height:auto}#process .card img{height:200px}.card h3{margin:1em 0;font-weight:700}.card p{margin-bottom:1em;line-height:1.4}.testimonials{display:flex;flex-flow:row wrap;gap:1em;justify-content:center}.testimonial{display:grid;grid-template-columns:auto 1fr;grid-template-rows:auto auto;grid-gap:1em;margin-bottom:1em;flex-basis:18em}.avatar{margin-left:1em;width:2em;height:2em;border-radius:1em;background:#212121}.name{align-self:center}.bubble{background-color:#212121;color:#fff;padding:.5em;border-radius:.5em;position:relative;grid-column:span 2;height:80px;display:flex;justify-content:center;align-items:center;text-align:center}.bubble:after{content:'';position:absolute;top:0;left:2em;width:0;height:0;border:.5em solid transparent;border-bottom-color:#212121;border-top:0;margin-left:-.5em;margin-top:-.5em}.site-header a{text-decoration:none;color:inherit}.site-header .container{display:flex;flex-flow:row wrap;justify-content:space-between;padding:1em}.site-masthead{display:flex;align-items:center}.site-icon{width:44px;height:44px;border-radius:100em;margin-right:1em}.site-title{margin-bottom:0;line-height:1;font-size:1em}body{margin-bottom:64px;min-height:calc(100vh - 64px)}.site-nav{flex-grow:1;position:fixed;bottom:0;left:0;width:100vw;background:#212121;color:#fff;z-index:2}.site-nav ul{height:64px;max-width:960px;margin:0 auto;display:flex;justify-content:space-around}.site-nav ul li{flex:1;border-bottom:4px solid #212121;color:#999}.site-nav ul li.active{color:#fff;border-bottom:4px solid #0060ff}.site-nav ul li a{display:flex;flex-flow:column;align-items:center;justify-content:center;height:100%}.site-nav ul li a span{padding:.25em}.site-footer{background:#333;color:#fff}.site-footer hr{display:none}.site-footer .container{padding:2em 1em;display:flex;flex-flow:row wrap}.site-footer dt{font-weight:700}.site-footer dd{margin-bottom:.5em}.site-footer a{color:inherit} \ No newline at end of file +html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}a:link,a:visited{color:inherit;text-decoration-color:#0060ff;transition:text-decoration-thickness 200ms ease-out,text-underline-offset 200ms ease-out,text-decoration-color 200ms ease-out,color 200ms ease-out}a:hover,a:focus,a:active{text-decoration-color:#0060ff}a:link{text-underline-offset:-.075em}a:hover{text-underline-offset:-.375em}@supports(text-decoration-thickness:1em){a:link{text-decoration-thickness:.125em;text-underline-offset:.125em;text-decoration-skip-ink:none}a:hover{color:#fff;text-decoration-thickness:1.125em;text-underline-offset:-.875em}}html{font-family:-apple-system,BlinkMacSystemFont,segoe ui,Roboto,Oxygen,Ubuntu,Cantarell,open sans,helvetica neue,sans-serif;scroll-behavior:smooth}body{display:flex;flex-flow:column;min-height:100vh;position:relative}main{flex-grow:1}.section{padding:2em 0}@media(min-width:600px){.section{padding:3em 0}}@media(min-width:62em){.section{padding:4em 0}}.container{width:100%;max-width:960px;margin:0 auto;padding:0 1em}.section-heading{font-size:1.2em;font-weight:400;line-height:1.2;text-align:center;max-width:18ch;margin:0 auto;margin-bottom:4em;padding-bottom:1em;border-bottom:2px solid rgba(0,0,0,.25)}.page-title{font-size:2.5em}.page{font-size:1em;max-width:960px;margin:0 auto}@media(min-width:600px){.page{font-size:1.25em}}@media(min-width:960px){.page{display:grid;grid-template-columns:minmax(45ch,65ch)1fr;grid-template-rows:auto auto;grid-template-areas:"header header" "content meta"}}.page .section{padding:1em 0;grid-area:content}.page .page-header{padding:2em 0;grid-area:header}.page .meta{grid-area:meta}.page .page-summary{margin:1em 0}.page .page-cover{width:100%}.page h1,.page h2,.page h3,.page h4,.page h5,.page h6{line-height:1.2;margin-bottom:1rem;margin-top:2rem}.page p{line-height:1.4;margin-bottom:1em}.page h1{font-size:2.49em}.page h2{font-size:2.07em}.page h3{font-size:1.728em}.page h4{font-size:1.44em}.page h5{font-size:1.2em}.page h6{font-size:1em}.page blockquote{font-size:1em;margin:1em 0;border-left:.25rem solid #000;padding-left:.5em}.page pre{font-family:monospace;background:#333;color:#fff;padding:1em;line-height:1.4;overflow-x:scroll;margin-bottom:1em}.page ul{list-style:disc;margin:1em 0}.page li{margin-bottom:1em;line-height:1.4;margin-left:1em}.page ol{list-style:decimal;margin:1em 0}.page dl{margin:1em 0;line-height:1.4}.page dt{font-weight:700}.page dd{margin-left:1em}.page em{font-style:italic}.page strong{font-weight:700}.page sup{position:relative;font-size:.8em}.page sup a{position:relative;top:-.5em}.page table{text-align:center}.page table thead{font-weight:700}.page table th,.page table td{border:1px solid #000;padding:.5em}.tags{display:flex;flex-flow:row wrap;gap:.25em}.tags li{list-style:disc;border-radius:4px;margin-left:1em;margin-bottom:0}.tags li a{color:inherit}.meta .container{height:100%}#TableOfContents{position:sticky;top:2rem;max-width:max-content;font-size:.75em;margin:2em 0}#TableOfContents ul{list-style:none;margin:0}#TableOfContents li{margin-bottom:0;margin-left:0}#TableOfContents li li{margin-left:1em}#TableOfContents a{background:unset;text-decoration-thickness:unset;text-underline-offset:unset;color:inherit;text-decoration:none;transition:color .2s ease-in-out}#TableOfContents a:hover{color:#0060ff;text-decoration:underline}.list .container{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:3em}.list .list-item{color:inherit;text-decoration:none;transition:color .2s ease-in-out}.list .list-item__image{width:100%;height:auto}.list .list-item__title{font-size:1.5em;margin:.5em 0}.list .list-item__summary{margin-bottom:.5em;line-height:1.4}.list .list-item__date{display:block;margin-bottom:1em}.list .list-item__readmore{margin-bottom:1em;display:block}.button{background:#0060ff;color:#fff!important;width:max-content;font-size:clamp(1em,2vw,1em);padding:.75em;display:flex;justify-content:center;text-decoration:none;border-radius:100em;font-weight:700}#contact .section-heading{font-weight:700}#contact p{line-height:1.4;max-width:65ch;margin:0 auto}#contact .cta{margin:6em auto 0}.hero .container{display:grid;grid-template-columns:1fr;grid-template-rows:auto auto auto;grid-template-areas:"img" "txt" "cta";gap:1em}@media(min-width:600px){.hero .container{grid-template-columns:1fr 1fr;grid-template-rows:auto auto;grid-template-areas:"txt img" "cta img"}}.hero img{grid-area:img;width:100%;max-height:160px}@media(min-width:600px){.hero img{max-height:unset}}.hero .cta{grid-area:cta;align-self:start}.hero .headline{grid-area:txt;font-size:clamp(1.4em,7vw,3em);line-height:1.2;align-self:end}@media(min-width:600px){.hero .headline{max-width:11ch}}.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(15em,1fr));gap:2em}.card{background:#fff}.card img{width:100%;height:auto}#process .card img{height:200px}.card h3{margin:1em 0;font-weight:700}.card p{margin-bottom:1em;line-height:1.4}.testimonials{display:flex;flex-flow:row wrap;gap:1em;justify-content:center}.testimonial{display:grid;grid-template-columns:auto 1fr;grid-template-rows:auto auto;grid-gap:1em;margin-bottom:1em;flex-basis:18em}.avatar{margin-left:1em;width:2em;height:2em;border-radius:1em;background:#212121}.name{align-self:center}.bubble{background-color:#212121;color:#fff;padding:.5em;border-radius:.5em;position:relative;grid-column:span 2;height:80px;display:flex;justify-content:center;align-items:center;text-align:center}.bubble:after{content:'';position:absolute;top:0;left:2em;width:0;height:0;border:.5em solid transparent;border-bottom-color:#212121;border-top:0;margin-left:-.5em;margin-top:-.5em}.site-header a{text-decoration:none;color:inherit}.site-header .container{display:flex;flex-flow:row wrap;justify-content:space-between;padding:1em}.site-masthead{display:flex;align-items:center}.site-icon{width:44px;height:44px;border-radius:100em;margin-right:1em}.site-title{margin-bottom:0;line-height:1;font-size:1em}body{margin-bottom:64px;min-height:calc(100vh - 64px)}.site-nav{flex-grow:1;position:fixed;bottom:0;left:0;width:100vw;background:#212121;color:#fff;z-index:2}.site-nav ul{height:64px;max-width:960px;margin:0 auto;display:flex;justify-content:space-around}.site-nav ul li{flex:1;border-bottom:4px solid #212121;color:#999}.site-nav ul li.active{color:#fff;border-bottom:4px solid #0060ff}.site-nav ul li a{display:flex;flex-flow:column;align-items:center;justify-content:center;height:100%}.site-nav ul li a span{padding:.25em}.site-footer{background:#333;color:#fff}.site-footer hr{display:none}.site-footer .container{padding:2em 1em;display:flex;flex-flow:row wrap}.site-footer dt{font-weight:700}.site-footer dd{margin-bottom:.5em}.site-footer a{color:inherit} \ No newline at end of file diff --git a/resources/_gen/assets/scss/scss/main.scss_48b060fe05b0a273d182ef83c0605941.json b/resources/_gen/assets/scss/scss/main.scss_48b060fe05b0a273d182ef83c0605941.json index 26a7668..153896f 100644 --- a/resources/_gen/assets/scss/scss/main.scss_48b060fe05b0a273d182ef83c0605941.json +++ b/resources/_gen/assets/scss/scss/main.scss_48b060fe05b0a273d182ef83c0605941.json @@ -1 +1 @@ -{"Target":"scss/main.min.397e9f4be3c1c6acf6a7296d927f7de2c656cb95a7be3eca73cea90a66bab151.css","MediaType":"text/css","Data":{"Integrity":"sha256-OX6fS+PBxqz2pyltkn994sZWy5Wnvj7Kc86pCma6sVE="}} \ No newline at end of file +{"Target":"scss/main.min.a33c5b7ac92a7c94b1f59dc550d96f946860187f3f8709040328040d6af77a50.css","MediaType":"text/css","Data":{"Integrity":"sha256-ozxbeskqfJSx9Z3FUNlvlGhgGH8/hwkEAygEDWr3elA="}} \ No newline at end of file diff --git a/static/images/liberapay-pleroma.jpg b/static/images/liberapay-pleroma.jpg new file mode 100644 index 0000000..362b51d Binary files /dev/null and b/static/images/liberapay-pleroma.jpg differ diff --git a/static/images/mastomods.jpg b/static/images/mastomods.jpg new file mode 100644 index 0000000..8297b4b Binary files /dev/null and b/static/images/mastomods.jpg differ diff --git a/static/images/obs-transform.jpg b/static/images/obs-transform.jpg new file mode 100644 index 0000000..94ba53a Binary files /dev/null and b/static/images/obs-transform.jpg differ diff --git a/static/images/photobucketgrabber.jpg b/static/images/photobucketgrabber.jpg new file mode 100644 index 0000000..420f9fe Binary files /dev/null and b/static/images/photobucketgrabber.jpg differ diff --git a/static/images/salatime.jpg b/static/images/salatime.jpg new file mode 100644 index 0000000..cbc73a2 Binary files /dev/null and b/static/images/salatime.jpg differ