{ "version": "https://jsonfeed.org/version/1.1", "title": "Matt Mcadams", "language": "en", "home_page_url": "https://mattmcadams.com/", "feed_url": "https://mattmcadams.com/feed/feed.json", "description": "", "author": { "name": "Matthew McAdams", "url": "https://mattmcadams.com/" }, "items": [{ "id": "https://mattmcadams.com/log/2024/03-24/", "url": "https://mattmcadams.com/log/2024/03-24/", "title": "24 March 2024", "content_html": "
Its been a long and busy week since we were coming back from spring break. On the up side, we released the first production build of the university web framework!
\nYesterday I got to see two of my best friends for the first time in a few months and we had a great time catching up and went on a hike at Hurricane Creek. I want to make an effort of hanging out with friends more often.
\nThis website is officially part of the XXIIVV web ring! It makes me happy to be recognized and included in a group of peers I've always been really impressed by.
\nI've started watching Tsukimichi: Moonlit Fantasy since I'm all caught up on Spy x Family and Metallic Rouge. Its pretty good so far and reminds me of Reincarnated as a Slime. In terms of gaming, I've been playing around in City Skylines 2. I always struggle making organic and believable road layouts though.
\nI think that's it for this week!
\n", "date_published": "2024-03-24T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2024/skp-njk-eval/", "url": "https://mattmcadams.com/posts/2024/skp-njk-eval/", "title": "Skip template evaluation in code blocks", "content_html": "In my last post, I needed to show some njk syntax as a code example, but 11ty kept trying to evaluate the njk instead of displaying it. So, let's look at how I got around this with shortcodes.
\nThis is going to be a short one because the answer is fairly straightforward. I will mention that I did try to replace the percent signs with %
but that just printed the html entity code literally.
The solution was to add two shortcodes: lbrace
and rbrace
. I chose these names because they're the html entity codes for the characters I need. The objective of these shortcodes is to render the brace characters that start and end nunjucks statements.
// .eleventy.js
eleventyConfig.addShortcode(\"lbrace\", function () {
return `{`;
});
eleventyConfig.addShortcode(\"rbrace\", function () {
return `}`;
});
\nUsing these shortcodes, we can show an example in a markdown codeblock like this:
\n{%lbrace%}{ expression }{%rbrace%}
or
{%lbrace%}% expression %{%rbrace%}
\nI'm surprised I couldn't find any other way to escape the characters and sure, it's a little inconvenient, but it gets the job done. Anyway, thanks for reading and I hope this helps!
\n", "date_published": "2024-03-16T00:00:00Z" },{ "id": "https://mattmcadams.com/log/2024/03-16/", "url": "https://mattmcadams.com/log/2024/03-16/", "title": "16 March 2024", "content_html": "This week has been spring break, so I've been off work. Its been a nice vacation though we couldn't afford to go anywhere. I did get to hang out with a friend of mine in Tuscaloosa though and we had a ton of fun.
\nI've gotten caught up on Spy x Family and I hope they're making more. I didn't really look into it before I started that one. Not quite sure what to watch now!
\nFor drawing, I've started refreshing myself on the fundamentals and found that I still have a good enough grasp on perspective and just need to get back into doing some construction from reference or life drawing. To be honest, its a little nostalgic to want to improve at my art again... I haven't seriously drawn anything really since I thought I wanted to be a commission artist back in college.
\nI'm kind of looking forward to work starting back. We've got a few exciting projects coming up that I'm ready to get into. I've also been playing with Microsoft Copilot and think it could be a good tool to help me write documentation by generating templates and outlines.
\nOH and I've decided to go back to school for my masters. I'll be taking classes at UA since I get benefits through work. I just need to write my statement of purpose, get my references together, and submit my application.
\n", "date_published": "2024-03-16T00:00:00Z" },{ "id": "https://mattmcadams.com/log/2024/03-09/", "url": "https://mattmcadams.com/log/2024/03-09/", "title": "09 March 2024", "content_html": "Almost broke a bunch of websites at work with a bad update where I had forgotten to include the meta viewport tag, so that was a fun hotfix to put out at 6pm haha. At least I knew exactly what the problem was when I saw the bug. On the plus side of work, I made our development hub more secure by making any API calls happen at build time rather than client side, then I just set up a cron job to run the build once a day.
\nI've created a vague content schedule for the second time in my life, hopefully I'll stick to it this time. Its basically just one post a month, one or two sketches, and four logs (one each week). I've never really kept a journal, so this is an interesting medium for me.
\nIn other personal news, I’ve put a lot of thought into goals at my therapists recommendation. I think I’d like to stay where I am professionally, but grow more with personal projects. And I hope to eventually move into a new house closer to the city.
\nOh and I started learning svelte for an RSS reader I’m building. I wanted to try something new and learn a new technology. Plus on the surface it seems more lean than next JS and react which is pleasant if not a slight learning curve.
\nI think that's all for this week!
\n", "date_published": "2024-03-09T00:00:00Z" },{ "id": "https://mattmcadams.com/log/2024/03-02/", "url": "https://mattmcadams.com/log/2024/03-02/", "title": "03 March 2024", "content_html": "Hmm. lets see... This week I updated this website with more feed options. You can see them all on the feed index. I've also published a new post covering how I did the previous and next links you can see at the bottom of this page.
\nOh, this week I created an account on Gumroad to host my free typefaces. Go check it out!
\nMy husband asked me where I see myself in 5 years and honestly? I'm not sure. I used to have a Plan but lately I think I've just been floating along, surviving week by week. I'm just thankful to have gotten my creative inspiration back.
\nIn my down time, I've put Spy x Family on hold while picking up Metallic Rouge which I'm really into. The soundtrack is really nice and Falling Starlight and Moonlight II has been stuck in my head all week. It just has a really nice lo-fi cyberpunk feel that I find really enjoyable. Lyrics attached below.
\n\nI see fallen stars and moonlight\nI feel a glow in my heart\nWhen I saw you\nI loved you from the start\n-- from the start\n\nI see a sun thats always shining\nblocking out neptune or mars\nA light so blinding baby\nit compares to the stars\n\nThe stars in your eyes\nlooking out at a world, so blue\nI can't imagine my life without you\n-- without you\n\nHere's to the moonlight\nHere's to the stars that shine\nwhen you're near me\nWhen the stars fall its forever\nin a starlight world with you\n\n", "date_published": "2024-03-03T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2024/11ty-prev-next-links/", "url": "https://mattmcadams.com/posts/2024/11ty-prev-next-links/", "title": "11ty - Previous and Next links", "content_html": "
When I was developing the template I use for each of my logs, I wanted to provide quick previous and next links since the collection items are serial in nature. To being with, I did this with metadata on each individual log. This worked well to get the concept, but it was really inconvenient for long term use. Later, I looked into 11ty pagination and found this solution.
\n\nThe first and really only challenge to this method is getting the collection item for the previous and next link to point to. Luckily, this is fairly simple since 11ty includes the getPreviousCollectionItem
and getNextCollectionItem
filters. We can filter our collection like this:
<!-- template.njk -->
{% set previousPost = collections.logs | getPreviousCollectionItem %}
{% set nextPost = collections.logs | getNextCollectionItem %}
\nThis will setup two variables called previousPost
and nextPost
with each holding a single collection item or being empty if there was no collection item found.
Next we want to render the previous and next links. First, we want to wrap the whole thing in an if statement to see if either previousPost or nextPost are truthy.
\nI'm using a nav
element here, which requires a label if you're using more than one.
<!-- template.njk -->
{% if previousPost or nextPost %}
<nav aria-label=\"pagination\">
<!-- Links will go here -->
</nav>
{% endif %}
\nNow let's add the links. Here we're using if statements to prevent rendering empty html tags if the variable is empty.
\n<!-- template.njk -->
{% if previousPost %}
<a href=\"{{ previousPost.url }}\">← Previous</a>
{% endif %}
<span> </span>
{% if nextPost %}
<a href=\"{{ nextPost.url }}\">Next →</a>
{% endif %}
\nNotice that I've included a span with a non breaking space. This is just here because I want to justify the links with space between. Adding the span makes sure that the next link is always on the right even if the previous link doesn't exist.
\nHere's it all together:
\n<!-- template.njk -->
{% set previousPost = collections.logs | getPreviousCollectionItem %}
{% set nextPost = collections.logs | getNextCollectionItem %}
{% if previousPost or nextPost %}
<nav aria-label=\"pagination\">
{% if previousPost %}
<a href=\"{{ previousPost.url }}\">← Previous</a>
{% endif %}
<span> </span>
{% if nextPost %}
<a href=\"{{ nextPost.url }}\">Next →</a>
{% endif %}
</nav>
{% endif %}
\nThis part is completely optional and subjective, but I figured for completeness I'd show the CSS I used to get the layout I wanted.
\n/* styles.css */
nav[aria-label=\"pagination\"] {
display: flex;
justify-content: space-between;
}
\nI'm using an attribute selector here to target a nav element with the aria-label "pagination". I do this so that the styling is dependent on accessible markup.
\n", "date_published": "2024-02-28T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2024/forgotten/", "url": "https://mattmcadams.com/sketchbook/2024/forgotten/", "title": "Forgotten", "content_html": "\n", "date_published": "2024-02-26T00:00:00Z" },{ "id": "https://mattmcadams.com/log/2024/02-25/", "url": "https://mattmcadams.com/log/2024/02-25/", "title": "25 February 2024", "content_html": "Finally recovered from whatever flu or cold I've been fighting for the past week, so I've been super full of energy. Taxes are also done, so that's off my chest.
\nI've pushed a huge update to my color tool adding a library and organization features. I learned about reading and generating json files so that was really neat.
\nThis week I also created a new leather notebook for a smaller size that can fit in my bag. The paper size for the new notebook is A6.
\nI've finished reading I'm ok -- You're ok this week, but I'm still thinking of what I want to read next. My husband wants me to read I hate you, don't leave me so I may do that next.
\nWork continues on getting comfortable with Procreate and drawing in general again. The brush customization is really remarkable and I've continued to tweak my pencil brush to feel as natural as possible. I kept having an issue where, when shading, if I would lift the pen and put it back down it would create dark lines where they overlapped. Thankfully I was able to fix this by adjusting the opacity settings in the brush itself.
\n", "date_published": "2024-02-25T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2024/cave/", "url": "https://mattmcadams.com/sketchbook/2024/cave/", "title": "Cave", "content_html": "\n", "date_published": "2024-02-21T00:00:00Z" },{ "id": "https://mattmcadams.com/log/2024/02-18/", "url": "https://mattmcadams.com/log/2024/02-18/", "title": "18 February 2024", "content_html": "Work continues on the WordPress theme refactor at UA and we now have a working prototype of the new templates.
\nI've also released a tool for generating color scales, which I'm calling ColorScale. This was a lot of fun to build and I learned a lot of new things about javascript. For example, when comparing objects you need to JSON.stringify
them first otherwise you might get a false negative. I also learned that you can accidentally modify the value of a const
.
This week I also made the first major update to my website in about a year. Adding more projects and expanding the scope of projects. I also added several meta pages to make my little corner of the web feel more personal, this journal being one of them.
\nI haven't gotten much done in terms of reading this week. I've had to be on campus in person on Valentine's Day and I've been feeling under the weather since then. I did start a new anime this week, Spy x Family. I'm only a handful of episodes in, but it seems like a clever set of characters and I'm interested to see what trouble they get up to.
\n", "date_published": "2024-02-18T00:00:00Z" },{ "id": "https://mattmcadams.com/log/2024/02-11/", "url": "https://mattmcadams.com/log/2024/02-11/", "title": "11 February 2024", "content_html": "Our VP has finally given us permission to talk about it, so here goes - I got a new title and a slight raise at work! I'm super happy about this as the changes to the compensation structure means I have a lot more room to grow. I love working at UA, so I'm happy to see myself here for a long time.
\nI decided to revert our WordPress theme back to classic templates from the block editor. This was not without its problems. We decided to leave index.html
alone for the time being to give users time to migrate. Stakeholders seem cautious or indifferent, but no one seemed terribly upset by the news.
In personal news, I finished Hunter x Hunter this week. It was incredibly good, I don't know why I put watching it off for so long.
\nI hope to write a log here each week, but I know I've never been great at keeping up with tight content generation schedules, so we'll just see how it goes.
\n", "date_published": "2024-02-11T00:00:00Z" },{ "id": "https://mattmcadams.com/projects/colorscale/", "url": "https://mattmcadams.com/projects/colorscale/", "title": "ColorScale", "content_html": "I've often had to generate a color scale based on a brand color, needing a tool that will allow the user to control the number of dark and light steps. There are a number of tools already for generating color scales, but this one focuses on the key color and also brings color graphing and easing functions to the table.
\n\nThere's much more information about this project over on GitHub, and you can try it out live at colorscale.app.
\n", "date_published": "2024-02-10T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2023/displaying-git-info-on-prompt/", "url": "https://mattmcadams.com/posts/2023/displaying-git-info-on-prompt/", "title": "Displaying Git info on the command prompt", "content_html": "In my previous post, I looked at ways to customize the ZSH prompt. Now let's take it a bit further and show some version control information on the right side of the prompt as well.
\n\nFirst let's set up support for the version control system. To do this, we need to autoload the version control system with autoload -Uz vcs_info
. Then we'll need to wrap that in a function and add that function to the builtin precmd_functions
.
autoload -Uz vcs_info
precmd_vcs_info() { vcs_info }
precmd_functions+=( precmd_vcs_info )
\nWe also need to tell the prompt string to first be subjected to parameter expansion, command substitution and arithmetic expansion. We do this with setopt prompt_subst
. We need another line to let us style the git information.
setopt prompt_subst
zstyle ':vcs_info:git:*' formats '%b'
\nLet's consider these goals as we work through displaying git information.
\nNow for the hard part, we need to use a variety of git commands to get information about git's status. I'm going to wrap all of this in a function called __git_prompt_status()
so keep in mind from this point forward, we'll be working inside this function.
First, we'll define some local veriables.
\nlocal INDEX TRACKING AHEAD BEHIND STATUS EDITS
\nNext, initialize the STATUS
variable. This is where we'll store the string we want to display on the prompt.
STATUS=\"\"
\nFor my use case, I don't care to know the number of stashes, only if a stash exists. We can do this by printing each stash entry and checking to see if anything is returned.
\nWe'll then add to the STATUS a cyan return icon ↩︎
.
if $(command git rev-parse --verify refs/stash &>/dev/null); then
STATUS=\"%F{cyan}↩︎%f $STATUS\"
fi
\nTo display if the local branch is tracking a deleted branch, or no branch at all, we'll need to set up a variable TRACKING
. Then, to see how many commits the branch is ahead or behind, we need to set up the variables AHEAD
and BEHIND
.
Tracking will print a message from git that we can use later. If no upstream branch is found, the message will be fatal: no upstream configured for branch 'BRANCH NAME'
. If the upstream branch has been deleted, the message will start with fatal: ambiguous argument '@{upstream}': unknown revision or path not in the working tree.
. Otherwise the message will equal the name of the upstream branch.
Breaking down ahead and behind, the git command here will print the commit hash for each commit the branch is behind or ahead by. We can take this and count the lines, then remove extra space.
\nTRACKING=$(git rev-parse --abbrev-ref @{upstream} 2>&1)
AHEAD=$(git rev-list HEAD@{upstream}..HEAD 2> /dev/null | wc -l | awk '{$1=$1};1')
BEHIND=$(git rev-list HEAD..HEAD@{upstream} 2> /dev/null | wc -l | awk '{$1=$1};1')
\nWe can use this tracking message and look for some specific language git uses in it's message.
\nCheck if there is no upstream by searching the return for "no upstream". In this case, I want to display a yellow house icon ⌂
in the first part of the status.
Te detect if the upstream has been deleted, search the return for "@{upstream}" In this case, I want to display a red exclamation point !
in the first part of the status.
if $(echo \"$TRACKING\" | grep 'no upstream' &> /dev/null); then
STATUS=\"%F{yellow}⌂%f $STATUS\"
elif $(echo \"$TRACKING\" | grep '@{upstream}' &> /dev/null); then
STATUS=\"%B%F{red}!%f%b $STATUS\"
\nIf neither of those points are true, we can continue to see how far behind and/or ahead the branch is. This section will say if AHEAD
is greater than 0, display the number of commits the branch is ahead along with a green up arrow ↑
. Additionally, if BEHIND
is greater than 0, display the number of commits the branch is behind along with a red down arrow ↓
This is the second and final part of the if statement we started above.
\nelse
if [[ $AHEAD -gt 0 ]]; then
STATUS=\"$STATUS$AHEAD%F{green}↑%f \"
fi
if [[ $BEHIND -gt 0 ]]; then
STATUS=\"$STATUS$BEHIND%F{red}↓%f \"
fi
fi
\nLastly, we need to set up the variable INDEX
, which stores a response from git which prints each uncommitted file with changes to it's own line. We'll use this variable to setup EDITS
to store the number of uncommitted changes.
INDEX=$(git status --porcelain 2> /dev/null)
EDITS=$(echo $INDEX | wc -l | awk '{$1=$1};1')
\nNow to display the number of edits with a bold, blue asterisk *
at the end of the status.
if [[ ! -z \"$INDEX\" ]]; then
STATUS=\"$STATUS$EDITS%B%F{blue}*%f%b \"
fi
\nThe last step is to return the status string if it's not empty.
\nif [[ ! -z \"$STATUS\" ]]; then
echo \"$STATUS\"
fi
\n__git_prompt_status() {
local INDEX TRACKING AHEAD BEHIND STATUS EDITS
INDEX=$(git status --porcelain 2> /dev/null)
TRACKING=$(git rev-parse --abbrev-ref @{upstream} 2>&1)
AHEAD=$(git rev-list HEAD@{upstream}..HEAD 2> /dev/null | wc -l | awk '{$1=$1};1')
BEHIND=$(git rev-list HEAD..HEAD@{upstream} 2> /dev/null | wc -l | awk '{$1=$1};1')
STATUS=\"\"
EDITS=$(echo $INDEX | wc -l | awk '{$1=$1};1')
if $(command git rev-parse --verify refs/stash &>/dev/null); then
STATUS=\"%F{cyan}↩︎%f $STATUS\"
fi
if $(echo \"$TRACKING\" | grep 'no upstream' &> /dev/null); then
STATUS=\"%F{yellow}⌂%f $STATUS\"
elif $(echo \"$TRACKING\" | grep '@{upstream}' &> /dev/null); then
STATUS=\"%B%F{red}!%f%b $STATUS\"
else
if [[ $AHEAD -gt 0 ]]; then
STATUS=\"$STATUS$AHEAD%F{green}↑%f \"
fi
if [[ $BEHIND -gt 0 ]]; then
STATUS=\"$STATUS$BEHIND%F{red}↓%f \"
fi
fi
if [[ ! -z \"$INDEX\" ]]; then
STATUS=\"$STATUS$EDITS%B%F{blue}*%f%b \"
fi
if [[ ! -z \"$STATUS\" ]]; then
echo \"$STATUS\"
fi
}
\nThis is all well and good, but all we've done is create a function that echos the git information. First, we have to make sure we're in a directory with version control. I created this function to handle that, display the branch name followed by the branch status, all wrapped in brackets.
\n__prompt_git_info() {
[ ! -z \"$vcs_info_msg_0_\" ] && echo \"[ $vcs_info_msg_0_ $(__git_prompt_status)]\"
}
\nI like to display the git information to the right of my prompt, so we'll assign the function to the builtin RPROMPT
variable. I also added a timestamp with %t
.
RPROMPT='$(__prompt_git_info) %t'
\n",
"date_published": "2023-12-01T00:00:00Z"
},{
"id": "https://mattmcadams.com/projects/minerva/",
"url": "https://mattmcadams.com/projects/minerva/",
"title": "Minerva Web Framework",
"content_html": "Minerva is the third iteration of the University of Alabama's web framework and is used to build the institutional Wordpress theme among other enterprise applications. I began working on a design system for the university back in 2021, as their senior designer. This design system would eventually become the bones of the Minerva framework. When I rejoined the team in 2022 as the assistant director, it was a great joy to oversee the continued development of this project, and I continued to work hands-on with the development team as they waded through other large departmental shifts.
\nDesigning at the scale of the university was a big challenge, but it gave us a few goals that would govern how we built the framework.
\nFirst of all, the framework and all of it's sub projects must be accessible and in compliance with WCAG 2 AA level standards. The layout system must be responsive, adapting to any viewport size, device type, or input.
\n\nThe framework also had to be platform agnostic since there are many tech stacks used across campus. Lastly, all components and compositions needed to be modular and flexible enough to adapt to any content needs campus communicators have. Unknown user input meant designing and building pieces that could fit together in a wide number of ways.
\nA year ago, the team did not have peer code reviews, no linting or tests, no documentation. This meant that earlier versions of the institutional wordpress theme could only reliably be worked on by one of the few developers that has historical knowledge of the codebase. This, of course, let to a maintainability nightmare. To remedy this, I worked with the team to establish some industry best practices and reinforced the value of documentation and encouraged our developers to write self documenting, readable code - adding comments for anything that might be unclear.
\n\nIn addition to new CI/CD checks and a modernized devops experience, the team built a beautiful collection of documentation around the new web framework and Wordpress theme. Integrating documentation into our development workflow has ensured our docs are always up to date, and our code adheres to the division's new higher standards. The team also meets with content managers frequently to see and understand how the product is performing in the wild.
\nWe decided to use React for the framework because it allowed us to more easily document component behavior. All of our components compile into plain HTML, CSS, and JavaScript which can be used as reference to build implementations in other languages - or even for small static built sites. The use of React, also opened us up to tools like Storybook, which we used to mock up use cases and test for regressions.
\n\nFor the wordpress theme, we used PHP backed gutenberg blocks so that any markup changes would be reflected instantly. Leveraging the Gutenberg block editor, also placed a few technical restrictions on how we thought about components and their use cases.
\nWhile its still early days of adoption for the new design system and web framework, more than a handful of sites have launched on pre-release versions of the new theme. Minerva is even being used directly as a React component library in the new developer documentation hub, built with NextJS.
\n\nThe team has received overwhelmingly positive feedback from stakeholders and users alike, saying that the new theme is beautifully simple, easy to generate content with, and friendly to navigate. Campus partners say the development experience and new documentation has changed how they approach web development, cutting out months of boilerplate and bloat. Most times, developers have found that the modifications their department or division needed before were now native to the framework.
\nThe team hopes to roll the system out over more sites over 2024, including a new design for UA home, and a few enterprise applications.
\n", "date_published": "2023-11-19T00:00:00Z" },{ "id": "https://mattmcadams.com/projects/task-cards/", "url": "https://mattmcadams.com/projects/task-cards/", "title": "Task Cards", "content_html": "I like stationary and making stuff with paper. I also love being organized, and wasing as much time as possible to become organized. This pair of traits led me to creating these task cards. I should also say these are heavily inspired by Analog.
\n\nThe Today card has a short list of actionable items, designed to help you focus on just a handful of key priorities. It has a little area for notes under the tasks, as well as a pomodoro tracker.
\nThe pomodoro technique is where you work for 25 minutes then take a 5 minute break. You repeat this for 4 sets of working segments then take a 30 mintue break.
\nThe Next card is to help organize whats on the horizon. Maybe these are tasks that need your attention, but they're not designated for today.
\nThe Someday card is for items you need to remember or get around to at some point, but they generally have no deadline and are not critical.
\nEvery card features a dot grid back for notes.
\nYou can download the printable PDF here
\n", "date_published": "2023-11-15T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2023/customizing-zsh-prompt/", "url": "https://mattmcadams.com/posts/2023/customizing-zsh-prompt/", "title": "Customizing ZSH", "content_html": "\nWhile I was learning about terminal customization, I found that many websites tend to leap straight to projects like "oh-my-zsh" where you can choose from many pre-configured themes. Don't get me wrong, these projects are impressive and show how wildly flexible ZSH can be, but I wanted to learn something along the way. Since I learn best by doing, I wanted to explore and build my configuration by hand.
\nFor my prompt, I use hyper with the One Dark (Vivid) Theme.
\nWe'll start with something simple, my list of aliases. I don't use the terminal so much that I need a lot of these, so I've focused on small quality of life shortcuts. You'll also find shortcuts for commands I forget all the time. I store these (and my functions) in an _aliases.zsh
file I load into my .zshrc
file.
My first set of aliases just lists various customizations I've made. aliases
lists all of my aliases, functions
lists my functions, and paths
lists all paths being loaded.
alias aliases=\"alias | sed 's/=.*//'\"
alias functions=\"declare -f | grep '^[a-z].* ()' | sed 's/{$//'\"
alias paths='echo -e ${PATH//:/\\\\n}'
\nFor some reason, I always have to look up how to make a script executable, so I made a shortcut make-exe
to help me remember. I also forget how to create a symbolic link, so I made symlink
and while this is more to type out, it keeps me from forgetting it. Lastly, I never know when I'll be required to use sudo
so I made an alias please
to rerun the last command with sudo.
alias symlink=\"ln -s\"
alias make-exe=\"chmod 700\"
alias please=\"sudo !!\"`
\nNext, as a web developer, sometimes I have to mess with my hosts file. I made host-edit
and clean-dns
to make that easier.
alias host-edit=\"sudo -b /System/Applications/TextEdit.app/Contents/MacOS/TextEdit /etc/hosts\"
alias clean-dns=\"sudo killall -HUP mDNSResponder\"
\nOn the topic of network related things, I also have an alias to show my local ip address, without all of the other information.
\nalias ip=\"curl ifconfig.me\"
alias ip-wifi=\"ipconfig getifaddr en0\"
alias ip-wired=\"ipconfig getifaddr en1\"
\nI ask this question a lot and depending on the software, the command could be different. Most seem to use -v
but others require you to write out -version
so I made a function to help me remember. This way, I can type version name
where name is the command I want to know about.
function version {
if [ \"$1\" = \"git\" ]
then
git --version
elif [ \"$1\" = \"node\" ]
then
node -v
elif [ \"$1\" = \"npm\" ]
then
npm -v
elif [ \"$1\" = \"php\" ]
then
php -v
elif [ \"$1\" = \"composer\" ]
then
composer -V
else
echo \"Options: [git, node, npm, php, composer]\"
fi
}
\nYou can find a complete list of prompt escapes on the ZSH docs, including login information, shell state, date and time, formatting, and more.
\nBefore we really get started on the prompt, I want to style my path. This involves a very complicated string.
\n# This sets up some fancier formatting for the path
ZSH_PROMPT_PATH='${${${${(%):-%~}/${(%):-%1~}/ %B${(%):-%1~}%b}/ %B${(%):-%-1~}%b/%B${(%):-%-1~}%b}//\\//%B%F{cyan\\}/%f%b}'
\nBreaking this down, ${(%):-%~}
is the full path, ${(%):-%1~}
is the last item in the path, %B${(%):-%1~}%b
is the styled version of the last item. The slashes /
perform a substitution "source/find/replace. Each time a substitution happens, the previous one must be wrapped in ${}
and the enture string has to be wrapped in ${}
as well. I save this path string to a variable ZSH_PROMPT_PATH
so we can use it later. %B
and %b
styles the text as bold while %F{color}
and %f
sets the text color.
The end result is a path where the separators are bold cyan and the last folder in the path is bold.
\nFor my personal prompt, I like having an empty line followed by the full path on it's own line, then my username the @
symbol and the machine name. I also like having the time on the right side. To achieve this, I'll start by creating a function in my .zshrc
file. This function will tell the prompt to print a blank line, then print the path using the ZSH_PROMPT_PATH
variable we just made.
__prompt_precmd() {
# Pass a line before each prompt
print -P ''
# Enable only for multiline prompt
print -rP $ZSH_PROMPT_PATH
}
\nFor this function to work, we need to load it into the precmd hook. You'll need to load add-zsh-hook
first, then use our function to tell it what to do.
# Add precmd hooks
autoload -Uz add-zsh-hook
add-zsh-hook precmd __prompt_precmd
\nNow we get to the prompt line itself. %n
displays the username and %m
displays the machine name. Then I add a bold cyan arrow to signify the end of the prompt.
PROMPT=\"%n @ %m %B%F{cyan}❯%f%b \"
\nLastly, to add the time to the right side of the prompt, you can use RPROMPT
and %t
. You can put whatever information you like on the right side.
RPROMPT=\"%t\"
\n\nYou can add character artwork or letters at the beginning of your terminal by echoing each line in your .zshrc
file. At the time of writing, I use this cute cat and my initials to add a little bit of personality. Not that you'll need to escape some characters with \\
.
echo \" |\\---/|\"
echo \" | ,_, |\"
echo \" \\_'_/-..----. ____ ___ ____ ___\"
echo \" ___/ ' ' ,--+ \\ / __ \\`__ \\/ __ \\`__ \\\\\"
echo \"(__...' __\\ |'.___.'; / / / / / / / / / / /\"
echo \" (_,...'(_,.'__)/'.....+ /_/ /_/ /_/_/ /_/ /_/\"
\nIf you want to check out the full code used here, in addition to all of the other modifications I've made, check out my dotfiles on GitHub!
\n", "date_published": "2023-11-05T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2023/arch/", "url": "https://mattmcadams.com/sketchbook/2023/arch/", "title": "Arch", "content_html": "\n", "date_published": "2023-08-12T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2023/nebula/", "url": "https://mattmcadams.com/sketchbook/2023/nebula/", "title": "Nebula", "content_html": "\n", "date_published": "2023-08-10T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2023/ship/", "url": "https://mattmcadams.com/sketchbook/2023/ship/", "title": "Ship", "content_html": "\n", "date_published": "2023-02-22T00:00:00Z" },{ "id": "https://mattmcadams.com/projects/laws-of-ux-cards/", "url": "https://mattmcadams.com/projects/laws-of-ux-cards/", "title": "Laws of UX Cards", "content_html": "I'm a huge fan of the Laws of UX website by Jon Yablonski. I think its a great resource for designers learning about user experience.
\n\nI made these flash cards as a companion piece for myself, a coworker, and a designer friend of mine, and I thought you might could use them too.
\nYou can download the template for free here
\nI printed these on 110lb cardstock and cut with my Cricut. Then, I folded them over with some craft glue to keep them together and rounded the corners with my corner cutter. This makes for a really thick and stiff card.
\n", "date_published": "2023-01-23T00:00:00Z" },{ "id": "https://mattmcadams.com/projects/dotfiles/", "url": "https://mattmcadams.com/projects/dotfiles/", "title": "Dotfiles", "content_html": "\nDotfiles are something few people care about, but they hold all of the configuration settings for key systems. Heavily modified setups need to be backed up and this also allows us to quickly get back up and running with a new computer.
\nMy dotfiles holds my git configuration and some extensive modifications to zsh, many of which I talk about in my blog. It may not seem like much, but this is a project close to my heart and brings me a great deal of joy.
\nThere's much more information about this project over on GitHub.
\n", "date_published": "2022-12-20T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2022/overlook/", "url": "https://mattmcadams.com/sketchbook/2022/overlook/", "title": "Overlook", "content_html": "\n", "date_published": "2022-11-08T00:00:00Z" },{ "id": "https://mattmcadams.com/projects/new-tab-page/", "url": "https://mattmcadams.com/projects/new-tab-page/", "title": "New Tab Page", "content_html": "This was something I made for myself and then made it open source in case anyone wanted to use it or learn from it. Basically, I'm underwhelmed by the default new tab page setup in any browser so I made my own. It uses the bookmarks API and some JSON configuration to display collections of bookmarks with FontAwesome icons.
\n\nIt also has a reference section, which is hard coded at the moment to display some common HTML entities I use a lot, with a link to the full list.
\nAt the bottom of the page is a extendable library, again using the bookmarks API to pull folders of bookmarks.
\nIf you're interested, check out the project on GitHub.
\n", "date_published": "2022-05-05T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2022/code-comments-revised/", "url": "https://mattmcadams.com/posts/2022/code-comments-revised/", "title": "Code Comments Revisited", "content_html": "Before we get started, please be aware I'm going to be using Visual Studio Code with the Comment Anchors extension. I really love the ability to place easy to skim, important callouts in my code.
\nIt's my opinion that you should write code that is easy to read. That doesn't mean to throw away abstraction or ignoring DRY principals, but you should weigh the gains of any refactor against how opaque it makes the code. Easy to read code doesn't need comments to explain how it works, any developer should be able to figure that out by reading the code.
\nAs a rule of thumb, avoid using comments to describe exactly how the code works, use them to answer questions instead. Comments are most powerful when they provide context about the code or answer questions not easily answered by reading the code itself.
\nUltimately, how you use comments are up to you and the project manager if you have one. Everyone has a different preference and system, but here's what works for me:
\nThe last rule to writting a good code comment is to ignore the rules. If you think adding a given comment will improve code clarity, by all means, write the comment!
\nIn general, I have a few rules about comment formatting:
\nI've yet to hear a definitive "right" answer to this age old controversy. My take is that you should setup your code editor to use soft tabs set to 2 spaces. My reason for this are:
\nYou can also set this setting up for individual workspaces if you work on projects with different indention rules. Ultimately, I don't think it matters too much, spaces have just never caused me problems while tabs have.
\nThe type of comment people are most familiar with, I use single line comments to make a short note to describe some code, for example:
\n/* Decrease space between sequential headings */
h1 + h2,
h2 + h3,
h3 + h4,
h4 + h5,
h5 + h6 {
margin-top: 0;
}
\nThis style of comment is meant to remind me what a specific piece of code does and why it is needed. They are short and to the point, thus only requiring one line.
\nThese are the complicated ones that have to explain how a function works or what it does. Typically, I prefer to use block level comments as documentation and rely on line by line comments to walk through what the code is doing if it's not obvious.
\n\n\nI think it's important to note here that I favor reasonably verbose code with plenty of comments over excessively opaque things that are hard to read. In my opinion, as long as the code does its job well and there are no performance concerns, the "right" way to code is the way that will allow future developers - maybe even your future self - to understand and modify the code more easily.
\n
Let's look at how I've documented this old JS function
\n/** ==============================
* ANCHOR Modular Scale
* Returns a number on a modular scale defined by the ratio
============================== **/
function modularScale (increment, base = 1, ratio = CONFIG.ratio) {
if (increment === 0) { return base; }
if (increment < 0 ) {
increment = increment * -1; // remove negative from number
return base / pow(ratio, increment);
}
return base * pow(ratio, increment);
}
\nI use a block comment to introduce the function and describe what it does. This is also a good place to define all arguments and parameters the function accepts, their expected types, defaults, and what they do.
\nAnother way I've used block comments is to group a bunch of single line comments to keep the code cleaner. Let's take a look:
\n/** ==============================
* [1] Remove default list margins
* [2] Allow content to sit on same row
* [3] Allow content to wrap if there's not enough room
* [4] Force content to wrap if the content would be less than 300
* [5] Set a low starting width - Would be 1/3, but we'll go much less than
* that to give room for padding
* [6] Add space between the items left and right
* [7] Allow items to fill remaining space
===============================**/
ul.it-news {
/*[1]*/ margin-left: 0;
/*[2]*/ display: flex;
/*[3]*/ flex-wrap: wrap;
}
.it-news > .newsflash-item {
/*[4]*/ min-width: 300px;
/*[5]*/ width: 20%;
/*[6]*/ padding: 0 token.space(0.5);
/*[7]*/ flex-grow: 1;
}
\nThis is one of the most important parts of code commenting you can do. To clearly divide code into logical sections within a single file creates landmarks that are easy to find when skimming. I use the plugin Comment Anchors to augment this practice, since it will add a list of all anchors to a sidebar for easy navigation. Section anchors allow you to define a region and group other anchors or even other sections together.
\nFor a section comment, I create a wide divider and include the section anchor, name, and any information about the section before closing it with another wide divider.
\n/** =================================================================
* SECTION Tokens
================================================================= **/
\nIt's important that when using the section
anchor, you also "close" it at the end of the section. I usually do this by adding a one line comment right above the next section's heading.
/* END !SECTION Tokens */
/** =================================================================
* SECTION Universal Defaults
================================================================= **/
\nI mentioned earlier that you can nest section anchors. I still prefer to leave these ultra-wide comments for top level sections, so for sub sections, I use my usual block comment style.
\n/** ==============================
* SECTION Typography
============================== **/
\nI've touched on anchors a bit with sections, but there are other great keywords you can use to add landmarks to a file. The Comment Anchors documentation outlines the ones it comes with by default, you can also add and customize your own.
\nHere's my anchors and what I use them for:
\nMany of these should never make it into production, but can be useful flags for your own reference working on a branch or during a code review / team coding.
\nI'd probably suggest resolving a FIXME
before pushing code to origin, or removing the comment and converting it to a proper ticket in your issue tracker.
I'd use TODO
for something quick, like a reminder for when I come back from lunch. Proper task management should probably be kept in a different program.
It is common for developers to comment out a chunk of code while exploring a different approach. TEMP
is a great way to mark these commented out bits making cleanup easier before submitting a pull request.
REVIEW
can be used as a reminder to yourself that you need to follow up on some research before considering something "done", maybe the code works, but you need to do some A11Y research to make sure the implementation is accessible. It's also a useful tool to flag code if you're working in a pair programming environment.
The other anchors may have a much longer lifespan depending on the nature of the comment.
\nBeing able to create meaningful bookmarks in code can be incredibly helpful, and thanks to Comment Anchors, we can take it a step further.
\nCreate links to code and other files with the special LINK
anchor. You can even link to a specific anchor within a file. Linking to another file is simple enough, use the link anchor followed by an absolute or relative file path.
// LINK src/file.js -->
\nTo link to a specific line, add ":#" where "#" is the line number.
\n// LINK src/file.js:175 -->
\nLinking to another anchor is a bit trickier, you'll need to set up the destination anchor with an ID, then link to it similar to how you would link to an ID of an element on a web page.
\n// Inside a different file: ANCHOR[id=My-ID]
// LINK src/file.js#My-ID -->
\n\n\nNote that as far as I can tell, there is no way to link to a spot in the same file. It's also important to note that links must point to a valid file in the project. One broken link will break all others in the document.
\n
I know a lot of developers like to keep the code free of comment clutter, but when used responsibly, these types of tags and comments can be really helpful. Plus, code comments can work fantastically alongside issue tracking systems like GitHub. For example, you could reference an edge case bug or conversation by adding the issue number or a link to the issue in a comment, providing richer context for future developers.
\nComment Anchors will highlight the entire comment line as well as provide a list of anchors established. The plugin is also extremely customizable, so you can add or remove anchors to fit your style.
\nDownload my personal configuration.
\n", "date_published": "2022-05-01T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2022/black-cat-apothecary/", "url": "https://mattmcadams.com/sketchbook/2022/black-cat-apothecary/", "title": "Black Cat Apothecary", "content_html": "\n", "date_published": "2022-04-19T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2022/resizing-a-window-with-shortcuts/", "url": "https://mattmcadams.com/posts/2022/resizing-a-window-with-shortcuts/", "title": "Resizing a MacOS window with the Shortcuts app", "content_html": "After getting my home office fully set up I found that I had a very niche and first world problem: When I share my screen or even a window from my ultrawide monitor during a zoom meeting, the image is too small for people on normal sized screens to see. I decided to make a button that would resize my last used window to the fullscreen size of my macbook pro. This way I could easily setup a window for screen sharing without needing to disconnect the macbook just for a meeting.
\nFirst we need to get information about the open windows. Luckily there is a way to filter this operation to almost exactly what we need.
\nFind \"All Windows\" {
Sort by: \"Window Index\"
Order: \"Smallest First\"
Limit: True
Get \"1\" Window
}
// This will return an array stored in the variable *Windows*
\nThis will return the window with the highest scoring window index. This is helpful because that should be the exact window we need.
\nTheoretically, you could get more than 1 window for testing or debugging, so to make sure the script handles that gracefully, I like to return the first item as a seperate variable. We'll use this variable as the target for resizing later.
\nGet \"First Item\" from *Windows*
Set variable \"Window\" to *Item from List*
\nOnce we have the single Window
to use as a target, we can resize it using the "Resize Window" action.
Resize *Window* to \"Dimensions\" \"1792 x 1120\"
\nThere is one bug with this however. If the active window is too lose to either the bottom or right edges of the screen, it will not be resized properly. To fix this, we'll also move the window into the top left corner of the screen.
\nMove *Window* to \"TopLeft\"
Resize *Window* to \"Dimensions\" \"1792\" x \"1120\"
\nThe only major problem with this shortcut is with how the window index system works. In some cases certain indicators, tooltips, widgets, and other UI extensions will have a higher index value by mistake. These are not often the desired targets and if they can't be resized it may cause an error.
\nTo get around this, I've created an if statement to detect likely problem windows by testing their width. Most small ui widgets or overlays are less than 100 wide. Inside this if statement, I set up some warning text and display an alert.
\nSet variable \"Window\" to *Item from List*
...
If \"*Window.width*\" \"is less than\" \"100\" {
Text {
WARNING
The selected window has a width smaller than 100. This likely means it is not the intended target of this function.
---
\"Index\" : *Window.Window Index*
\"Title\" : *Window.Title*
\"App Name\" : *Window.App Name*
\"Width\" : *Window.Width*
---
}
Set variable \"Warning Message\" to *Text*
Show Alert *Warning Message*
End If }
...
Move *Window* to \"Top Left\"
Resize *Window* to \"Dimensions\" \"1792\" x \"1120\"
\nThis way we can get some timely debugging info and a more useful error message than a vague "An error has accured" message when the shortcut fails.
\nTo fix this, you need to add a "Filter" to the Find Windows action. The filter should look something like this:
\nFind \"All Windows\" where
\"All\" of the following are true
\"App Name\" \"is not\" \"STRING\"
\nThe STRING
used after the is not
in the filter must match the exact spelling, capitalization, and punctuation of the "App Name" responsible for window you want to exclude from the search.
One of the helpful things about adding some debugging options is that it gives you a much easier and complete view of the window data you're working with. When working with multiple windows from the find action, it can be helpful to display an array of each object in the list. Let's work on building this output first.
\nRepeat with each item in *Windows*
Text {
---
\"Index\" : \"Repeat Item > Window Index\"
\"Title\" : \"Repeat Item > Repeat Item\"
\"App Name\" : \"Repeat Item > App Name\"
\"Width\" : \"Repeat Item > Width\"
}
Add *Text* to *Window Array*
End Repeat
Text {
---
}
Add *Text* to *Window Array*
\nThis will provide an easy to read list of every window found.
\nNext I make a debug info variable that we can use in the output action, which acts like a console.
\nText {
DEBUG INFO
TARGET
---
\"Index\" : *Window.Window Index*
\"Title\" : *Window.Title*
\"App Name\" : *Window.App Name*
\"Width\": *Window.Width*
---
ALL WINDOWS
*Window Array*
}
Set variable \"Debug Info\" to *Text*
\nThen we can display the debug info at any step in the Shortcut.
\nStop and output *Debug Info*
If there's nowhere to output: \"Do Nothing\"
\nThis is a pretty cool exploration of a practical use of Shortcuts, and to test their ability. Its a great way to learn more about how MacOS works too. There is also a really cool "set up" option to ask for user inputs the first time the shortcut in ran on the machine, which would be really useful for configuration. For example, you might want to customize the resize dimensions on installation.
\nI'm interested to see if there's a lot of examples of people using Shortcuts for genuinely useful or complex tasks. Most of the options I've looked through seemed like very shallow app specific actions.
\nYou can download this shortcut and use as-is, or expand on to fit your needs.
\n", "date_published": "2022-04-05T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2022/working-with-11ty-collections/", "url": "https://mattmcadams.com/posts/2022/working-with-11ty-collections/", "title": "Working with 11ty Collections", "content_html": "When building my website, I didn’t expect the biggest challenge to be having a separate collection for posts and projects, each collection with their own independent list of tags.
\nAt its face value, it doesn't seem that big of a problem, but the way 11ty handles content by default creates a lot of opportunities for cross-collection pollution. For example, 11ty doesn't have a clear way to create a list of all "posts" with the "typography" tag, it really wants to provide one or the other.
\nLet’s start at the simplest point, by making a custom collection that only contains my posts. One way to do this is to get items by a functional "post" tag present on all of my posts, but this pollutes the tag list with junk I'd need to filter out. I want to avoid this wherever possible.
\nInstead, we can create a collection based on the content of a directory:
\neleventyConfig.addCollection(\"posts\", function (collectionAPI) {
return collectionAPI.getFilteredByGlob(\"./src/posts/*.md\");
});
\nNow I can get a list of posts with collections.posts
, which is a good starting point. Now, let’s say we want to list all of the tags in a specific content type. This is useful for making a tag directory among other things.
Because I have to create two collections using the same logic, just pulling from a different source, I chose to make this a function that takes a collection as an argument.
\nFirst, we create a new tag set and loop through each item in the collection, adding the tags as we go.
\n// Return a list of tags in a given collection
function getTagList(collection) {
let tagSet = new Set();
collection.forEach((item) => {
(item.data.tags || []).forEach((tag) => tagSet.add(tag));
});
return filterTagList([...tagSet]);
}
\nYou may notice I also run the result through a filterTagList
function I declaired earlier. This is not doing anything in my code anymore, since I no longer use utility tags that need to be filtered out. I decided to leave it in though, just in case I need it later. You can create the tag filter like so:
function filterTagList(tags) {
return (tags || []).filter(
(tag) => [\"utility-tag\", \"unwanted-tag\"].indexOf(tag) === -1
);
}
eleventyConfig.addFilter(\"filterTagList\", filterTagList);
\nNext, we'll use the getTagList
function to create the actual collection.
eleventyConfig.addCollection(\"postTags\", function (collectionAPI) {
let collection = collectionAPI.getFilteredByGlob(\"./src/posts/*.md\");
return getTagList(collection);
});
\nNow let’s say we want to list all of the projects with the design tag. This gets much more difficult. 11ty does not have a convenient way to list items in a collection filtered by what is, essentially, another collection.
\nWe could do collections[tag]
OR collections.type
but if we use the “design” tag in both projects and in posts (as I do), you’ll get pollution and have to filter out the other content type, which would ultimately require you have extra info in the frontmatter to filter by.
This took a lot of sleuthing to figure out. Huge credit to Laurence Hughes, who solved a really similar problem that led me to the solution to mine.
\nThe ideal functionality is to be able to use the collectionAPI to get a list of posts with a specific tag with collections.posts[tag]
and luckily we can do just that.
Again, I created this as a function, because I'll need to do the exact same thing with projects later. This gets a little complicated so I tried to walk through what the code is doing in the code comments.
\n// Return an object with arrays of posts by tag from the provided collection
function createCollectionsByTag(collection) {
// set the result as an object
let resultArrays = {};
// loop through each item in the provided collection
collection.forEach((item) => {
// loop through the tags of each item
item.data.tags.forEach((tag) => {
// If the tag has not already been added to the object, add it as an empty array
if (!resultArrays[tag]) { resultArrays[tag] = []; }
// Add the item to the tag's array
resultArrays[tag].push(item);
});
});
// Return the object containing tags and their arrays of posts
// { tag-name: [post-object, post-object], tag-name: [post-object, post-object] }
return resultArrays;
}
\n\n\nBy this point, I no longer needed
\nfilterTagList
and so the above snippet does not filter the tags it creates collections for. If you DO use a tag filter, you may want to add some logic to deal with that here.
Now we can create the actual collection with this:
\neleventyConfig.addCollection(\"postsTagged\", function (collectionAPI) {
let collection = collectionAPI.getFilteredByGlob(\"./src/posts/*.md\");
return createCollectionsByTag(collection);
});
\nWith all the above added, I can now:
\ncollections.posts
collections.postTags
collections.postsTagged["tag"]
And I can do the same thing with projects.
\nBut what if we wanted to kick it up a notch?
\n\n\nI've got to warn you that this may or may not be a good idea. As far as I know, there isn't a really good reason to do this, and 11ty doesn't always handle nested collections in the way you'd expect. For example, the 11ty pagination feature won't understand nested collections from my experiments. Hopefully these are classified as bugs and get worked out in the future. I opened an issue on the eleventy repo to ask about this behavior.
\n
I was inspired to do this after realizing the collection created by addcollection
is essentially a javascript object that you can manipulate to your needs.
I combined the above collections into two: posts and projects. Using these two collections, I can get all the information I need in a nested way.
\n// Create the posts object that contains post and tag information
eleventyConfig.addCollection(\"posts\", function (collectionAPI) {
let POSTS = collectionAPI.getFilteredByGlob(\"./src/posts/*.md\");
let collection = {};
collection.all = POSTS;
collection.tags = getTagList(POSTS);
collection.tag = createCollectionsByTag(POSTS);
return collection;
});
\nWith nested collections:
\ncollections.posts.all
Returns list of all postscollections.posts.tags
Returns list of all tagscollections.posts.tag["tag-name"]
Returns list of all posts that match tag-nameRegardless of the bugs, it’s an interesting idea, and it's really cool to see how flexible the collection system can get if you push it.
\n", "date_published": "2022-03-06T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2022/falls/", "url": "https://mattmcadams.com/sketchbook/2022/falls/", "title": "Falls", "content_html": "\n", "date_published": "2022-02-07T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2021/making-leather-discbound-cover/", "url": "https://mattmcadams.com/posts/2021/making-leather-discbound-cover/", "title": "Making a leather discbound notebook cover", "content_html": "I want to start with a disclaimer that I’m not a leather worker, so I can’t guarantee that this is all technically correct or good advice, but my custom discbound notebook has been a project of mine for a few years. I’ve tried all kinds of covers, both homemade and store-purchased, but none of them ever felt special or personal. Making a leather cover had been in the back of my mind for quite a while, but I never dived into actually making one because it was daunting and expensive.
\nWhile I can’t say it wasn’t expensive, I would say it was worth it to me. If not for the final product, then for the learning experience, and the pride of making something tangible myself. It took three attempts and many weeks of trial and error, tests, and practice, but I finally got something I’m happy with.
\nWhile I’m going to focus on my final design and how I made it, I’m also going to point out where I went wrong, and share some other tips I learned during the process. Let’s get started.
\nThis will be a leather notebook cover for an A5 discbound notebook with half-inch discs. It will wrap completely around the notebook, instead of being two separate covers punched to use the rings. We’ll attach it to the notebook with two sleeve pockets for the notebook’s plastic cover to fit into. We’ll also have an elastic pen loop attachment and an elastic band to keep the book closed.
\nIf you have a different notebook size, you’ll need to alter the template and measurements to fit your notebook.
\nThe first step is designing and making a template. The best way I know of to do this is to open Illustrator and use the shape tool to make the design. Here are some things to keep in mind when making the design:
\nMy notebook uses A5 paper and half inch discs, and the final dimensions of the design ended up at 14.5 x 9 inches.
\nI also added marks for holes that the discs would slot into and two slots for an elastic band to go through. On another artboard, I made a template for the inside panels (not shown in screenshot).
\nYou can download my template if you’re making the same size notebook as I am.
\n\nPrint these, cut them out using an X-Acto knife, and staple around the edges where the stitching will be. Then fit it on the notebook to make sure the measurements are good (a bit larger than they need to be). Then print out a new copy on cardstock. Make adjustments as needed and make another mock until you get a template that fits. It’s much cheaper to waste cardstock than it is to waste leather.
\nThe plastic cover attached to the notebook will need to be trimmed down, or a new one might even need to be made, depending on your design. I ballpark the size to be the same height as the paper and leave the width a bit longer. You’ll have to trim it again as the last step to make sure it all fits.
\nThis is one of the most nerve wracking parts - actually cutting the leather. A wrong cut can mean wasted material, which can become costly. This is where the cork backed ruler comes in handy, since it doesn’t slip as easily.
\nFirst, we’re going to cut out our template. Leave the inside holes and slots; we’ll cut those out later. Next, prepare your chipboard. If you are like me and had a large sketchbook laying around, cut the back cover off and into a manageable size. Use double sided tape to affix the INSIDE template panels to the chipboard, then line up the ruler with the edge of the template and cut out the chipboard panels.
\n\n\nIt will take a few cuts with an X-Acto knife on every edge, just keep going over it until you get a nice, clean cut. Don’t rush. Replace the X-Acto blade often, maybe after every panel. Chipboard will dull the blade quite quickly.
\nDull blades are much more dangerous than sharp ones. They make it more difficult to cut through the material, and create less clean cuts. I should also mention that you should always be mindful of the direction you are cutting and the position of your fingers. Safety glasses are also probably a good idea. (Thanks for coming to my TED talk on safety.)
\n
At this point, you should have the following:
\nNext, we’ll do the same thing for the leather. Use double sided tape to affix the cardstock templates to the leather. Pay attention to what the leather looks like, as you may want to avoid creases or blemishes.
\n\n\nPlace your templates close together and in a way that minimizes the amount of material waste. Keep all of your leather scraps to do practices and tests with.
\n
Place the ruler along the edge of the template, and cut along the edge to make the leather cover and inside panels. Don’t cut out the slots or holes in the outside cover just yet, we’ll cover that next.
\nDo, however, cut out the space for the pen loop. Notice we didn’t do this for the other layers, only the inside leather panel gets this cut taken out. This cut helps the pen loop lay flush with the inside of the notebook.
\nBefore you remove the cardstock template from the back cover, it’s time to punch the slots for the discs to go through.
\n\nYou can buy a slot punch, but I wasn’t able to get one the width or length I needed. Instead, we’re going to cheat just a little. First, place the cover leather side up on the scrap wood. Then, punch a hole on either end of the slot using the punch and a mallet. You’ll notice I left circles on the template as a guide. Next, take your ruler and cut from the outside edge of one circle to the other, more or less following the lines on the template.
\nHere’s a video of this technique if you need help visualizing it.
\nTo be clear, you should be cutting through both the cardstock template and the leather cover underneath it. The double sided tape will make sure everything lines up.
\nWith all the slots punched for the discs to fit into, you can cut out the small holes for the elastic band. I’ve just free-handed this section since they’re so small.
\nThis is where I like to put everything together to make sure the measurements are right. There’s not a lot you can do if they’re wrong, other than not waste time building the rest of it.
\nOnce the edges are lined up, you’ll notice that they probably don’t match up perfectly. That’s ok, as long as they’re close. This is why we made the templates a little larger than they needed to be. Use clamps to secure the edges. You should now have a decent idea of how the cover will feel when it’s done.
\nThe next major part is putting the inside panels together. First, use the leather cement to glue the layers together. We’re not doing the outside cover yet because it would get in the way.
\nThe type of glue I’m using says to apply the glue to both sides, and to let it dry before pressing them together, so make sure you read the instructions for your specific brand of glue. This is very important.
\n\n\nThe type of glue I used was much like rubber cement which means that I was able to remove the excess glue from any spills or accidents with a rubber cement eraser - just remember to be careful and gentle with the leather, as it can easily get damaged if you’re too rough or rub the eraser too fast.
\n
When all layers have been glued together, cut some strips of chipboard, or use some folded cardstock or something to go between the clamp and the leather. This will protect the leather from the clamp by distributing pressure. Last, clamp the layers together and let it dry overnight.
\nThe next day, unclamp the inside panels. Identify the inside edge of the panels and use a ruler and the X-Acto knife to cut off a sliver of the edge. This will make sure all layers are flush and aligned perfectly.
\n\nUse a ruler and the back side of the X-Acto knife to make a small score along the inside edge of the panels. The score should be about a quarter inch in.
\nNow place the panel on the scrap wood and use the cross stitch punches and rubber mallet to punch holes for the stitches.
\nThe plastic backing is to help give the chipboard strength during this step, as the process of punching the holes causes some damage to the chipboard. This punching causes its fibers to separate or tear apart from one another. I’m unsure how big of a problem this is long term, but so far, it seems to hold up alright with the added plastic backing.
\n\nNext, thread your needles and cross stitch the inside seam. This was extremely difficult and frustrating for me, so be patient. Explaining how to cross stitch is probably outside the scope of this tutorial (I gotta draw the line somewhere) so here’s the saddle stitch video I used to learn how.
\nAfter the stitching is done, measure a length of elastic that will securely hold your favorite pen. This takes some trial and error, but by using a pen as a reference point for size, and a clamp to hold the elastic in place, you should be able to get a length that works for you.
\nGlue the edges together flat, then glue the elastic band to the outside edge of the inside flap where we made the cutout earlier.
\n\nWith the inside panels completely glued together and the inside seam stitched, we can move on to attaching the inside panels to the outside cover.
\nGlue the outside edges of the inside panels to the outside edges of the back cover. Clamp these layers together and let dry overnight. The next morning, cut off a sliver of the outside edges to make them flush.
\nNext, you can round the corners if you want. Find something with a rounded corner to use as a reference, and cut around the edge with the X-Acto knife. Again, this will take some time due to the thickness of the layers.
\nScore a guide for the stitching holes, then punch the holes just as we did before. Stitch the edges to complete the look.
\n\nFit the cover to the notebook again to ensure the stitches are good. You might need to trim down the plastic covers on the notebook again to make sure they slide in the pockets.
\nMeasure a length of elastic that will fit around the notebook and guide it through the holes we made in the outside cover. Glue the ends together to create the elastic band.
\n\nFit the cover back onto the notebook and enjoy!
\nIf I ever decide to try this again or need to make another one for whatever reason, I’ll probably try going for thicker leather instead of adding a chipboard layer for structure. I’m also interested in the idea of adding pockets to the inside of the front cover. For now though, I’m content with the work I’ve done.
\n", "date_published": "2021-08-20T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2021/website-redesign-2021/", "url": "https://mattmcadams.com/posts/2021/website-redesign-2021/", "title": "Website Redesign 2021", "content_html": "My website has always been a testing ground of new ideas, a place for me to experiment and learn new things, so it’s only natural I do a major redesign every other year or so. However this design marks a shift in how I talk about myself and my work.
\nMy previous website design, and all of those that came before it, had a more specific goal. I needed to market myself and put my best foot forward to get a job out of college. It was about showing fancy features and clever solutions and elegant design in the style I like to do for a living. It was about simplifying who I am and what I do into something easily understood and marketable.
\nI’ve recently realized that my personal site doesn’t need a lot of bells and whistles. I’ve also never been good at (or enjoyed) talking about what I do or trying to market that. My hope is that if I’m ever being considered for a new job, they will hire me based on who I am, what I have to say, and the work I’ve done and continue to do.
\nIn light of this shift, I’ve made the decision to reevaluate the design and layout of my website. Looking at a utilitarian, almost brutalist aesthetic, back to the roots of what a website is at its core before designers like myself transform it into something else...
\nI want this to be the last redesign for a long time. Something low maintenance I don’t have to worry about. There’s plenty of work for me at my full time job and plenty more ideas for personal projects.
\nThis shift in intention will help me funnel those design ideas and inspiration into more interesting work including design for good projects that could have a larger impact than my little personal website.
\nOther than the design itself, this edition of my website is also built with new technology (yay!), using 11ty and nunjucks instead of Gridsome and Vue. I really liked Gridsome but wanted something more bare bones. Plus 11ty is blazing fast.
\nThe new colors have higher contrast for accessibility, the site has more robust tagging and url structure, RSS feeds, and there are plenty of opportunities to add things like site search and archives (if I can ever get into a rhythm of writing) down the line.
\nThe last major change for the website is that I’m going closed source. It was a hard decision for me to make because I always took pride in having that out there, but I think it’s time to reign it in. I’ll post tutorials about anything interesting I do for the site so maybe people can learn along with me. If you’re interested in the design of the site itself, it’s largely based on my custom CSS boilerplate.
\nLet me know what you think about the design and the direction I'm going. Its a bold step for me and my humble site, but I think it will free up some space to write, learn, experiment, and build new things.
\nThanks for reading my little rant!
\n", "date_published": "2021-07-05T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2021/custom-notebook/", "url": "https://mattmcadams.com/posts/2021/custom-notebook/", "title": "Custom Discbound Notebook", "content_html": "I love stationary. I always have. In college, when I had to get a job and all I could find was retail, I was genuinely happy to work in the print department of Office Max. I was able to talk about different paper types, finishes, and features with clients and (much to their exhaustion) co-workers. I might go as far as to say that stationary is one of the things that got me into graphic design.
\nToday though, I want to share one of my personal labors of love, a project I’ve worked on for a few years, and one that continues to evolve - my notebook.
\n\nOh and one last thing, I’m not affiliated with or sponsored by anyone. Everything I talk about or link to here, I do because I think they’re genuinely good products.
\nDisc binding is a relatively new and less recognized method of binding pages together. It takes everything that is good about binders, being able to rearrange pages, and combines it with the smaller form factor of a spiral bound notebook.
\n\nDisc bound notebooks and note taking systems have gathered a cult following, attributed in part to the utility of such systems, but also to the level of customization and personalization each notebook can have. Ultimately, everything is truly modular.
\nChoosing the right notebook size is a task in itself. I considered letter size, so that I could add things like hand outs and printed documents, but it always felt too cumbersome for me personally. I’m on the go a lot, and wanted something smaller. I also considered TUL’s junior size, but felt that the pages were too tall. I even tried designing my own paper size. This worked out well enough for a year or two but I eventually decided to move to a more standard paper size. I finally landed on A5. This size is the perfect balance of width and height, and is small enough to bring with me everywhere.
\nPaper is beautiful and versatile. All you need to do to see a variety of paper is take a stroll through the sketchbook isle of any art store. Some paper is art in its own right. One of my professors was so adamant about his paper that he made his own from scratch.
\nI too became fairly particular about what I write on. I wanted paper that feels luxurious, is good to draw on, is thick enough to hold up to highlighters, but thin enough to get a lot of sheets in a thin notebook. Ultimately, I decided on the 60lb sketch paper from Strathmore. I had been using their sketchbooks for years through art school and it became something I enjoied to write and draw on.
\nI also love dot grid paper, and like disc bound systems, my interest is based in the versatility it has to offer. The dots on dot grid paper give enough reference to keep lines of text straight, like lined paper, but they also provide enough reference to make more technical sketches and designs, like graph paper.
\nNow that I’ve decided on paper size and paper type, it comes the time to fill my notebook with paper. Spoiler alert, I’m going to cut, print, and finish my own filler paper:
\nIts a lot of work for some paper, and it requires some extra equipment, but I find the activity relaxing and rewarding. For anyone that wants to follow in my footsteps, I use a basic Xacto guillotine cutter to cut the pages down to size. To round the corners, I use this corner cutter - by far one of my best purchases in art school. And for the hole punch, I use the Staples brand Arc hole punch. I like this punch because it has a setting specifically for A5 paper and it can punch though several pages with ease.
\n\nAn extra little note about my dot grid paper design. I initially just did a very standard dot grid design, but later had trouble locating the exact middle of the page. I then updated the paper design to darken the middle 4 dots, and the middle 2 dots on all 4 sides. This greatly helps with placing things in the page. I’ve uploaded the printable dot grid paper to Google Drive if you want to use it.
\nCovers are one of the most difficult things to figure out. Apparently, A5 is not a popular paper size for disc bound systems, and you’ll be hard pressed to find a cover that was designed for this size in mind. Ultimate dream goals is to get one of these beautiful notebooks from William Hannah. For now though that’s too expensive for me, so I’ve found some alternatives.
\nIn the beginning of this journey, I made my own covers. I liked these a lot, but they didn’t have the level of polish I wanted. (I’m not a master book binder or leather worker and have limited equipment, go figure) The front cover was a softish cover I had taken from another sketchbook. I cut it down to size and used e6000 glue to affix it to a poly cover I had also cut down to size. The back cover was also affixed to a poly cover, but it was made from a rotary cutting mat. It was extremely difficult to work with, as you can imagine. Altogether though, it was brilliant. I could use the back of my notebook to cut things on with an Xacto knife, which is pretty cool.
\nLater on, I decided I wanted the cover to be leather. I’ve looked at so many leather notebook cases I can’t begin to list them. I thought my best bet would be to get a case meant to wrap around any notebook. But I was always worried that my notebook wouldn’t fit because of the extra bulk the discs add. Last month on a whim, I decided to take my notebook in to Office Depot just see if the junior size covers were big enough for the A5 paper, and to my surprise they were!
\n\nI might get a new cover one day, but I’m finally content with this one from TUL.
\nNext up are the discs. Short and sweet, I wanted the smallest size (0.75in) and I wanted them to be metal. Unfortunately, very very few brands offer this combo, and when you do find them they are expensive. I really like the dark silver ones from TUL, but the smallest they come in is 1in. I’m currently using some from Levenger, but there are a few other options.
\nEven after all that, I felt it needed a little something extra. I like the aesthetics of the moleskine notebooks with the elastic strap, and decided to make one for my notebook too. I went to the local art supply store and bought a length of 0.5in black woven elastic. Wrapping it around the front cover, I cut it to length and clued the ends with my trusty e6000. Leave this overnight to bond, and when you stretch it around the whole notebook it should have the desired tightness. I also had a metal bookmark / paper clip thing that I thought made a nice decoration.
\nDespite all the work and consideration thats gone into it, it’s not done. I don’t know that it will ever be finished. Just like this website, it’s a place of experimentation and self expression. I want to make my own leather cover one day, or find one that fits my style even better. I also still love the idea of having covers made out of rotary mats haha.
\nIf you’re deep into stationary and / or liked this content as a break from my usual tech / design thoughts let me know! I’d love to see your ideas and setups.
\n", "date_published": "2021-03-12T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2021/smart-selectors/", "url": "https://mattmcadams.com/posts/2021/smart-selectors/", "title": "Writing Smarter CSS", "content_html": "CSS authoring has become my favorite part of being a designer and developer but like anyone who has built more than three websites, I got tried of starting fresh every time. Frameworks, boilerplates, and other systems became part of my toolkit but I hated using the metric ton of extra CSS that I never used and didn't write and overriding a lot of styles because they were too opinionated.
\nI decided to make my own boilerplate built on modern authoring techniques and design systems. In this article I'm going to talk about a few tricks I used and why this set of base styles works so well.
\nOne challenge that is easy to overcome but difficult to do well is controlling the visibility of elements. Sure, we can always reach for display: none;
but let's look at these two first and I'll explain why they're better.
When you need to hide something from users but it needs to be available for screen readers using display: none;
is fairly unreliable. Screen readers have gotten pretty smart and sometimes ignore elements that don't display. This feature will render the element invisible, and the technique is pretty common, but pay close attention to the :not
selectors.
Let's consider a "skip to content" button. We want this to be hidden but available for assistive technology. You HAVE to display this content on focus for it to be WCAG compliant. This selector will cover that use case.
\n.visually-hidden:not(:focus):not(:active) {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
\nFun fact: the default HTML hidden
attribute has an incredibly low specificity. This means that you can show a hidden element with CSS. To fix this, I always include the following CSS to my project:
[hidden] {
display: none !important;
}
\nIt's probably best practice to use the hidden
attribute truly hide content instead of having a .hidden
CSS class. This will help you do that without any unexpected problems.
How many times have you gone to do some sort of layout using the correct html elements and had to override a bunch of default styles? Think about a traditional horizontal navigation bar. These are typically built with an unordered list and formatted to look like a nav bar. Take a look at the following HTML:
\n<nav>
<ul class=\"nav-bar\">
<li><a href=\"/home\">Home</a></li>
<li><a href=\"/services\">Services</a></li>
<li><a href=\"/about\">About</a></li>
</ul>
</nav>
\nThe first thing you'll need to do is work on removing all the <ul>
and <li>
styles before you start any layout work. We can actually skip this step with some smart CSS in our base styles. Let's look at an example:
ul:not([class]) { list-style-type: disc; }
\nThe CSS now knows that you're going to be altering the style of the list so it goes ahead and removes those basic styles for you. We can leverage this trick for links too.
\nOne really nice thing I see from time to time are links that feature an icon to indicate that the link will open in a new tab or that the link will go to a PDF. We can implement this with some clever CSS.
\na:not([class])[target=\"_blank\"]::after,
a:not([class])[data-link-type=\"external\"] {
content: ' \\e91f';
font-size: 90%;
}
\nThis CSS will apply to any link that doesn't have a class and opens in a new tab. The reason I wanted to restrict this to links without classes is because there may be a time where I need a link to be styled like a button and the button doesn't need the icon.
\na:not([class])[href$='.pdf']::after,
a:not([class])[data-link-type='document']::after {
content: '\\e91d';
font-size: 90%;
padding-left: 0.1em;
}
\nThis CSS uses the same principal but adds a PDF icon to the end of the link. We can achieve this with [href$='.pdf']
this looks for a link who's href
ends with ".pdf".
\n\nIt's important to note that this exact implementation was designed with an icon font in mind. You can get around this by changing the
\ncontent
property to whatever suits your need.
Another cool trick we can do with CSS is help enforce accessible markup. For example, we can require certain aria labels to be present. Let's look at a common "alert" component.
\n<div role=\"alert\">Test Alert</div>
\nIt is more semantically correct to use role="alert"
than a generic class="alert"
because it gives more information to assistive technologies. We can enforce this markup by authoring our CSS with that in mind:
div[role='alert'] {
color: white;
background-color: tomato;
padding: 1em;
}
\nI firmly believe that EVERY project can benefit from having a design system. I don't mean that you need a huge and involved framework or documentation. I'm talking about a handful of standard colors and numbers to act as design tokens. Let's look at a super simple design toolbox.
\n:root {
--font-body: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-head: var(--font-body);
--font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
--color-gray-100: #F3F4F6;
--color-gray-500: #6B7280;
--color-gray-900: #111827;
--color-red-100: #FEE2E2;
--color-red-500: #EF4444;
--color-red-900: #7F1D1D;
--color-yellow-100: #FEF3C7;
--color-yellow-500: #F59E0B;
--color-yellow-900: #78350F;
--color-green-100: #D1FAE5;
--color-green-500: #10B981;
--color-green-900: #064E3B;
--color-primary-100: #E0F2FE;
--color-primary-500: #0EA5E9;
--color-primary-900: #0C4A6E;
--page-bg-color: white;
--element-bg-color: var(--color-gray-100);
--text-color: var(--color-gray-900);
--light-text-color: var(--color-gray-500);
--heading-color: var(--text-color);
--link-color: var(--color-primary-500);
--space-unit: 1.5rem; /* Should equal base line-height */
--content-width: 900px;
--sidebar-width: 300px;
--side-pad: var(--space-unit);
}
\nThis small section of design tokens can evolve to create a ton of visually robust UIs. Another huge part of having a great design system is great typography. I've talked a lot about typography before, so I won't get on that soap box here, but do check out those articles.
\nWith today's wide variety of screen sizes and resolutions its more and more difficult to keep up using traditional media queries. The solution? Consider testing for screen orientation instead of screen width. Let's look at a simple grid implementation:
\n.grid {
--grid-gap: var(--space-unit);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-gap: var(--grid-gap);
}
@media ( orientation: portrait ) {
.grid-portrait {
--grid-gap: var(--space-unit);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-gap: var(--grid-gap);
}
}
@media ( orientation: landscape ) {
.grid-landscape {
--grid-gap: var(--space-unit);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-gap: var(--grid-gap);
}
}
\nWith this system, you leverage responsive-by-default grid layouts and can constrict grid functionality to landscape only, portrait only, or both. You can expand this principal to work with sidebars or other content layouts.
\nThese are just a few new techniques I've implemented in several of my personal projects and I hope that you found something here that was fresh and insightful. If you liked this article and these ideas, please check out my CSS boilerplate on GitHub.
\n", "date_published": "2021-01-14T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2020/ruins/", "url": "https://mattmcadams.com/sketchbook/2020/ruins/", "title": "Ruins", "content_html": "\n", "date_published": "2020-12-14T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2020/get-started-with-plausible/", "url": "https://mattmcadams.com/posts/2020/get-started-with-plausible/", "title": "Getting Started with Plausible", "content_html": "Website analytics are great tools to monitor your website's traffic, see what people are interested in on your site, and inform how you develop your site going forward. They can help you understand more about your audience.
\nUnfortunately, analytics have a bad rep, and I pin most of this on Google. Being the largest and defacto standard analytics platform, Google makes sure to collect as much information about users as is technologically possible. This often means compromising your user's privacy and taking a little hit to website performance.
\nThis is where Plausible comes in. It takes a more modern approach to web analytics. It's beautiful, lightning fast, super affordable, and it's built around privacy. This is increasingly important as new internet privacy laws are passed (think GDPR). With Plausible, you don't have to worry about a privacy statement or cookie banners or any of that garbage. Your users can focus on your content, and you can still get valuable stats.
\n\nNow's a good time to qualify "super affordable". They charge $6/month or $4/month billed annually at the time of writing. Compare this to Netlify's $9/month server side analytics. Personally, I think it's well worth about 50 bucks a year for a great product like this.
\n\n\nI should also note that I'm not sponsored by anyone. I just think it's a great service, and to be honest, this is going to be more of a checklist than a how to guide. Sorry!
\nThe Plausible docs are really good and there's no reason to repeat all of it here. Instead, I'll highlight some cool features they don't tell you about.
\n
Head on over to plausible.io and sign up for their 30 day free trial (no credit card required). They'll give you a script to put in your website's <head>
and thats pretty much all there is to it!
Analytics will start rolling in as soon as you get 1 page view. You can test it out by visiting your site yourself. (We'll omit ourselves from the analytics later).
\nPlausible also lets you track which URLs are going to 404 pages. This is a little more involved, but still extremely straight forward.
\nIf you're using one of the cool new frameworks like Vue (Gridsome in my case), you may have to put the 404 page script in a module export or something. Here's what that looks like:
\n<script>
export default {
mounted() {
plausible(\"404\",{ props: { path: document.location.pathname } })
}
}
</script>
\nYou can also track what outbound links are being clicked on and what pages those links were found on. To do this, add a new "custom event" goal, like we did for 404 page tracking and use the exact goal name Outbound Link: Click
.
That's pretty much all there is to it. You can read more on Plausible's blog where they talk about this feature and how it can be helpful.
\nI really really like this feature. Not because it's overly useful or necessary, but because it helps me keep things organized. The only true benefit I can think of for doing this is to ensure the analytics script doesn't accidentally get blocked by an ad blocker. You can skip this step if you want, otherwise head over to Plausible Custom Domains to follow along.
\nYou do unfortunately have to be patient for this part since it can take a while for the DNS records to update, though mine only took a minute or two.
\nAs developers, content creators, editors, we spend a fair amount of time looking at our own sites. This can be a little problematic when trying to figure out analytics, so we're going to "opt out".
\nThankfully, they have an article on how to exclude yourself too. Again, this is pretty straight forward and shouldn't take long to have up and running.
\nAfter you've excluded yourself from your own analytics, you may find that you need to test something else out. Maybe you need to create another goal of some kind, or want to double check to make sure something is working. The best way to do this is to temporarily disable the ad block extension and enable it again when you're done testing.
\nIt's also worth noting that you don't have to make any changes to those instructions if you chose to use a custom domain. Blocking plausible.io will still work.
\nAfter you finish playing around with everything you may want to go ahead and reset your analytics so far to remove your own activity.
\n\n\nWARNING: This will reset ALL collected data - not just your own, so I only recommend using this when you first finish setting things up.
\n
Follow this doc to reset your site's data to date.
\nCongrats, you're all set up with a pretty powerful little analytics tool and you don't have to worry about it impacting performance or causing privacy headaches.
\n", "date_published": "2020-12-02T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2020/approaches-to-typography/", "url": "https://mattmcadams.com/posts/2020/approaches-to-typography/", "title": "Approaches to Web Typography", "content_html": "There are a handful of ways we can use what we learned about modular scales when creating the typographical system of our website, from simple single scale systems to fully fluid typography. This article will take a look at each style and implementation, but all of these systems aim to address the question of how to handle text on both small and large screens.
\nThe simplest system here is what I call a "Single Scale System". These systems rely on a single typographical scale and use that scale on all screen types.
\n\nThis is the most common style of web typography, and could be setup so that there is absolutely no difference between the text on a phone or a desktop or it could be set up so that the scale is the same but the website text is overall larger or smaller on a specific screen size.
\n/* Major Third (1.25) */
:root { font-size: 16px; }
#h1 { font-size: 1.953rem; }
#h2 { font-size: 1.563rem; }
#h3 { font-size: 1.25rem; }
p { font-size: 1rem; }
small { font-size: 0.8rem; }
@media only screen and (min-width: 600px) {
:root { font-size: 18px; }
}
\nThe dual scale uses two (or more) typographical scales, one for each range of screen sizes. These can be useful if you want massive headdings on desktop but need them to be smaller on mobile without changing the base font size.
\n\nA dual scale system can be more difficult in that each scale creates a certain mood or feeling. It can be hard to ballance those in a single brand. This style is however extremely effective and relatively easy to setup.
\n/* Major Third (1.25) */
#h1 { font-size: 1.953rem; }
#h2 { font-size: 1.563rem; }
#h3 { font-size: 1.25rem; }
p { font-size: 1rem; }
small { font-size: 0.8rem; }
/* Perfect Fifth (1.5) */
@media only screen and (min-width: 600px) {
#h1 { font-size: 3.375rem; }
#h2 { font-size: 2.25rem; }
#h3 { font-size: 1.5rem; }
p { font-size: 1rem; }
small { font-size: 0.667rem; }
}
\nThis is a clever technique I learned from Medium. In this system, you would use a single scale but use only odd steps for mobile and even steps for desktop.
\n\nThis style of typography is great for creating a consistant mood across all platforms.
\n/* Major Third (1.25) odd steps */
#h1 { font-size: 3.052rem; }
#h2 { font-size: 1.953rem; }
#h3 { font-size: 1.25rem; }
p { font-size: 1rem; }
small { font-size: 0.8rem; }
/* Major Third (1.25) even steps */
@media only screen and (min-width: 600px) {
#h1 { font-size: 3.815rem; }
#h2 { font-size: 2.441rem; }
#h3 { font-size: 1.563rem; }
p { font-size: 1rem; }
small { font-size: 0.64rem; }
}
\nThe downside to all of the above solutions is that they rely on a set breakpoint. Fluid typography changes all of that and presents typography relative to the size of the screen between a maximum and minimum threshold. This is where things get tricky - a fully fluid typographical system involves a lot of math.
\n\nIn a fluid scale, I highly recommend using a preprocessor such as Sass (SCSS) to help put names to the numbers. These systems are highly customizable and can augment any of the methods above. For this demo, I'll show the typography I use for my own website, based on the dual scale system.
\nFirst, I'm going to create a mixin for the bare essentials. This mixin sets the max and min screen sizes. The system will reach it's minimum font size at $fluid-min-width
and the maximum font size at $fluid-max-width
. This code also assumes you're working with html at 100% so that 1rem = 16px.
@mixin fluid-init($fluid-max-width: $config-fluid-max-width, $fluid-min-width: $config-fluid-min-width) {
:root {
--fluid-screen: 100vw;
--fluid-bp: calc((var(--fluid-screen) - #{($fluid-min-width / 16) * 1rem}) / #{($fluid-max-width / 16) - ($fluid-min-width / 16)});
}
@media screen and (min-width: #{$fluid-max-width * 1px}) {
:root { --fluid-screen: #{$fluid-max-width * 1px}; }
}
}
\nNext we'll work on the meat of the system, a fluid mixin. This mixin will be used to calculate the value of eeach step of the scale.
\n/* Define our mixin with variables for min size, max size, min ratio, and max ratio */
@mixin fluid(
$step: 0,
$min-size: $config-min-size,
$max-size: $config-max-size,
$min-ratio: $config-min-ratio,
$max-ratio: $config-max-ratio)
{
/* Define internal values */
$_min-size: null;
$_max-size: null;
/* Start logic */
@if ($step < 0) {
/* Remove the negative from negative steps */
$_step: $step * -1;
/* Devide for negative steps, a min and max size */
$_min-size: ($min-size / pow($min-ratio, $_step)) / 16;
$_max-size: ($max-size / pow($max-ratio, $_step)) / 16;
} @else if ($step > 0) {
/* Multiply for positive steps */
$_min-size: ($min-size * pow($min-ratio, $step)) / 16;
$_max-size: ($max-size * pow($max-ratio, $step)) / 16;
} @else {
/* Normal size for step 0 */
$_min-size: ($min-size) / 16;
$_max-size: ($max-size) / 16;
}
/* Find the difference of the two sizes */
$_difference: $_max-size - $_min-size;
/* Magic variable that calculates based on screen size */
--fluid-#{$step}: calc(#{$_min-size * 1rem} + (#{$_difference} * var(--fluid-bp)));
}
\nNow all thats left is to include the mixins to output the actual CSS and use the css variables to style the text.
\n@include fluid-init;
:root {
@include fluid(-1); /* --fluid--1 */
@include fluid(0); /* --fluid-0 */
@include fluid(1); /* --fluid-1 */
@include fluid(2); /* --fluid-2 */
@include fluid(3); /* --fluid-3 */
}
#h1 { font-size: var(--fluid-3) }
#h2 { font-size: var(--fluid-2) }
#h3 { font-size: var(--fluid-1) }
p { font-size: var(--fluid-0) }
small { font-size: var(--fluid--1) }
\n",
"date_published": "2020-08-20T00:00:00Z"
},{
"id": "https://mattmcadams.com/sketchbook/2020/kalimba-map/",
"url": "https://mattmcadams.com/sketchbook/2020/kalimba-map/",
"title": "Kalimba key map",
"content_html": "\n",
"date_published": "2020-08-01T00:00:00Z"
},{
"id": "https://mattmcadams.com/sketchbook/2020/doodles/",
"url": "https://mattmcadams.com/sketchbook/2020/doodles/",
"title": "Doodles",
"content_html": "\n",
"date_published": "2020-07-18T00:00:00Z"
},{
"id": "https://mattmcadams.com/posts/2020/vertical-rhythm/",
"url": "https://mattmcadams.com/posts/2020/vertical-rhythm/",
"title": "Vertical Rhythm",
"content_html": "\nVertical rhythm is a concept in typography that aims to keep vertical spaces between elements consistent with each other. This creates repeatable patterns that readers subconsciously understand and use to read faster and more accurately.
\nAs a disclaimer, this is going to be an opinionated and complicated article.
\nThe baseline grid is perhaps the pinnacle of vertical rhythm and many have tried to use it on the web. This is an admirable goal, but one that will lead to many headaches.
\nWhat is a baseline grid? The baseline is the invisible line that all typographical characters sit on. A baseline grid is a system that use's the leading of a page's body copy to align all elements to the baselines of the text. This ensures that in two columns, the lines of text look like they sit on the same invisible line.
\nSo what's the problem? The web doesn't care about baselines. It takes a much looser approach to typography, using line-height
an invisible bounding box the text sits in.
This article is not going to go into replicating the baseline grid. If you're really interested in this approach, I'd recommend taking a look at the 8pt grid system. It's probably the closest thing to the baseline grid on the web.
\nThe only thing about the 8pt grid system and other systems like it, is that they require you to work from the outside in. I firmly believe the base of any design should be it's text. Determining your unit of measurement first requires you to select font sizes that work with that measurement.
\nWhen you think about it, what is the smallest unit of vertical measurement we already have established by default? For the designers who created the idea of a baseline grid, it was the leading from one line of body copy to the next.
\nLeading doesn't exist in web typography - there is no space between lines of text, so what do we use? The line height itself.
\nNow, to create perfect vertical rhythm, like would exist if using a baseline grid, every element's height and margin should equal a multiple of the base line height.
\nExcept, I'm going to bend the rules a little. You may have already realized this but the average line height is just too large for practical use. My suggestion is to divide it in half and use that number as your base unit.
\nThis number needs to become your new best friend. Where I currently work, this value is 0.8rem (our base line height is 1.6)
\nIn the previous article, we looked at modular scales, so let's calculate the line height for each step of a scale. This would use a formula like this:
\nX = Font Size
Y = Base Line Height
Z = Line Height
A * Y = Z
Where A is an increment of 0.5 and is as high as possible so that Z >= X
\nFor example, let's say our base font size is 10px, we have a 1.5 ratio, and the base line height is 1.6 (16px). The next step in the scale would be 15px and its line height would be 16px since 16 is greater than or equal to 15.
\nAt this point, the designer might say "well that's just too tight and it needs to be wider" and they could increase the line height up to 24px since 16 * 1.5 = 24
\nKeep in mind that the total height of any element including margins, borders, paddings, and content should be a multiple of the base line-height / 2. I'll refer to this as the "space unit" from here on.
\nObviously, this is highly nit picky and would be next to impractical to use in production. This is simply the goal of a perfect vertical rhythm.
\nAs proof that an absolutely perfect vertical rhythm is likely impractical in production, consider responsive images where the height changes fluidly as the window is scaled to maintain the image’s aspect ratio.
\nThis could be achieved with JavaScript by cropping the image to a height multiple of the line height but less than the auto height of the image, but I’m not sure this level of perfection is necessary or desirable. In the future, advancements in CSS math may provide an answer to this.
\nMaintaining vertical rhythm can be done quite easily as long as we understand where we can break the rules. In my experience, I feel that we can allow images, borders, and small paddings to fudge the rules.
\nThe most important take away is that we have a consistent margin that is preferably based on the line height of our body copy. Large paddings like those on cards could also be considered when using this system of spacial measurement, as well as the height of arbitrary containers or content where it is convenient to do so.
\n", "date_published": "2020-07-13T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2020/typographical-scale/", "url": "https://mattmcadams.com/posts/2020/typographical-scale/", "title": "Typographical Scales", "content_html": "In any form of design, one uses a series of measures to plan and layout the product. Measurements create precision, consistency, and promote trustworthiness. In print, common measurements are inches, centimeters, points, and picas. In the world of digital and web design we use pixels and more relative measurements such as percentages.
\nI bring this up in a series about typography because typography and its measurements sit at the very core of any design - even if unintentionally. I love typography and like to think of it as the starting point of any project. Today we'll be talking about the relationships that relate most to typography, though we'll see how these measurements have much further reaching implications in a future article.
\nTypographic measuring systems began with the letterpress, which required all characters and spaces to sit tightly packed line after line on a physical press. What resulted from this was the classical type scale: 18pt, 24pt, 36pt, 48pt, 64pt, 72pt, and 96pt. These numbers were chosen because of their mathematical relationship. 18 = two lines of 9; 24 = two lines of 12; 48 = two lines of 24; 72 = two lines of 36 or six lines of 12.
\n\nThis typographical scale served for hundreds of years and is now considered the default in many word processing software. Now in the age of computer graphics and websites, we more often need to specify a relative size for each step in the scale to ensure the composition responds to responsive and reactive environments.
\nThe base unit of a typographical composition is the default size of the body. In most browsers, this default is 16px.
\nA trick I've seen many times to simplify typographical math on the web is to reset the base font size. Because typical browsers set the base font size at 16px, a web designer can reset that to 62.5% thus resetting the base font size to 10px. From here, the relative unit em
may be used to set responsive font sizes that easily equate to solid pixels. For example 1.8em would equal 18px.
The classical typographical scale uses multiples of 3 and a designer has selected a few values from that set to create a range of font sizes that work harmoniously together. Now that there are no physical constraints to selecting font sizes, what keeps designers from selecting completely arbitrary values? Well, sometimes they do, but creating a set scale is a more reliable way to create font sizes that share a rhythm.
\nOne way to create a typographical scale is to use a ratio. Sometimes called "modular scales" these scales can have an infinite number of steps and can react to changes in the base unit.
\n\nThe first step is to choose a ratio, for this example I'll be using 1.5 and a base unit of 10px. Multiply each step by the ratio to get the value of the next.
\nsmall { font-size: 6.667px; }. /* 10 / 1.5 */
body { font-size: 10px; } /* base */
h3 { font-size: 15px; } /* 10 * 1.5 */
h2 { font-size: 22.5px; } /* 15 * 1.5 */
h1 { font-size: 33.75px; } /* 22.5 * 1.5 */
\nA more concise way to write the formula would be x = y * z^x
where x is the step in the scale, y is the base unit, and z is the chosen ratio.
Instead of using absolute values, try using rem
units. These units in CSS use the document's base font size as its unit of measure. For example, if the document's default size is 16px, then 1rem = 16px.
The above scale can instead be written as x = x^y
where x is the step on the scale and y is the chosen ratio.
small { font-size: 0.667rem } /* 1 / 1.5 */
body { font-size: 1rem; } /* base */
h3 { font-size: 1.5rem; } /* 1 * 1.5 */
h2 { font-size: 2.25rem; } /* 1.5 * 1.5 */
h1 { font-size: 3.375px; } /* 2.25 * 1.5 */
\nYou can explore other scales and see the results live Type Scale, an awesome tool by Jeremy Church.
\nUsing a scale built on a ratio does have the limitation that each step usually yields a more complex number that is harder to remember. At work, we've used sticky notes to remember common measurements without having to look it up or redo the math.
\nIn the example below, I'll be using the preprocessing syntax SCSS, but this could also be achieved using CSS custom properties better known as CSS variables.
\n$ms_-1: 0.667rem;
$ms_0: 1rem;
$ms_1: 1.5rem;
$ms_2: 2.25rem;
$ms_3: 3.375rem;
}
small { $ms_-1; }
body { $ms_0; }
h3 { $ms_1; }
h2 { $ms_2; }
h1 { $ms_3; }
\n",
"date_published": "2020-06-30T00:00:00Z"
},{
"id": "https://mattmcadams.com/projects/attika/",
"url": "https://mattmcadams.com/projects/attika/",
"title": "Attika Variable",
"content_html": "Attika is an exploration of typographical design through technological advancements introduced in OpenType Variable Fonts. The typeface is a bold and versatile humanistic sans-serif typeface suitable for headlines and display text. It is roughly inspired by it’s namesake, a region of Greece home of Athens, a haven of philosophy and democracy in the ancient world.
\nAttika was part of the 2020 BFA Exhibition at the University of Alabama at Birmingham.
\nThe typeface includes uppercase and lowercase letters A-Z, punctuation, and special characters. Attika is available for free for personal and commercial use, and can be downloaded below.
\n\n", "date_published": "2020-04-06T00:00:00Z" },{ "id": "https://mattmcadams.com/projects/cahaba-river-print/", "url": "https://mattmcadams.com/projects/cahaba-river-print/", "title": "Cahaba River", "content_html": "This silkscreen print was designed for the Cahaba River Society as a fundraising opportunity. The Cahaba River is a large waterway running through the heart of Alabama and is home to a wide range of wildlife. It fosters one of the most diverse ecosystems in the state. The print is designed to be easily mass-produced using a single color design and draws inspiration from the ripples of the river itself.
\n\n\n", "date_published": "2020-03-23T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2020/attika/", "url": "https://mattmcadams.com/sketchbook/2020/attika/", "title": "Attika", "content_html": "\n", "date_published": "2020-03-20T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2020/life-study/", "url": "https://mattmcadams.com/sketchbook/2020/life-study/", "title": "Life Study", "content_html": "\n\n", "date_published": "2020-03-09T00:00:00Z" },{ "id": "https://mattmcadams.com/projects/uabit-website/", "url": "https://mattmcadams.com/projects/uabit-website/", "title": "UABIT Website", "content_html": "In my third year as UAB IT's web developer, I had the great opportunity to make sweeping changes to the design and structure of the IT website. As we began a deeper dive into the website's architecture than we ever had, we found years of technical debt, patchwork solutions, and poor organization.
\nThe primary problem with the website's user experience was the lack of navigation to guide the user around the website. However, the lack of a navigation system was only a symptom of the underlying chaos that was the page organization on the back end.
\nTo tackle this problem, I began with a site map. The site map is a great tool to show stakeholders the problem in a visual way, and show a marked improvement. After evaluating and taking inventory of each of the website's 120 pages, we began to reorganize and restructure content.
\n\nOne of the most difficult problems was the website's media folder. All images and documents were stored in the root media folder which totaled 2.32GB. Aside from disk space, this made a huge impact on the image picker in the WYSIWYG editor which would cause it to freeze for over a minute each time it was opened.
\nIn the end, we decided that it would be easier to rebuild the website from the ground up - following new standards for development - to ensure consistency in the future. After rebuilding the website, we had a clear folder structure, and had reduced the media disk usage down to 77.2MB - a huge win for performance.
\nAfter we solved the website's technical problems, we turned our attention to the aspect of design. After several months of UX research, I presented several new mockups and the beginning of a design system for the website going forward. The new design was focused on intentional use of white space, thoughtful use of color, and clear typographical hierarchy. The final designs and illustrations were a collaborative effort involving our project manager Jessika Reed, graphic designer MJ Moon, and myself.
\n\nTo ensure the maintainability of the structure and design of the website, my team and I created a comprehensive design system and documentation website that outlined core design philosophy, UI components, content strategy, organization, and naming conventions.
\n\nAfter a year of research, development, and countless revisions, the new website launched with overwhelmingly positive feedback. Our website audits are also seeing massive improvements to UX, accessibility, and performance. Due to the new design system and clearly structured backend, it has become much easier to design and launch a new webpage, cutting development time in half.
\n", "date_published": "2020-03-01T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2020/on-a-modern-grid/", "url": "https://mattmcadams.com/posts/2020/on-a-modern-grid/", "title": "On a Modern Grid", "content_html": "Back in October, Marvin Danig made a fantastic blog post on the state of responsive web design and the future of CSS frameworks. I wanted to continue some of his points and see how a responsive first grid system might look.
\nBefore we get into the deep end of building a grid, I wanted to review some of the systems we have today and the pain points associated with them.
\nThe range of device sizes are growing faster than we can keep up with them. This was originally addressed by adding a breakpoint for small screens... then a breakpoint for tablet sized screens... then a breakpoint for large screens as the web finds itself on more and more devices.
\nIt doesn't make sense to continually adjust the design to adapt to so many displays individually. Marvin outlines this problem in his article and his solution changed how I think about design for the web. His solution, in short, was to use screen orientation instead of size. Portrait or landscape.
\nAnother problem with existing grid systems is the reliance on wrapper elements and class names. This is a little more tricky to deal with, but we'll look at a few solutions below.
\nMarvin hinted at this being the answer to all our grid related problems and that it should replace flexbox and floats. There is some merit to this and CSS grid is extremely powerful, but having tried building a "responsive by default" and highly flexible grid have shown that it might not be the best tool for the job. CSS experts have also said that CSS grid was never intended to replace flexbox.
\nThat being said, CSS grid has a part to play in this system. First, let's look at some initial ideas.
\n.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-gap: 1rem;
}
\nThis little line packs a ton of punch. This allows us to arrange all children in the .grid
container as equally sized grid items, with as many columns as will fit while making sure no column is smaller than 250px.
This is extremely light weight and would work well for a gallery or similar layout of identical items. The only problem with this is that the minimum width of each column is static and must be set based on the developers knowledge of the content. What if you want a grid of blog posts and a grid of images. 250px might be fine for the images, but too small for the blog posts.
\nCSS grid works alright in situations like these, but it truly shines in bigger picture use cases, such as laying out the entire webpage, or the content in an individual component. It's fantastic for rearranging content too.
\nThe problem with CSS grid is that it's only half as powerful as its potential. Developers must know what content will be laid out beforehand, making it hard to use it as the basis of a framework grid system.
\nFlexbox has its own problems, mostly with its implementation in internet explorer. Besides that flexbox, no pun intended, is extremely flexible. It gives us the ability to automatically fit as many items on a single row as possible, or limit the max number of items per row. Flexbox grids also give us the freedom to make an item larger or smaller than the other items in its row without overflowing.
\nSounds like a dream, so what's the problem? Well, Flexbox isn't great at dealing with the gaps between items. grid-gap
doesn't exist for flexbox, so we have to rely on the margins of the flex items themselves. This gets extremely hairy when you get into it, involving lots of math (and css variables if you want different gap sizes).
Bulma offers my favorite implementation of the flexbox grid, but in my opinion, it's overly complicated for its best use case, and it relies on fixed media queries mentioned earlier. Essentially, the grid logic looks something like this:
\n.grid {
display: flex;
flex-wrap: wrap;
margin: calc(var(--grid-gap) * -1);
}
.grid-item {
display: block;
flex-basis: 0;
flex-grow: 1;
flex-shrink: 1;
margin: var(--grid-gap);
}
.has-2-columns > .grid-item {
width: calc( 50% - (var(--grid-gap) * 2));
}
.has-3-columns > .grid-item {
width: calc( 33.3333% - (var(--grid-gap) * 2));
}
.has-4-columns > .grid-item {
width: calc( 25% - (var(--grid-gap) * 2));
}
.grid-item.is-full {
width: calc( 100% - (var(--grid-gap) * 2));
}
.grid-item.is-three-fourths {
width: calc( 75% - (var(--grid-gap) * 2));
}
.grid-item.is-two-thirds {
width: calc( 66.6666% - (var(--grid-gap) * 2));
}
.grid-item.is-half {
width: calc( 50% - (var(--grid-gap) * 2));
}
.grid-item.is-one-third {
width: calc( 33.3333% - (var(--grid-gap) * 2));
}
.grid-item.is-one-fourth {
width: calc( 25% - (var(--grid-gap) * 2));
}
\nNow the last problem I have with this I'm on the fence about. It still bothers me that each grid item must have a class. One of the appeals of CSS grid is that child items just work. Sure you could use the universal selector, but it still somehow feels like bad practice because of the stigma around selector performance.
\nBefore I close out this article, I wanted to show one more grid solution that uses CSS grid. Similar to above, we can use CSS custom properties to adjust values inside the grid. If we replace a few key parts of our CSS grid based system at the very beginning with a handful of variables, it becomes much more versatile.
\n.grid {
--col-min-width: 250px;
display: grid;
grid-template-columns:
repeat( auto-fit, minmax( var(--col-min-width), 1fr ));
grid-gap: 1rem;
}
\n<!-- Larger content -->
<div class=\"grid\" style=\"--col-min-width: 500px\">
\nThis brings a new problem though. What happens when the screen is smaller than 500px? One solution I saw was the use of min()
inside the minmax
function:
.grid {
grid-template-columns:
repeat(
auto-fit,
minmax(min( var(--col-min-width), 100% ), 1fr)
);
}
\nBuyer beware however, at the time of writing the min()
function sees extremely limited browser support.
If you have components that use a different layout for portrait and landscape, you could also add another CSS variable to this system to control the min width of the items in portrait and landscape mode individually, which could prove to be quite powerful.
\nBoth of these systems have problems if you need to support internet explorer, because these systems either rely on CSS grid or custom properties, which IE never properly implemented.
\nThe auto grid using CSS grid seems like such a beautiful and simple solution to the common layout problem, which makes the difficulty of creating deviations all the more frustrating.
\nAt the end of the day, the fact of the matter is that there is no one-size-fits-all grid system. But I'm pretty happy with the ideas here, and look forward to seeing how the landscape of CSS evolves in the future.
\n", "date_published": "2020-01-23T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2020/kitsune/", "url": "https://mattmcadams.com/sketchbook/2020/kitsune/", "title": "Kitsune", "content_html": "\n", "date_published": "2020-01-07T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2019/css-organization/", "url": "https://mattmcadams.com/posts/2019/css-organization/", "title": "CSS Organization", "content_html": "While researching best practices of CSS organization this past year for a huge UI project at work, I ran across several ideas and strategies to solve this problem. My goal here is to digest these ideas and formulate a more comprehensive approach to organizing CSS.
\nFor the purposes of this article, I'll be talking more specifically about Sass and similar workflows.
\nMany developers I've seen attacking this problem think of the cascade as the enemy - a tangled mess of specificity issues and unpredictable styles. I'd argue that they're not great at writing CSS. The cascade is one of the fantastic features of CSS and it can make your life much easier if you lean into it.
\nFor example, why define the typography for every component when you can rely on inheritence? If you decide to later change the typographical scale the project is built on, its much easier to change at the root level than have to hunt down every component you've used. Of course, you can leverage CSS variables, but we'll get into that later.
\nThis is a concept I read a few months back and really enjoied it's concept. It doesn't seem to be widely talked about, but it essentially shows how to arrange CSS based on specificity, leaning into the cascade to carry styles throughout the design.
\nI think this is the most common-sense method of organizing a massive stylesheet or project. Consider this example index.scss
\n// Layer 1: Preprocessor
@include 'requirements';
@include 'settings';
@include 'tools';
// Layer 2: Basic Styles
@include 'reset';
@include 'elements';
// Layer 3: UI
@include 'layout';
@include 'components';
@include 'objects';
// Layer 4: Utilities
@include 'helpers'
\nIn the first layer, we include libraries our project relies on, our Sass functions and mixins, and any configuration variables / setup needed for them to run.
\nIn the second layer, we include any CSS resets and lay down the base styles for all of the html elements. This layer should have the least specificity, relying almost exclusively on html element selectors.
\nThe third layer is where all the fun is, and where you have the most freedom to move things around. In layout, I have things like the overall site layout, grid systems, etc. These should all essentially just be containers used to position other elements.
\nI also include UI components in this layer. Components are usually reusable class-based UI elements like cards, buttons, chips, etc. The third layer is the only layer where things are allowed to have "children". And I say children here in quotes because I'm not using literal descendent selectors, but BEM style naming to create child elements in a design sense. More on this below.
\nObjects in the third layer are one-per-page, major UI components referenced with an ID. This might be the header, footer, sidebar, etc. There should only be one of them. Using an ID selector here is debatable. It should be fine, but you can use classes for selecting those items if you want. The important thing is that there is a mental understanding that these are major UI elements.
\nThe last layer includes style rules with !important
. These are utility and helper rules that take precidence over the other styles no matter what, like .hide
to make sure the element is set to display: none.
\n\nThere are only two hard things in Computer Science: cache invalidation and naming things.
\n-- Phil Karlton
\n
Naming things are always hard. Too verbose and its a chore to type out, too short and its difficult to understand. For years, I tried to walk this fine line, to create abbriviations or short abstractions to make the code "cleaner" and easier to write. But as I've gotten more experience I've been leaning toward readability over writability because maintenance is a bigger chore than development.
\nThis is how I approach class names:
\ncomponent: .noun
child: .noun__noun
theme: .noun--adjective
state: .is-state | .has-state
object: #noun
layout: .l-layout
utility: .u-utility
accessibility: .aria-
\nAt first, it looks like BEM. Thats because it is. But then it looks kind of like SMACSS. Thats because it is. BEM and SMACSS solve similar but different problems, this is just a way of bringing them together.
\nI'm conflicted about this because it feels hacky, but I've seen several other developers use this method so I thought I'd mention it here as well.
\nYou can attach a suffix representing a media query to the end of a class like this: .l-grid@landscape
. This would show the contents of that container as a grid if the screen orientation is landscape. The problem with this is that the @
symbol isn't technically valid - you'll have to escape the character in the CSS.
@media (orientation: landscape) {
.l-grid\\@landscape {
...
}
}
\nIf using a framework like Vue or React that allow you to store your HTML, CSS, and JavaScript all in one file, I think you can move the component styles to those one-file components. The UI layer is so flexible that it shouldn't matter where exactly you put these styles, as long as you don't start overriding them or combining them.
\nRemember that each component and their children should be mutually exclusive from any other component. That is to say, a .card
can not also be a .button
. If you have a use case for this, consider creating a card theme: .card--button
.
I think variables are fantastic and you should absolutely use them wherever possible. If you can afford to drop support for older browsers, CSS custom properties are the way to go. Otherwise, you can use Sass or other preprocessor variables throughout the code. Variables of any kind should be included in the first layer of the CSS, before resets or base styles are added.
\n", "date_published": "2019-12-06T00:00:00Z" },{ "id": "https://mattmcadams.com/projects/rebar/", "url": "https://mattmcadams.com/projects/rebar/", "title": "Rebar", "content_html": "Inspired by humanistic sans-serif and slab serif typefaces, Rebar is a unique and modern hybrid serif / sans-serif font.
\nRebar is more than a typeface, it explores the anatomy of typography and looks at type as form and texture.
\nUpper and lowercase letters and numbers
\nType as texture
\nSpecial characters
\nThe typeface includes uppercase and lowercase letters A-Z, numbers, punctuation, and special characters. Rebar is available for free, and can be downloaded below.
\n\n", "date_published": "2019-11-18T00:00:00Z" },{ "id": "https://mattmcadams.com/posts/2019/code-comments/", "url": "https://mattmcadams.com/posts/2019/code-comments/", "title": "Code Comment Anchors", "content_html": "One of the really cool plugins I found for VS Code is this tool called Comment Anchors. It’s a way of adding more visible, meaningful comments to your code. I’m a really big fan of writing documentation in the code itself, so its nice to have a way to highlight the most important notes.
\nSeveral of my special comments fall under the category of “Notes to Self” and aren’t really meant to be seen by anyone else. I try to clean these up before a commit.
\nI think its better practice to store todo items in a separate task management system. Though sometimes items on a todo list may be much more technical and intimate to the task at hand to put it somewhere else.
\nI only ever use these on my local branch, and I try to remove or complete them before pushing to remote. It’s a good reminder of your current thoughts before going on lunch.
\nIf any todo item overstays its welcome in a code comment, it’s easy enough to move it to an issue or task.
\nSometimes it’s nice to jot down ideas about a specific code block in the code itself. These ideas may be a note-to-self, or brought up to be discussed with other developers. If the latter, you may consider using an Issue or communication channel to talk about the idea in real time.
\nThese are great for any generic comment to your future self, and I like to use them before switching branches or if I notice something about a different part of the code than the one I’m actively working on.
\nMuch like optimize, if you know what code block is causing a bug it could be helpful to tag it with a debug comment that you can reference in an issue. If you know how to fix the bug however, you may want to just go ahead and correct it.
\nThis could also be useful to use as a note-to-self tag if you’re working on another part of code and need to come back to fix the bug later
\nSometimes considered bad practice, leaving experimental code in while developing is occasionally useful. I like to use this tag as a self reminder to delete code I’ve commented out before opening a PR. I like to do this in its own commit, so that those experimental blocks are logged in case they are needed in the future.
\n// TEMP: Use JavaScript to X
// commented out code
\nUseful for calling out sections of code and ideas that need another set of eyes. If you feel unsure about a segment of code, it may be useful to include a review
comment that can then be seen easily during a pull request. Just remember to delete it after it’s been reviewed.
Warnings are used to communicate to another developer something they must know before modifying related code. Useful especially when the code does something odd or its purpose is not obvious.
\nMuch like warning
this comment can help communicate information to another developer. This information would be important but not critical.
For general comments, just use normal comment syntax for your language.
\nIf you need to use unorthodox methods to fix a bug or implement a feature, it can be nice to comment with one of these tags to communicate that to your future self or other developers. You can also revisit this from time to time to see if this method is still the best solution.
\nI know a lot of developers like to keep the code free of comment clutter, but when used responsibly, these types of tags can be really helpful.
\nComment Anchors will highlight the entire comment line as well as provide a list of anchors established. The plugin is also extremely customizable, so you can add or remove anchors to fit your style.
\nDownload my personal configuration.
\n", "date_published": "2019-11-02T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2019/wayfinding/", "url": "https://mattmcadams.com/sketchbook/2019/wayfinding/", "title": "Wayfinding", "content_html": "\n", "date_published": "2019-10-23T00:00:00Z" },{ "id": "https://mattmcadams.com/projects/skytrail/", "url": "https://mattmcadams.com/projects/skytrail/", "title": "SkyTrail", "content_html": "This branding project is a work of design fiction exploring the brand identity of a proposed public works project. The Birmingham SkyTrail is an inexpensive, environmentally-friendly form of transportation that will serve the communities in and around Birmingham by connecting people to parks, medicine, jobs, education and more in the downtown area.
\nThe circular form creates a humanistic feel while the extended corner anchors the mark and implies reliability. The interior of the mark references transit lines and the rolling landscape of Birmingham.
\nIdeation and process sketches
\nMountain is a 2 color silk screen print inspired by floating islands and geometric design.
\n\n", "date_published": "2019-05-03T00:00:00Z" },{ "id": "https://mattmcadams.com/sketchbook/2019/river-thumbnails/", "url": "https://mattmcadams.com/sketchbook/2019/river-thumbnails/", "title": "River thumbnails", "content_html": "\n", "date_published": "2019-03-20T00:00:00Z" },{ "id": "https://mattmcadams.com/projects/abstract-print/", "url": "https://mattmcadams.com/projects/abstract-print/", "title": "Abstract 2-06", "content_html": "\nThis illustration was born from one of the ideation sketched for another project. I brought it into a new file and played around with color and texture.
\n\n\n", "date_published": "2019-02-06T00:00:00Z" },{ "id": "https://mattmcadams.com/projects/uabit-annual-report/", "url": "https://mattmcadams.com/projects/uabit-annual-report/", "title": "UABIT Annual Report", "content_html": "\n2018 was a great year for the University of Alabama at Birmingham’s Department of Information Technology and we wanted to create a website to showcase the team’s efforts and achievements throughout the year.
\nSection colors controlled by CSS variables allowed faster prototyping, more reusable code, and less repetitive CSS
\n#section-1 { --theme: #1f6b52; }
.header { color: var(--theme); }
\nDesigned and developed to adjust to device screen size
\n