Levi McGranahan2023-12-08T13:55:00-07:00https://levimcg.com/Levi McGranahanlevimcg@gmail.comShare Controls Web Componenthttps://levimcg.com/blog/share-controls-web-component2023-12-08T13:55:00-07:002024-02-27T14:41:22-07:00I built a Web Component that adds native copy to clipboard and sharing controls to a web page.<p>I built a Web Component that adds native copy to clipboard and sharing controls to a web page. The original idea was based on this excellent <a href="https://set.studio/simplify-sharing-with-built-in-apis-and-progressive-enhancement/">blog post</a> by the folks at Set Studio. In this post I don't really want to go into the technical details of how it's built, but more of the conceptual approach and potential alternative ways to think about building progressively-enhanced Web Components.</p>
<h2>Initial implementation and design</h2>
<p>I've built the first version of this out and am currently using it on my recipe site, <a href="https://recipes.levimcg.com/">recipes.levimcg.com</a>. It works pretty well for my purposes, but I'm thinking about how it might be re-written or improved for more general-purpose use. It currently works like this:</p>
<pre class="hljs"><code data-language="html"><span class="hljs-tag"><<span class="hljs-name">share-controls</span> <span class="hljs-attr">url</span>=<span class="hljs-string">"https://levimcg.com"</span> <span class="hljs-attr">share-text</span>=<span class="hljs-string">"Share this!"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">p</span>></span>Place fallback content here. For example a simple URL.<span class="hljs-tag"><<span class="hljs-name">p</span>></span>
<span class="hljs-tag"></<span class="hljs-name">share-controls</span>></span></code></pre>
<p>Once the page is loaded and JavaScript is available, the <code><share-controls></code> element will replace the fallback content with JS-capable buttons that use the native <code>clipboard</code> and <code>share</code> APIs where browser support is available.<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup></p>
<p>This is what it looks like if JS is not available.</p>
<figure><img alt="The share section of a recipe with a URL visible" src="https://levimcg.com/media/pages/blog/share-controls-web-component/8e0225e83f-1702063606/share-controls-before-js.jpeg"></figure>
<p>This is after JS is available.</p>
<figure><img alt='A "copy" and a "share" button at the end of an article' src="https://levimcg.com/media/pages/blog/share-controls-web-component/f4b017b4b7-1702063159/share-controls.jpeg"></figure>
<p>The HTML looks like this once it's rendered.</p>
<pre class="hljs"><code data-language="html"><span class="hljs-tag"><<span class="hljs-name">share-controls</span> <span class="hljs-attr">url</span>=<span class="hljs-string">"https://levimcg.com"</span> <span class="hljs-attr">share-text</span>=<span class="hljs-string">"Share this!"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"sc-wrapper"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"sc-copy-button"</span>></span>Copy<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"sc-share-button"</span>></span>Share this!<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">share-controls</span>></span></code></pre>
<h2>Attributes</h2>
<p>This is how it's in its most simple form, but there are also a handful of attributes that can be used to customize the rendered button controls output. All of these attributes have default fallbacks.</p>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>url</code></td>
<td>A string that is coppied to the clipboard and/or used by the native share API. Defaults to the current page's URL</td>
</tr>
<tr>
<td><code>copy-button</code></td>
<td>A string that is used for the copy button text. Defaults to "Copy"</td>
</tr>
<tr>
<td><code>share-text</code></td>
<td>A string that is use as the message by the native web share API. Defaults to the current page title</td>
</tr>
<tr>
<td><code>share-button</code></td>
<td>A string that is used for the share button text. Defaults to "Share"</td>
</tr>
</tbody>
</table>
<h2>Design Alternatives</h2>
<p>I do wonder if there might be better ways to handle this kind of component than fully rendering the button controls on page load with JavaScript. I've started to ask myself, "<a href="https://blog.jim-nielsen.com/2023/as-good-as-html/">what would HTML do</a>?" in this case. For example, could my component just accept button elements as children and progressively enhance those with share and copy/paste functionality?</p>
<pre class="hljs"><code data-language="html"><span class="hljs-tag"><<span class="hljs-name">share-controls</span> <span class="hljs-attr">url</span>=<span class="hljs-string">"mywebsite.com"</span> <span class="hljs-attr">copy</span>=<span class="hljs-string">"copy-button"</span> <span class="hljs-attr">share</span>=<span class="hljs-string">"share-button"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"copy-button"</span> <span class="hljs-attr">hidden</span>></span>Copy<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"share-button"</span> <span class="hljs-attr">hidden</span>></span>Share<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"></<span class="hljs-name">share-controls</span>></span></code></pre>
<h3>Pros of this approach</h3>
<ul>
<li>This would eliminate the need for three of the four attributes that are currently need to offer customization.</li>
<li>The actual controls markup can be customized however needed and server-rendered.</li>
<li>This would make it more extensible if more sharing capabilities or APIs are added (or never fully implemented ) in browsers. Or maybe even implementing share via email with <code>mailto</code> links.</li>
</ul>
<h3>Cons of this approach</h3>
<ul>
<li>In order to get the fully progressive enhancement benefits, the author would have to remember to add <code>hidden</code> attributes to the buttons so that if JS was not available, the controls would not be available to use.</li>
<li>Associating the share and copy functionality with the child button elements becomes problematic without re-introducing additional attributes that author would need to know and understand. For example, using <code>id</code> on the buttons to associate those with custom <code>copy</code> and <code>share</code> attributes on the <code><share-controls></code> element</li>
</ul>
<h2>Wrapping up</h2>
<p>I guess for now I'll leave things as is and maybe come back to it later with a fresh perspective. If you want to have a look I put the source and some demos up on GitHub. I may or may not publish it to npm if it seems like it could become something useful.</p>
<ul>
<li><a href="https://github.com/levimcg/share-controls">Github Repo</a></li>
<li><a href="https://levimcg.github.io/share-controls/">Demos</a></li>
</ul>
<p>I love that this concept of <em>HTML Web Components</em> is having a moment right now. I've <a href="https://github.com/levimcg/accordion-container-element">built</a> a <a href="https://github.com/levimcg/focus-trapper">couple</a> of Web Components in this model in the past, but never really knew what to call them. I think <em>HTML Web Components</em> is a great term and has already done so much to improve the general awareness of Web Components. As <a href="https://gomakethings.com/an-example-of-an-html-web-component/">Chris mentioned</a> in their post, this approach is quickly becoming my preferred way of writing encapsulated JavaScript functionality with progressive enhancement.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>Currently, only Safari and Edge support the native share API <a href="#fnref1:1" rev="footnote" class="footnote-backref">↩</a></p>
</li>
</ol>
</div>Reviving my recipe websitehttps://levimcg.com/blog/reviving-my-recipe-website2023-12-05T13:12:00-07:002023-12-05T15:14:43-07:00A quick post about redesign my personal recipe website<p>I started a <a href="https://github.com/levimcg/recipe-book">Git repo</a> eight or nine years ago to store all of the recipes I use regularly. Some of them are my own and some tweaked versions of recipes I've found elsewhere. The very first version of the repo was just a folder full of markdown files. Eventually I used <a href="https://jekyllrb.com/">Jekyll</a> to output the markdown files to a super simple site so that I could easily reference them on my phone while cooking. I've changed up the design of the site a few times over the years and eventually migrated it from Jekyll to <a href="https://www.11ty.dev/">Eleventy</a>, but generally I don't touch the site for years at a time except to add new recipes periodically.</p>
<p>We recently had our kitchen remodeled and I've been cooking a lot lately. I got the itch to dust off the recipe site a couple of weekends ago and had a ton of fun doing a super quick redesign. I stayed with Eleventy for the static site generator and kept the styling super simple with all inlined vanilla CSS, leveraging a lot of modern CSS features that make things so easy these days.</p><figure class="hero" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/reviving-my-recipe-website/b9721cc689-1701800566/blog-post-recipes.jpeg" alt="A single recipe page for bread dough. The main part of the page looks like a Mead composition notebook." loading="lazy" decoding="async">
</figure>
<p>I added a super simple share widget last week that uses a progressively-enhanced <a href="https://adactio.com/journal/20618">HTML Web Component</a> to add a copy/paste and share (using the native <code>share()</code> API) buttons. I hope to write a separate post about that sometime soon.</p>
<p>It's been a fun and refreshing little project that has scratched an itch I've had recently to design and build something. Definitely check out my newly updated <a href="https://recipes.levimcg.com/">recipe site</a> if you're looking for something new to cook!</p>Playing shows with Yukihttps://levimcg.com/blog/playing-shows-with-yuki2023-10-13T13:05:00-06:002023-10-18T14:32:49-06:00I played bass on my fried Yuki's new record and played some shows for the first time in a decade.<p>I started playing in a new band on a fairly regular basis this year. My friend <a href="https://yukikawana.bandcamp.com/">Yuki</a> is a singer-songwriter that has been working on a new album and ask me and some of our other friends to play in her back-up band.</p>
<p>We started regular rehearsals early this year and went into the studio to record six tracks with a full band in addition to four solo tracks that Yuki recorded. The record will come out some time later this year or early next. In the meantime we are playing a couple of shows as a full band this year. We already played one back in September at a really cool block party thrown by <a href="https://www.russianrecording.com/">Russian Recording</a> celebrating their 20th anniversary, and we have another show coming up November 11 here in Bloomington at the I Fell Gallery. Here's an image of the flyer that I designed for the show in November.</p><figure class="" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/playing-shows-with-yuki/1890e2f353-1697225284/yuki-show-flyer-web.jpg" alt="A concert poster with large hand-drawn type and vivid colurful shapes and textures" loading="lazy" decoding="async">
<figcaption>
8.5x11 flyer for the November show </figcaption>
</figure>
<p>It feels really nice to be playing music again. It was such a huge part of my life from the time I was a teenager until about 10 years ago, so it's very fulfilling to be back at it, even if it's only for a while.</p>Weathering a shitstormhttps://levimcg.com/blog/weathering-a-shitstorm2023-04-06T16:41:00-06:002023-04-06T14:42:51-06:00Quick update on how things are going at our house so far this spring.<p>It's been a heck of a few weeks around here. It started off when we got back from spending a week in Florida for spring break with some of our close friends and their kids. I caught a nasty cold the day before we left to come home which made for a not so great 15 hour drive home. I went straight back to work on Monday, failing to take the sick time I actually needed to recover which caused my cold to stick around for nearly two weeks.</p>
<p>The week after we got home from vacation Sam fell and broke her elbow. After X-rays then a CT scan, it was immediately clear to the doctor that the bone needed repaired, so they schedule surgery for the following day. She's recovering well, but it's all a lot to come back to after a relaxing vacation at the beach.</p>
<p>To round out March and kick off April we got hit with some pretty gnarly storms, including a tornado touching down, this past Friday which happened to be April Fool's Day. My mom, who lives about 20 minutes from here, lost power for almost 72 hours. Sometimes when it rains it really does pour, as they say.</p><figure class="" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/weathering-a-shitstorm/7f49edab54-1680813448/diagram-edited.jpeg" alt="A hand-drawn diagram of a wave the x-axis represents time and the y-axis how sucky things are" loading="lazy" decoding="async">
</figure>
<p>I'm learning to accept that this is just the way life works. I like to image myself moving through life on one of those wave diagrams from math class. Time moves forward. Sometimes you're on peak and others you're down in the trough. I'm not good at science, and this metaphor is probably falling apart, but here's hoping your peaks are longer than your troughs.</p>
<p>Here are a few things to remember when you're in the trough weathering a shitstorm.</p>
<ul>
<li>Take something off your plate. It's ok to bail on a personal commitment or reschedule a meeting or two.</li>
<li>Focus on one thing you can can control and move forward if you can.</li>
<li>Take a nap. Naps rule and you don't need to earn them.</li>
</ul>Who gets my fonts?https://levimcg.com/blog/who-gets-my-fonts2023-03-07T23:51:00-07:002023-03-07T23:23:32-07:00Is there a way to bequeath your ownership of a collection of fonts? Like, fonts that you paid for? The short answer is...no.<p>Is there a way to bequeath your ownership of a collection of digital stuff? I'm defintely not talking about stupid shit like NFTs. I mean useful digital things like fonts and assets that you paid for. I started to look around and the short answer is...no.</p><blockquote>
Every font user must be covered by a license. Each license is valid for one legal entity only. Separate entities must purchase separate licenses. <footer>
<a href="https://fatype.com/eula" rel="noopener noreferrer">Fatype EULA</a> </footer>
</blockquote>
<p>It's a pretty standard of an end user license agreement (EULA) to only allow the person who bought the fonts—the license holder—to use them. That's it. If that's you, when you're gone, so are those fonts. Unusable.</p><blockquote>
Only you can use the fonts. <footer>
<a href="https://lettermatic.com/fonts/parclo-serif/buy?plan=freelancer" rel="noopener noreferrer">Lettermatic</a> </footer>
</blockquote>
<p>$100–500 for a well-designed and well-built font from an independent foundry is honestly a <em>very</em> reasonable price at the time of this writing. Fonts are something I value and prioritize spending money on, but most non-designer people could never understand paying the equivalent of a car payment for a folder of files you install on your computer. My kid lost their mind when I told them I paid $400 for a font the other day.</p>
<p>It is conceivably <em>very</em> easy to spend thousands of dollars on fonts as a professional designer. Imagine being some sort of craftsman (say, a baker, or carpenter) and spending $10K on various specialized and durable tools over the course of your career. If it was me, I would very much want to pass those tools on to someone that might make good use of them.</p>
<p>There are a few digital-only products I'm thinking of here. These are all things you buy on the internet as a professional desinger.</p>
<ul>
<li>Fonts (.woff, woff2, .otf, .ttf files...)</li>
<li>Brushes and textures (Procreate, Illustrator, etc.)</li>
<li>Textures (image files)</li>
<li>eBooks</li>
<li>PDFs </li>
</ul>
<p>Could we give our licenses and ownership of digital stuff back to some entity/organization that could repurpose them? There are so many designers that deserve to work with these tools. I want to give my fonts to some one!</p>Migrating from Eleventy to Kirbyhttps://levimcg.com/blog/migrating-from-eleventy-to-kirby2023-03-07T12:09:00-07:002023-03-09T13:15:29-07:00I decided to migrate my site from Eleventy, a static site generator to Kirby a file-based CMS written in PHP.<p>I wrote a <a href="https://levimcg.com/blog/static-site-generator-fatigue/">post</a> a while back toying with the idea of migrating my personal site from Eleventy to a new platform with more built-in content management features. Over my recent holiday break, I decided to give <a href="https://getkirby.com/">Kirby</a> a go and was able to migrate and launch my new site after a few weeks of work. I've seen other folks online <a href="https://front-end.social/@matuzo/109938175237327457">posting about</a> potentially migrating to Kirby, so I thought I'd take some time to write up my experience migrating and launching a new Kirby-powered site.</p>
<p><strong>This isn't meant to be a tutorial on how to use Kirby</strong>. The Kirby docs are very thorough, well-written, and some of the best out there, in my opinion. If you want to learn how to build a Kirby site, I would <a href="https://getkirby.com/docs/guide">start there</a>. My goal here is to share my experience of migrating my site and why I chose to move away from a static site generator. If that sounds interesting or helpful to you, keep on reading!</p>
<h2>About Kirby</h2>
<p>At it's simplest, Kirby is a file-based CMS that allows you to write content in text files. Content is stored as plain text in .txt files. Kirby reads content files from disk and displays a page when a user visits a URL on your site. There is no database involved by default, but it is possible to integrate external data sources. It's written in PHP and includes, among lots of other great features, the following:</p>
<ul>
<li>A robust templating system</li>
<li>An optional, but very flexible admin UI (the Panel) for managing content.</li>
<li>A powerful plugin system with lots of good community-contributed plugins</li>
<li>Easy and flexible image processing</li>
<li>Excellent <a href="https://getkirby.com/docs/reference">documentation</a>, a helpful <a href="https://forum.getkirby.com/">support forum</a>, and an active <a href="https://chat.getkirby.com/">Discord community</a></li>
</ul>
<p>Kirby can be easily hosted on most shared hosting providers and in my experience, can make very fast and performant websites. I've got an old shared hosting account that I started using again for my new site and there hasn't been any noticeable dip in performance and/or page load. That's after migrating from a fully static site hosted on Netlify's super fast edge CDNs.</p>
<h2>Managing content</h2>
<p>All content is stored as plain text files in folders which makes Kirby very fast and reliable. If you want to keep things as simple as possible you can write all of your content in Markdown (or <a href="https://getkirby.com/docs/guide/content/text-formatting#kirbytext">KirbyText</a>) inside of plain .txt files and then just upload those to your server. In addition to Markdown you can write structured data in a front matter-like YAML syntax that Kirby calls <a href="https://getkirby.com/docs/guide/content/fields">fields</a>. You then have access to your content and structured data in your templates. Coming from Eleventy, this set up feels very comfortable to me. A typical Kirby content file might looks something like this. Each field is a key/value pair written in YAML.</p>
<pre class="hljs"><code data-language="yaml"><span class="hljs-attr">Title:</span> <span class="hljs-string">My</span> <span class="hljs-string">first</span> <span class="hljs-string">post</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">Date:</span> <span class="hljs-number">2023</span><span class="hljs-number">-03</span><span class="hljs-number">-01</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">Cover:</span> <span class="hljs-string">my-cover-image.png</span>
<span class="hljs-meta">---</span>
<span class="hljs-string">Write</span> <span class="hljs-string">normal</span> <span class="hljs-string">**markdown**</span> <span class="hljs-string">content</span> <span class="hljs-string">here...</span></code></pre>
<p>In Kirby you keep any related media like images in the same folder as your content .txt files so referencing images like in the previous example is as easy as providing the file name. This co-location of assets with content files also works for CSS and JavaScript files too—hello, easy <a href="https://getkirby.com/docs/cookbook/templating/art-directed-blog-posts">art directed posts</a>!</p>
<h3>The Panel</h3>
<p>Kirby also comes with a content management UI called the <em>Panel</em>, which is available in a default Kirby install at <code>yoursite.com/panel</code>. The Panel allows you to easily configure an admin UI that is tailored to your content using YAML files called <a href="https://getkirby.com/docs/guide/blueprints/introduction"><em>Blueprints</em></a>. In Blueprints you configure the fields you want to use and the layout of the admin interface. It's a very straightforward and powerful system for creating structured content. Under the hood the Panel is just writing input from fields in your Panel to the same .txt files that you can also edit by hand.</p><figure class="hero" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/migrating-from-eleventy-to-kirby/e1065986f5-1678142303/blog-post-panel.jpeg" alt="The Kirby admin panel made up of several form fields. It shows a blog post on my site." loading="lazy" decoding="async">
<figcaption>
The Kirby Panel </figcaption>
</figure>
<p>I've only scratched the surface when it comes to what you can do with the panel, but the previous image shows what my Panel looks like for blog posts.</p>
<h2>Migrating content</h2>
<p>I was planning a total redesign toward the end of last year which meant by the time I got started on the migration to Kirby I had already built out the HTML and CSS for the majority of my site. I was starting from a blank slate, so the only real migration that need to be done was the content. My site was previously built in Eleventy, so all the content was already in Markdown which Kirby supports out of the box. I considered the idea of writing some sort of script to migrate all of my existing Markdown files to Kirby's similar, but slightly different <code>.txt</code> file format, but decided instead to move content over manually and take the opportunity to clean up, edit, and cull some of the Markdown in my old posts.</p>
<p>While setting up the main Blueprints for my new Kirby installation, I decided to use the <a href="https://getkirby.com/docs/reference/panel/fields/blocks">Blocks field</a> for the main content of each page/post on my site. The <em>Blocks</em> field is <em>"A visual editor for long-form text and modular pages"</em> as it's described in the Kirby docs. It's similar in concept to the WordPress block editor and allows you to use various pre-defined content blocks that you can drag and drop to reorder. When you use the blocks field, the content is stored in a big JSON array of block objects. For example, the <a href="https://getkirby.com/docs/reference/panel/blocks/markdown">Markdown block</a> stores content in an JSON object that looks something like the following.</p>
<pre class="hljs"><code data-language="json">{
<span class="hljs-attr">"content"</span>: {
<span class="hljs-attr">"text"</span>: <span class="hljs-string">"This JSON field contains the [Markdown]([...](https://www.markdownguide.org/getting-started/)) for a block of text."</span>
},
<span class="hljs-attr">"id"</span>: <span class="hljs-string">"bb91e03c-7393-4087-aa22-b20d1d84ac74"</span>,
<span class="hljs-attr">"isHidden"</span>: <span class="hljs-literal">false</span>,
<span class="hljs-attr">"type"</span>: <span class="hljs-string">"markdown"</span>
}</code></pre>
<p>Migrating would have been easier an more automate-able if I had used the regular text field (which supports Markdown) for the main content of pages and posts, but the Blocks field seemed like a really appealing way to write and edit posts, so the manual migration process was a bit tedious. The good news was my blog was only about 45 posts at the time of migration, so it took me less than three hours to migrate all the content to Kirby blocks.</p>
<h2>Templating</h2>
<p><a href="https://getkirby.com/docs/guide/templates/basics">Templates</a> in Kirby art .php files that contain the HTML for your pages. You use PHP inside of templates to dynamically render content. When I think of PHP templating my mind immediately goes to WordPress' rats nest of a templating system (or lack there of), but Kirby does PHP templating pretty well, in my opinion.</p>
<p>To create a new template for a content type you add a new .php file to your <code>/site/tempaltes/</code> folder with the same name as your <code>.txt</code> files for that content type. For example, if you call have a content type named "post" with a folder structure like this: <code>/posts/my-new-post/post.txt</code> in your content folder then all you need to do is create a templated at <code>/site/templates/post.php</code> and all posts will be rendered with that template. Pretty cool!</p>
<h3>Data in templates</h3>
<p>Inside your templates you have access to <code>$page</code>, <code>$site</code>, and <code>$pages</code> variables by default which allow you to access the content/fields of your pages and site. For example, if you have a field in your about page content file called <code>description</code>, I can access it in my templates like this <code><?= $page->description() ?></code>. This is very similar to Eleventy and other static site generators. There are several other ways to get data into your templates like <a href="https://getkirby.com/docs/guide/templates/controllers">controllers</a>, and <a href="https://getkirby.com/docs/guide/templates/controllers">collections</a>, but those concepts are beyond the scope of this post.</p>
<p>Given a content file that looks likes this:</p>
<pre class="hljs"><code data-language="yaml"><span class="hljs-attr">Title:</span> <span class="hljs-string">My</span> <span class="hljs-string">post</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">Description:</span> <span class="hljs-string">A</span> <span class="hljs-string">cool</span> <span class="hljs-string">new</span> <span class="hljs-string">post</span> <span class="hljs-string">about</span> <span class="hljs-string">Kirby!</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">Date:</span> <span class="hljs-number">2023</span><span class="hljs-number">-03</span><span class="hljs-number">-03</span></code></pre>
<p>You can access these values in your template like this:</p>
<pre class="hljs"><code data-language="html"><span class="hljs-tag"><<span class="hljs-name">h1</span>></span><span class="php"><span class="hljs-meta"><?</span>= $page->title() <span class="hljs-meta">?></span></span>
<span class="hljs-tag"><<span class="hljs-name">p</span>></span><span class="php"><span class="hljs-meta"><?</span>= $page->description() <span class="hljs-meta">?></span></span>
<span class="hljs-tag"><<span class="hljs-name">time</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"<?= $page->date() ?>"</span>></span>
<span class="php"><span class="hljs-meta"><?</span>= $page->date()->toDate(<span class="hljs-string">"M j, Y"</span>) <span class="hljs-meta">?></span></span>
<span class="hljs-tag"></<span class="hljs-name">time</span>></span></code></pre>
<p>Kirby also comes with a lot of template helpers right out of the box that make it easy to do things like sorting, date conversion (see previous example), and lots of other super helpful stuff. I won't go into all of those helpers here, but there is a really great <a href="https://getkirby.com/docs/cookbook/templating/php-templates">primer on templating</a> in Kirby on the docs site.</p>
<h3>Snippets and slots</h3>
<p><a href="https://getkirby.com/docs/guide/templates/snippets#passing-slots-to-snippets">Snippets</a> are the way Kirby handles including re-usable pieces of code across multiple templates. Until recently they worked mostly similar to languages like Nunjucks includes and macros where you could use them to just include a piece of code or pass variables to them which made it possible to render different types of data based on context. </p>
<p>Earlier this year the Kirby team release an update to snippets that added a new <a href="https://getkirby.com/docs/guide/templates/snippets#passing-slots-to-snippets"><em>slots</em> feature</a>, which kind of rules. If you've used Web Components and/or frontend libraries like Vue, then slots will feel very familiar. This feature allows you to create <em>slots</em> inside of snippets where you can render content from the parent template where they are used. This opens up a lot of cool possibilities to build self-contained component-like snippets or even use them for template inheritance.</p>
<p>Here is what my base template that wraps every other template on my site looks like. That <code><?= $slot ?></code> is the default slot in the snippet where everything gets rendered.</p>
<pre class="hljs"><code data-language="html"><span class="php"><span class="hljs-meta"><?</span>= <span class="hljs-comment">/* /site/snippets/layouts/default.php */</span><span class="hljs-meta">?></span></span>
<span class="hljs-meta"><!DOCTYPE <span class="hljs-meta-keyword">html</span>></span>
<span class="hljs-tag"><<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">head</span>></span>
<span class="php"><span class="hljs-meta"><?</span>= snippet(<span class="hljs-string">'head-content'</span>) <span class="hljs-meta">?></span></span>
<span class="php"><span class="hljs-meta"><?</span>= css(<span class="hljs-string">'assets/css/styles.css'</span>) <span class="hljs-meta">?></span></span>
<span class="hljs-tag"></<span class="hljs-name">head</span>></span>
<span class="hljs-tag"><<span class="hljs-name">body</span>></span>
<span class="php"><span class="hljs-meta"><?</span>= snippet(<span class="hljs-string">'header'</span>) <span class="hljs-meta">?></span></span>
<span class="hljs-tag"><<span class="hljs-name">main</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"main-content"</span>></span>
<span class="php"><span class="hljs-meta"><?</span>= $slot <span class="hljs-meta">?></span></span>
<span class="hljs-tag"></<span class="hljs-name">main</span>></span>
<span class="php"><span class="hljs-meta"><?</span>= snippet(<span class="hljs-string">'footer'</span>) <span class="hljs-meta">?></span></span>
<span class="hljs-tag"></<span class="hljs-name">body</span>></span>
<span class="hljs-tag"></<span class="hljs-name">html</span>></span></code></pre>
<p>And here is what that layout snippet looks like in-use with my <em>post</em> template. The markup is simplified for demo purposes.</p>
<pre class="hljs"><code data-language="html"><span class="php"><span class="hljs-meta"><?</span>= <span class="hljs-comment">/* /site/templates/post.php */</span><span class="hljs-meta">?></span></span>
<span class="php"><span class="hljs-meta"><?php</span> snippet(<span class="hljs-string">'layouts/default'</span>, slots: <span class="hljs-keyword">true</span>) <span class="hljs-meta">?></span></span>
<span class="php"><span class="hljs-meta"><?php</span> slot() <span class="hljs-meta">?></span></span>
<span class="hljs-tag"><<span class="hljs-name">h1</span>></span><span class="php"><span class="hljs-meta"><?</span>= $page->title() <span class="hljs-meta">?></span></span><span class="hljs-tag"></<span class="hljs-name">h1</span>></span>
<span class="php"><span class="hljs-meta"><?</span>= $page->text()->toBlocks() <span class="hljs-meta">?></span></span>
<span class="php"><span class="hljs-meta"><?php</span> endslot() <span class="hljs-meta">?></span></span>
<span class="php"><span class="hljs-meta"><?php</span> endsnippet() <span class="hljs-meta">?></span></span></code></pre>
<p>Pretty cool. I've not had a ton of time to dig into snippets with slots yet, but I'm planning to refactor the templates on my site later this year to make better use of this great new feature.</p>
<h2>Pros and Cons</h2>
<p>So far I'm very pumped about my new site. I'm really enjoying using the Kirby Panel for writing and editing new posts. The block editor is a lot of fun! I didn't realize how many features of a more traditional CMS I missed having. One of my goals in migrating to Kirby was to eliminate as much friction as possible when writing and posting on my site. Kirby makes editing and posting new content a breeze. I can even write and updated content from my phone using the Panel, which is super valuable for someone like me that generally has at least a few typos in every new post.</p>
<h3>Code updates and deployment</h3>
<p>Hosting my old site on Netlify also made deploying design and functionality updates a bit easier because of the tight integration with GitHub. Updates were just a matter of pushing from my local Git repo and then seeing the changes go live almost immediately. While it's possible to use services like DeployHQ with Kirby to do the same thing, I've opted to stick with a pretty straightforward rsync deployment script (based on <a href="https://help.fortrabbit.com/rsync">this article</a>) that is working out just fine for now. It's a touch more sophisticated than a FTP drag-and-drop situation, but to be honest, I'm also kind of ok with FTP these days too.</p>
<h3>Long-term portability</h3>
<p>While the blocks field is a lot of fun to use and a really powerful way to edit content, I do think about the long-term portability of the content on my site. Markdown is ubiquitous on the web and probably will not be going away any time soon. I'm using the Markdown block in Kirby so my content is still <em>technically</em> being stored as Markdown, but it is inside of a JSON array which makes it a more difficult to port to another CMS or static site generator should I ever want to migrate of of Kirby. I'm not too worried about this since it would be very possible to write a script that would convert the JSON array of Kirby blocks back to regular Markdown files.</p>
<h2>Wrapping up</h2>
<p>For anyone thinking about making the move to a CMS from a static site generator, I would highly recommend checking out Kirby. On top of all the great features it offers out of the box, it has a vibrant and helpful user community and the project is very active and well-managed with lots valuable and thoughtful features released on a consistent basis. In a time where personal sites seem to be making a comeback, I'm very happy to have mine running on Kirby.</p>It's ok to need a day offhttps://levimcg.com/blog/its-ok-to-need-a-day-off2023-03-02T15:37:00-07:002023-03-07T13:51:25-07:00Sometimes the only way to avoid burning out is to stop what you're doing and lay on the couch.<p>As I've grown older it's become a lot easier for me to tell when I'm in desperate need of a break. Apathy sets in and every task feels tedious, boring, and nearly impossible to finish. I've had a lot of experience, but to be clear, it took living through a deadly pandemic, starting talk therapy, and medication for me to be able to identify the signals of slowly burning out.</p>
<p>The best way I can describe my mindset when I get this way is that I'm mad at <em>everything</em>, and I don't want to do <em>anything</em>. I've felt this way all week, so yesterday I took a personal day to rest and recharge. I spent the day doing <em>nothing</em>. I laid on the couch for five to six hours, pet my dogs, watched a couple of movies and YouTube videos, ate pizza, and then went to bed early. No laptop, no productivity, just rest and purposeful distractions.</p>
<p>I feel like a different person today. I was able to get a wonderful night's sleep which is also <a href="https://www.theguardian.com/science/2016/aug/23/sleep-resets-brain-connections-crucial-for-memory-and-learning-study-reveals">crucial for a reset</a>. This morning when I sat down at my computer everything seemed clear, I felt motivated, and was more productive than I have been for quite a while. I recognize the privilege of being able to take a last-minute day off to do nothing, but if you occasionally get to feeling this way too, it's ok to take a day off every once in a while.</p>How I take notes with Obsidianhttps://levimcg.com/blog/how-i-take-notes-with-obsidian2023-02-20T15:49:00-07:002023-02-20T13:59:36-07:00I started using Obsidian to manage all of my notes about a year ago and now I don't know how I would get by without it.<p>Back in early 2022 I decided to start being a bit more intentional about the way I capture and keep track of my notes. Super-serious pros call this <em>Personal Knowledge Management (PKM)</em>. There are <a href="https://zettelkasten.de/">multiple</a> <a href="https://www.buildingasecondbrain.com/course">formalized</a> PKM <a href="https://johnnydecimal.com/">systems</a>, which sound appealing, but I know myself. I don't have tons of discipline in terms of keeping things organized and structured, so I took the least complicated, most friction-free approach to get started—just dump every note into a folder.</p>
<p>I used <a href="https://www.notion.so/">Notion</a> off and on for a few years up until that point, but it never really stuck for me. A colleague of mine (an avid notetaker) mentioned that they had recently switch from using Notion to an app called <a href="https://obsidian.md/">Obsidian</a>. They had nothing but good things to say, so I decided to check it out.</p>
<p>Obsidian doesn't have ton of bells and whistles out of the box which was very appealing, but the real clincher for me was that it's really just a tool that helps you organized a bunch of Markdown files that sit on your computer. You can up and move them anywhere you want if you decided you don't like the app. And since it's all based around Markdown (plain text files) and not locked up in some else's database, your notes are mostly future-proof. That plus a robust plugin system that allows you to control the complexity and feature set of the app had me sold.</p>
<p>I've used Obsidian for just over a year now and it's become an important tool in my day-to-day work. I probably only take advantage of about 10% of what it is possible to do with the app, but I thought I would share my personal set up, including some of the plugins and features I can't live without.</p>
<p><strong>A quick note</strong>. This isn't meant to be a tutorial on how to use Obsidian, for that you should definitely check out this <a href="https://www.youtube.com/@nicolevdh">Nicole van der Hoeven's</a> YouTube channel. They have tons of great videos about all aspects of taking notes with Obsidian.</p>
<h2>My set up</h2>
<p>I've taken inspiration from some of the more formal systems mentioned above, but I try to keep my system as simple as possible and only introduce complexity when it actually helps me be more productive and/or stay more organized. I mostly rely on search to navigate my <em>vault</em>, as Obsidian calls it, so the structure is pretty flat. Here's what my vault set up looks like.</p><figure class="hero" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/how-i-take-notes-with-obsidian/59566be401-1676862853/obsidian-set-up.jpeg" alt="A note open in the Obsidian interface with the title "How I take notes with Obsidian"" loading="lazy" decoding="async">
<figcaption>
Writing this blog post in Obsidian </figcaption>
</figure>
<h3>Daily Notes</h3>
<p>Daily notes is a core plugin that gives you an easy way to create a daily note from a template. There is an additional plugin called <a href="https://github.com/liamcain/obsidian-calendar-plugin"><em>Calendar</em></a> that works really well with daily notes. It gives you a nice calendar view in the right sidebar that makes it easy open notes from a specific day of the month.</p>
<p>As I already mentioned, I don't put a ton of structure around my daily notes. They tend to serve as an all-purpose scratchpad most days. I draft emails there, capture interesting links, and other random things I want to remember.</p>
<h3>Inbox</h3>
<p>This is the engine room of my vault. I defined my <em>Inbox</em> folder as the default location for all new notes so I don't have to stop and think about where to put notes and can get straight to caputring whatever I'm working on. The important thing here is that there's no friction to creating a new note.</p>
<p>There are no subfolders inside my <em>Inbox</em>. Instead I use <a href="https://help.obsidian.md/Plugins/Backlinks">backlinks</a> to other notes (more on that later) to organize things. This folder is just a catch-all spot for new notes. Obsidian has a great <em>find</em> functionality (⌘ + O) that I use to navigate between notes, so additional folder structure in my Inbox isn't that important.</p>
<p>I try to spend some time each week organizing my <em>Inbox</em> notes. Mostly that means adding backlinks where needed to what I call <em>index notes</em>, which are a loose interpretation of the <a href="https://www.dsebastien.net/2022-05-15-maps-of-content/">Maps of Content</a> (MOCs) concept. In general, I make heavy use of backlinks to organize notes, but a lot of people also use tags or even folders to keep things organized. I haven't found a way that tags add value to my workflow, but I can see why a lot of folks make heavy use of them.</p><figure class="hero" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/how-i-take-notes-with-obsidian/915f6552c0-1676925107/obsidian-index-note.jpeg" alt="A note in the Obsidian interface with the headline "Recipes" and a list of linked notes" loading="lazy" decoding="async">
<figcaption>
Typical "index note" in my vault </figcaption>
</figure>
<p>I use the <a href="https://github.com/blacksmithgu/obsidian-dataview">Dataview plugin</a> to auto-generate a list of linked notes in my <em>index notes</em> (similar to a MOC). All I have to do is add a backlink in any note to its <em>parent</em> index note and the dataview plugin will automatically display a link to that new note.</p>
<h3>Evergreen notes</h3>
<p>This is where I keep most of my <em>Index notes</em>. The content of this folder changes over time based on long-running projects I'm working on, hobbies that I'm into at the moment, etc. But, some things like <em>Idea Seeds</em> and <em>Recipes</em> are permanent.</p>
<h3>Projects</h3>
<p>These notes are similar to evergreen notes, but have a finite lifespan. They are projects that have a beginning and end. They generally start off as a single note or outline and eventually become index notes that use the Dataview plugin to create an index out of backlinks. Once I'm finished with a project note, it gets moved into the <em>Archive</em> folder. </p>
<p>There is a really cool new-ish plugin called <a href="https://github.com/marcusolsson/obsidian-projects"><em>Projects</em></a> that I started using recently and it's pretty great. It brings a lot of the functionality of <a href="https://www.notion.so/help/intro-to-databases">Notion databases</a> to Obsidian which can be really very for larger projects with lots of tasks and research associated with it.</p>
<h3>People</h3>
<p>This is a new (to me) folder I've been experimenting with recently, but <em>people</em> notes is a fairly common practice in the PKM world, especially for folks that do a lot of more formal research. I'm sort of thinking of it like my own lightweight CRM. My people notes work very similar to evergreen notes and projects. Each note is an index note for a person with an auto-generated list of links to other notes related to them. I use these to keep track of notes from 1:1s at work, important details from doctors visits, and contact info for service providers like plumbers, HVAC repair, etc.</p>
<h3>Archive</h3>
<p>The Archive folder is where notes go when I'm done with them, but I'm bad about staying on top of this. For example, there are tons of notes in my inbox that I would probably only rarely need to reference, if ever. I should probably be more diligent about moving those to my archive, but there's always room for improvement 🤷♂️.</p>
<h3>Attachments & Templates</h3>
<p>These two are pretty boring, but worth mentioning. The <em>Attachements</em> folder is where any media like images and videos get automatically stored when you paste or drag them into a note. My <em>Templates</em> folder is where is store templates for different types of notes that I create often. For example, my daily note. 🔥 Hot tip 🔥 there is a great plugin called <a href="https://github.com/SilentVoid13/Templater"><em>Templater</em></a> that does some cool dynamic stuff with note templates. Definitely worth checking out.</p>
<h2>Wrapping up</h2>
<p>To wrap up here are some of my favorite plugins, themes, and features.</p>
<ul>
<li><a href="https://help.obsidian.md/Plugins/Graph+view">Graph view</a> — As I mentioned, I use a lot of backlinks to organize notes. The graph view is a neat way to visualize all of the connections between your notes.</li>
<li><a href="https://github.com/blacksmithgu/obsidian-dataview">Dataview plugin</a> — I've talked about this a lot already, but it's a must-have in my opinion</li>
<li><a href="https://github.com/liamcain/obsidian-calendar-plugin">Calendar</a> — Really helpful if you're taking daily notes</li>
<li><a href="https://github.com/oliveryh/obsidian-emoji-toolbar">Emoji Toolbar</a> — 🤓 Makes it easy to insert emoji using the command palette (⌘ + P)</li>
<li><a href="https://minimal.guide/Home">Minimal theme</a> — this is by far the most popular theme and the best looking, IMO</li>
</ul>
<p>I'm far from being an Obsidian power user, but this system works for me. If you're looking to be more intentional in your daily note-taking practice, the best advice I have is keep things as simple and only layer on complexity and/or organization when it actually makes your life easier.</p>Spring chanceshttps://levimcg.com/blog/spring-chances2023-02-11T19:33:00-07:002023-02-17T19:34:00-07:00I started growing a garden in my back yard two years ago and I contiue to learn from it.<p>I started growing a garden in my back yard two years ago in the spring of 2021. Everyone was still in full pandemic mode at that point and the vaccines were just starting to roll out. Gardening seemed like a good activity to pick up back then. It's mostly done outdoors, it doesn't necessarily need to be a group activity, and I love to cook so having a supply of home-grown vegetables is very welcome. I had several pandemic hobbies. This one just happened to stick and it was a real bright spot in an otherwise bleak time. This coming spring will be my third growing season.</p><figure class="hero" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/spring-chances/65b8459c17-1676162056/spring-2023-1200x900.jpeg" alt="Two small chive plants close up. It's sunny, but still winter. They're bright green, but everything around them is dead and gray." loading="lazy" decoding="async">
<figcaption>
My baby chive plants live on </figcaption>
</figure>
<p>This morning I was making coffee at the kitchen sink, staring out into the back yard. It's been a sunny and fairly warm (47°F/8°C) day for February, a welcome treat in the deepest, grayest part of the Midwestern winter. Looking out at one of the small raised beds I built last year, I noticed two small green tufts sticking up out of the dirt among all the other gray and brown dead stuff. It was two small bunches of chives I cut off at ground level last fall shortly after our first frost. Those two bunches came from splitting and transplanting one big bunch I had planted the year before in my inaugural pandemic garden. Here they are again, starting over after making it through another winter.</p>
<p>My tiny backyard garden isn't much, but it means a lot to me. Remember to always look forward to spring and a new chance to start over.</p>More control over Figma SVG exportshttps://levimcg.com/blog/more-control-over-figma-svg-exports2023-01-29T14:44:00-07:002023-03-07T11:14:06-07:00How to actually flatten vector artwork when exporting SVGs from Figma<p>We are in the process of redrawing all of the source artwork for our <a href="https://rivet.iu.edu/">design system's</a> icon set at work, and he other day we came up against some issues with Figma's default SVG exports. I started googling for articles about how to get cleaner exports from Figma, but kept coming up short for answers. We were eventually able to resolve our particular issues and now have a good process in place for exporting SVGs, but I thought I would take some time to <a href="https://twitter.com/chriscoyier/status/925081793576837120?s=20&t=Tn9ZXoX6r0jIIiCT_atubw">write the blog post I wish I'd found when I was googling</a>.</p>
<p><strong>What I mean by "cleaner" exports?</strong> For our particular use case, we had the following specific requirements for the final SVG icon output.</p>
<ul>
<li>All artwork needs to be flattened to expanded <code><path></code> elements—That means no <em>live</em> strokes or shape elements in the SVG markup. We draw the source artwork for our icons with live strokes that get flattened for production. We distribute our final icon set this way so that if the icons are scaled up all of the shapes in the icons scale proportionally and we don't end up with weird thin strokes in the icons.</li>
<li>No <code>clip-path</code> or <code>fill-rule</code> attributes—the fill rule attribute can cause weirdness in the way browsers display the icons after exported (more on that later). Figma does its best to apply these attributes based on the way the vector art was drawn, and boolean operations (union, subtract, etc.) are performed, but for our purposes, we prefer to eliminate the use of these attributes all together.</li>
<li>We need the parent SVG elements to use the <code>fill="currentColor"</code> attribute instead of individual elements having their own fill attributes hard coded. We do this so that the color of each icon can be changed with the CSS <code>fill</code> property.</li>
</ul>
<h2>How we did it</h2>
<p>After a fair bit of trial and error I discovered a handful of small but important steps we would need to take before exporting the icons that would ultimately get us close to the SVG markup we needed. Before I go any further, it might be helpful to show some examples of the Figma default SVG export markup vs. the final exported markup that we were after. All of these examples are based on this relatively simple example of a <em>ban</em> icon.</p><figure class="" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/more-control-over-figma-svg-exports/dc525451b6-1674922087/ban-icon-screenshot.png" alt="A black "Ban" icon on a white background" loading="lazy" decoding="async">
</figure>
<p>Here is the default Figma exported SVG markup for the <em>ban</em> icon after outlining all strokes, using the <em>union</em> boolean operation to merge the overlapping out lines, and flattening the merged artwork. As you can see there is quite a bit of cruft in there that, in our case, could be much simpler. There are unnecessary groups, <code><def></code> tags, and a <code><clipPath></code> element used by Figma to create the <em>Frame</em> that contains the artwork in the app.</p>
<pre class="hljs"><code data-language="html"><span class="hljs-tag"><<span class="hljs-name">svg</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"20"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"20"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 20 20"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"none"</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">g</span> <span class="hljs-attr">clip-path</span>=<span class="hljs-string">"url(#clip0_24_2)"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">path</span> <span class="hljs-attr">fill-rule</span>=<span class="hljs-string">"evenodd"</span> <span class="hljs-attr">clip-rule</span>=<span class="hljs-string">"evenodd"</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10ZM14.6687 16.4971C13.3548 17.4429 11.7425 18 10 18C5.58172 18 2 14.4183 2 10C2 8.25752 2.55708 6.64516 3.50286 5.33129L14.6687 16.4971ZM16.1348 15.1348L4.86515 3.86515C6.25462 2.70094 8.04545 2 10 2C14.4183 2 18 5.58172 18 10C18 11.9545 17.2991 13.7454 16.1348 15.1348Z"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"black"</span> /></span>
<span class="hljs-tag"></<span class="hljs-name">g</span>></span>
<span class="hljs-tag"><<span class="hljs-name">defs</span>></span>
<span class="hljs-tag"><<span class="hljs-name">clipPath</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"clip0_24_2"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">rect</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"20"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"20"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"white"</span> /></span>
<span class="hljs-tag"></<span class="hljs-name">clipPath</span>></span>
<span class="hljs-tag"></<span class="hljs-name">defs</span>></span>
<span class="hljs-tag"></<span class="hljs-name">svg</span>></span></code></pre>
<p>The first step to cleaning this up is making sure that the <em>Clip content</em> checkbox is uncheck on the icon's frame in Figma. To do this select the icon's frame in the left sidebar, or click on the frame title that's displayed in the upper left corner of the frame itself. Then look at the explorer in the right sidebar and you should see the <em>Clip content</em> checkbox. Make sure that is unchecked.</p><figure class="" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/more-control-over-figma-svg-exports/8266d91149-1674922230/figma-clip-contents.png" alt="Screenshot of Figma interface showing the controls for the "Clip contents" option" loading="lazy" decoding="async">
<figcaption>
Clip content option is highlighted in red </figcaption>
</figure>
<p>After clip content is uncheck, if we re-export the icon we get an SVG with much simpler markup. Now we're getting somewhere!</p>
<pre class="hljs"><code data-language="html"><span class="hljs-tag"><<span class="hljs-name">svg</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"20"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"20"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 20 20"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"none"</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">path</span> <span class="hljs-attr">fill-rule</span>=<span class="hljs-string">"evenodd"</span> <span class="hljs-attr">clip-rule</span>=<span class="hljs-string">"evenodd"</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10ZM14.6687 16.4971C13.3548 17.4429 11.7425 18 10 18C5.58172 18 2 14.4183 2 10C2 8.25752 2.55708 6.64516 3.50286 5.33129L14.6687 16.4971ZM16.1348 15.1348L4.86515 3.86515C6.25462 2.70094 8.04545 2 10 2C14.4183 2 18 5.58172 18 10C18 11.9545 17.2991 13.7454 16.1348 15.1348Z"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"black"</span>/></span>
<span class="hljs-tag"></<span class="hljs-name">svg</span>></span></code></pre>
<p>The last thing we needed to do in our case is get rid of those <code>fill-rule</code> and <code>clip-rule</code> attributes. The <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule">fill-rule attribute</a> defines the algorithm used to determine what is the <em>inside</em> part of an SVG shape, but in our testing sometimes the <code>fill-rule</code> attribute cause some inconsistencies in the way our icons displayed. Ultimately we want all of the fill-rules in our artwork to be "non-zero". I'm not 100% sure I understand what a <code>non-zero</code> value does, but here is <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule#nonzero">the explainer on the MDN docs</a> if you're interested. Luckily, we discovered two plugins that helped us convert the fill-rules created by Figma and modify the exported markup to get what we needed.</p>
<h2>Fill rule editor plugin</h2>
<p>The <a href="https://www.figma.com/community/plugin/771155994770327940/Fill-Rule-Editor">Fill Rule Editor</a> Figma plugin lets you edit the fill rules of the vector object before you export them. We used this plugin to convert/remove any <code>fill-rule</code> attributes that Figma was automatically setting to <code>evenodd</code>. Once we figured this part out, we were 90% of the way to the cleaner SVG export we were looking for.</p><figure class="hero" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/more-control-over-figma-svg-exports/b483c1e3d0-1674946703/fill-rule-editor-plugin.png" alt="The fill rule editor dialog with controls for converting SVG fill-rules" loading="lazy" decoding="async">
</figure>
<p>The fill-rule editor plugin let's you convert the fill rules of the vector artwork including the <em>inside</em> shapes. For example in the preceding screenshot, the bottom half-moon inside shape fill-rule needs to be converted/removed. This plugin allows us to convert all of the flattened vector objects to non-zero fill rules and remove the fill rules for the inside shapes by clicking on the gray strokes.</p>
<h2>SVG Export plugin</h2>
<p>The other plugin that helped us with the remainder of our SVG clean up and optimization is the <a href="https://www.figma.com/community/plugin/814345141907543603/SVG-Export">SVG Export plugin</a>. This plugin uses <a href="https://github.com/svg/svgo">SVGO</a> under the hood and has all the same options as the popular Node.js-based tool for optimizing SVGs.</p><figure class="hero" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/more-control-over-figma-svg-exports/5ac03baa01-1674945076/svg-export-screenshot.png" alt="The svg export plugin dialog showing the exported markup and a preview of the icon" loading="lazy" decoding="async">
</figure>
<p>The last step in our process was using the SVG Export plugin to remove any remaining <code>clip-rule</code> attributes and setting the <code>fill</code> attribute of each parent <code><svg></code> element to <code>currentColor</code>. This plugin is super handy and will definitely be part of my Figma toolkit moving forward.</p>
<h2>Wrapping up</h2>
<p>Here is the exported SVG markup we ultimately ended up with that met all of our criteria for our icon set.</p>
<pre class="hljs"><code data-language="html"><span class="hljs-tag"><<span class="hljs-name">svg</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"20"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"20"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"currentColor"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 20 20"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">path</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M20 10c0 5.523-4.477 10-10 10S0 15.523 0 10 4.477 0 10 0s10 4.477 10 10Zm-5.331 6.497L3.503 5.331a8 8 0 0 0 11.166 11.166Zm1.466-1.362a8 8 0 0 0-11.27-11.27l11.27 11.27Z"</span>/></span>
<span class="hljs-tag"></<span class="hljs-name">svg</span>></span></code></pre>
<p>We were even able to make improvements over our old Illustrator-baser export process. There was no way to set the fill to <code>currentColor</code> in Illustrator so we would have to add the <code>fill="currentColor"</code> attribute to the exported SVGs by hand. This way is much easier and more fool-proof.</p>
<p>The requirements we had for our exported SVGs won't necessarily be the same as your project, but in case you were looking for a way to truly flatten all of your SVGs to non-zero paths when you export them from Figma, now you know how!</p>Styling link underlines in 2023https://levimcg.com/blog/styling-link-underlines-like-its-20232023-01-13T17:50:00-07:002023-02-16T14:55:16-07:00Create better link underlines using a handful of newer CSS properties<p>I remember years ago reading this <a href="https://medium.design/crafting-link-underlines-on-medium-7c03a9274f9">article by Marcin Wichary</a> about how they created custom link underlines at Medium using a lot of CSS trickery. I thought of it again when I was styling the links on my site and discovered all of the great link underline related CSS properties that have been <a href="https://caniuse.com/mdn-css_properties_text-underline-offset">well-supported</a> since 2019! I'm not sure why, but I've totally missed all of these properties landing in browsers. Here's and example.</p>
<pre class="hljs"><code data-language="css"><span class="hljs-selector-tag">a</span> {
<span class="hljs-attribute">text-underline-offset</span>: .<span class="hljs-number">25rem</span>;
<span class="hljs-attribute">text-decoration-thickness</span>: .<span class="hljs-number">25rem</span>;
<span class="hljs-attribute">text-decoration-skip-ink</span>: none;
<span class="hljs-attribute">text-decoration-color</span>: <span class="hljs-number">#666</span>;
}</code></pre>
<p>With just a few lines of CSS you creates some really nice-looking link underlines. It's a subtle design detail, but it can make the typography on your site look and feel much more refined. And as a bonus your links will still get a normal old underline if someone's browser doesn't support one of these properties. That's progressive enhancement, baby.</p>
<p>You can read in more detail about what all these properties do on MDN.</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-underline-offset">text-underline-offset</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-thickness">text-decoration-thickness</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-skip-ink">text-decoration-skip-ink</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-color">text-decoration-color</a></li>
</ul>Triangulatehttps://levimcg.com/blog/triangulate2023-01-08T20:58:00-07:002023-02-16T14:55:21-07:00Blog posts as markers in time<p>There have been a handful of times in the last few months when I was able to pinpoint some past event in my life based on when I wrote something completely unrelated down in a blog post or notebook. Blogging, or just writing in general, is really valuable in lots of ways, but I’ve never really thought of it as creating markers in time you can use to triangulate other events.</p>
<p>Write something down, post it at a URL, and you’ve got a reference point you can access whenever you need to from any device connected to the internet. That’s pretty cool.</p>
<p>So, happy new year! Here is my first marker in 2023.</p>November 2022https://levimcg.com/blog/november-20222022-11-21T15:14:00-07:002023-01-27T19:02:52-07:00Trying to start a new habit of a monthly check-in post<p>I would like to start writing a monthly check-in post that I can aggregate into a feed for a new <a href="https://nownownow.com/about">now page</a>. I tried keeping a <em>Now</em> page a few years ago and it never really stuck, but I think treating is as a feed of short, focused posts might work a little better for me. I like the idea of having a unified feed where I can look back month-by-month on what I was working on, thinking about, etc.</p>
<p>Like a lot of other people, I have developed a pretty unhealthy (verging on toxic?) relationship with social media over the past 4-5 years. I started following a lot of journalists and political commentators during the lead up to the 2016 election and while I do think it is important to stay informed, I've learned that maybe Twitter isn't the most productive platform for me to do that on. I realize that I'm very privileged to be able to just tune out, but for the sake of my mental health, I need to find a more productive less rage-inducing way to engage.</p>
<h2>Personal websites are cool</h2>
<p>I've started to use my old Mastodon account that I created the last time I thought seriously about quitting Twitter in 2018. I'm really enjoying it over there. I <a href="/blog/the-web-feels-exciting-again/">wrote a post</a> about that experience a little while back.</p>
<p>The mass exodus from Twitter has also rekindled my excitement for writing and working on my own site. I was already finishing up a redesign when all of the stuff with Twitter started happening, but the events of the past few weeks really lit a fire under me to finish it up. I've got a few new features I'm excited to add over the next few months like the new <a href="/blog/tag:now/"><em>Now</em> page</a> I mention in the opening of this post.</p>
<h2>Rapid fire status updates</h2>
<ul>
<li>🎚️ <strong>Home studio</strong> — I built a very basic recording set up so that I can start making demos at home. It's really fun, and I actually finished writing two new songs that I almost have demos finished for. It's the first time I've written any new music in almost 10 years.</li>
<li>🎸 <strong>Playing music</strong> — I've also been playing music pretty regularly with some of my friends. We rehearse once a week on Thursday nights. It feels good to be jamming regularly with other real humans. Right now we're working on some tracks with our friend <a href="https://yukikawana.bandcamp.com/">Yuki</a> that she wants to record in the spring. I'm having a blast!</li>
<li>🎨 <strong>Redesign and rebuilt my website</strong> — I made some space in the hero to use some of my own original artwork. It will be fun to work up new collages as/when I feel like it.</li>
<li>⚽ <strong>The World Cup in Qatar</strong> started this past weekend. I just watched the US v. Wales match. Team USA is looking great and, in my opinion, were totally on track to win it until the gave away a sloppy penalty late in the game. I'm excited to watch some more matches over the coming month.</li>
<li>🦃 <strong>We're hosting Thanksgiving</strong> this week. There's plenty of stuff to do before everyone comes over to our place on Thursday. I love cooking and I'm actually looking forward to hosting this year.</li>
</ul>
<p>That's it until next time 👋.</p>The web feels exciting againhttps://levimcg.com/blog/the-web-feels-exciting-again2022-11-16T16:45:00-07:002023-01-27T18:42:48-07:00The recent Twitter mass exodus has me feeling nostalgic about some of my early experiences on the web<p>The recent and unexpectedly fast implosion of Twitter made me realize that it's just not a place that I want to spend much more of my time. It seems to me like there are a lot of other folks that feel the same. I have been spending more time over on <a href="https://mastodon.social/@levimcg">Mastodon</a> and keep seeing some version of the same comment over and over—"<em>Wow, this feels like the old, pre-corporate social media Web</em>". I agree.</p>
<p>I built my first website in 2007. I was about to be laid off from my job at the time and needed an online portfolio fast so I could put it on my resume. I authored all of the static HTML and CSS by hand. After a couple weeks of work I ended up with a folder full of files that I dragged from my hard drive and dropped at the root of my new hosting account with a plain old FTP client. And that was it, I had a website! What a cool feeling.</p>
<p>I've had a lot of those same nostalgic feelings the past couple of weeks. It's got me fired up to put more energy into my personal site where I own all the content I create. For now, this is how I'm thinking about my relationship with the web and social media:</p>
<ol>
<li>My personal site as home base—If I want to share a thought, maybe write a blog post instead</li>
<li>Mastodon for sharing and interaction with humans</li>
<li>RSS for keeping up with with other folks that are writing on their own sites</li>
</ol>
<p>Honestly, I'm addicted to Twitter and it's probably going to take me a while to give it up completely, but I have spent considerably less time there in the last couple of weeks and I plan to keep that up. If you feel the same maybe grab my RSS feed and/or find me on <a href="https://mastodon.social/@levimcg">Mastodon</a>. ✌️</p>Static site generator fatiguehttps://levimcg.com/blog/static-site-generator-fatigue2022-11-13T17:20:00-07:002023-01-08T08:04:10-07:00Thinking about moving my site to a new platform with an actual CMS<p>I've been running my site on static site generators (SSG) for a little over eight years now. First it was on <a href="http://jekyllrb.com/">Jekyll</a> and later on <a href="https://11ty.dev/">Eleventy</a>, which I'm still using today. I really enjoy the simplicity and performance benefits of static sites and building with SSGs is a great developer experience, in my opinion. However, lately I'm starting to feel a handful pain points that have me thinking about moving my site to different platform for content management.</p>
<ul>
<li><strong>VSCode overload</strong> — I would like writing a blog post to be a totally separate experience from writing code. I know writing Markdown isn't the same as writing code, but it's in the same environment where I do a lot of my work during the day. I'd sort of prefer it not to be.</li>
<li><strong>Posting is a desktop/laptop only activity</strong>. I would love to be able to fire off posts from my phone while I'm sitting on the couch or in the car on a road trip. With a more traditional CMS it's generally easier to write and publish post on a mobile device since the admin UI is accessible in a web browser and most likely somewhat optimized for smaller screens.</li>
<li><strong>Static is great, until it isn't</strong>. I don't generally need to do much server-side stuff on my site, but when I do think about doing anything remotely dynamic, the amount of set up and cobbling services together required sometimes puts me off (see note about Webmentions below).</li>
</ul>
<h2>PHP is pretty good, actually.</h2>
<p>I kind of miss having a server available by default. I was reading Matthias Ott's <a href="https://matthiasott.com/articles/into-the-personal-website-verse">great article</a> on personal websites this morning and in it they talk a bit about <a href="https://indieweb.org/webmention">Webmentions</a> and ways to go about adding them to a personal site. It got me thinking that I would love to add that capability to my site, but being a static, it would involve quite a bit more complexity. I'm not sure I have the appetite for that at the moment. With a PHP running on a server, it would be much more straightforward. Matthias even points out that sever popular CMSs have plugins that make it a pretty simple process.</p>
<h2>Back to a CMS?</h2>
<p><a href="https://getkirby.com/">Kirby</a> looks really great to me. It seems like there is a really healthy community built up around it and <a href="https://getkirby.com/new-company">the Kirby team</a> is doing a lot of good work to make it a long-term, sustainable product. I built a site for a client years ago using Kirby and it was a really wonderful experience. In the time since then it looks to me like it has a come a long way and has a ton of really great features out of the box. It allows you to have a folder full of flat files and write in markdown if you want to keep it super simple, but also comes with a really nice and customizable UI (<em>The Panel</em>) for managing and writing content. To me that sounds like the best of both worlds.</p>
<p>I think I'll sit with it for a while before I make a decision. I should probably just put all of the energy that it would take to move to a new platform into writing more posts anyway. 😅</p>A small, but significant gesturehttps://levimcg.com/blog/a-small-but-significant-gesture2022-11-04T16:40:00-06:002023-01-27T18:43:09-07:00It nice when someone takes the time to send you a link<p>My friend text me a link the other day to <a href="https://open.spotify.com/track/0cciBj4236w1xByzZvn92D?si=WozsVYWKSA-dgYjPb9wBJQ">some music</a> they thought I should check out. We like a few of the same bands, but I really have no idea what all types of music they like.</p>
<p>Turns out the link they sent was pretty good stuff. Not necessarily something I would seek out, but I ended up listening to most of the record. It's definitely something I'd put on in the background while I'm cooking dinner.</p>
<p>I've been trying to do my best to at least try and listen, read, or watch when someone in my life sends me a link to something they like or found interesting. It's a tiny gesture, but it means they thought about me and they took time out of their day to commit to a potential conversation. If I stop and think about it, that's really cool and makes me feel good.</p>My notes on Eleventy WebC componentshttps://levimcg.com/blog/my-notes-on-eleventy-webc-components2022-10-28T16:35:00-06:002023-02-16T14:55:33-07:00WebC is a compiler for single file Web Components that brings first-class component model to Eleventy!<p>The WebC plugin brings a component model similar to frameworks like Vue and Svelte to <a href="https://www.11ty.dev/">Eleventy</a>. It allows you to create HTML-only components (basically includes) all the way up to interactive Web Components with shadow DOM and all the benefits it offers.</p>
<p>I generally get by just fine without components in 11ty. Nunjucks macros are fine, but they never really felt like true components to me. They also are super clunky to work with, IMO.</p>
<p><a href="https://www.11ty.dev/docs/shortcodes/">Shortcodes</a> are great, but they also feel just a small step away from first-class components. WebC components bring all of the things you would expect from a component model—the <a href="https://www.11ty.dev/docs/languages/webc/#props-(properties)">ability to pass in "props"</a>, <a href="https://www.11ty.dev/docs/languages/webc/#slots">slots</a> for placing content, <a href="https://www.11ty.dev/docs/languages/webc/#webcscoped">CSS scoping</a>—to Eleventy.</p>
<h2>Killer features</h2>
<p>Here are some of my initial impressions that got me excited about the Eleventy WebC plugin. I'm sure I'll need to add to this list as I get a bit more experience under my belt.</p>
<h3>Slots and composition</h3>
<p>Slots in WebC components do a bit more than the standard <code><slot></code> element used in Web Components. They have the ability to work just like Web Component/custom element slots, but they can also function similar to something like the built-in <code>props.children</code> in React.</p>
<p>I see a lot of potential here. You could do some very cool things when combined with layout chaining like create your own template/layout inheritance without having to use Nunjucks layout inheritance, or create compound components like a site header:</p>
<pre class="hljs"><code data-language="html"><span class="hljs-tag"><<span class="hljs-name">site-header</span>></span>
<span class="hljs-tag"><<span class="hljs-name">site-header-logo</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"logo"</span>></span><span class="hljs-tag"></<span class="hljs-name">site-header-logo</span>></span>
<span class="hljs-tag"><<span class="hljs-name">site-header-nav</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"nav"</span>></span><span class="hljs-tag"></<span class="hljs-name">site-header-nav</span>></span>
<span class="hljs-tag"></<span class="hljs-name">site-header</span>></span></code></pre>
<h3>Render functions</h3>
<p><a href="https://www.11ty.dev/docs/languages/webc/#webctyperender-(javascript-render-functions)">Render functions</a> are pretty cool. To me, they seem a lot like shortcodes except you don't have to register them in your <code>.eleventy.js</code> (or whatever yours is called) config file. Instead, you can keep them alongside your other components or includes as a HTML/.webc file. Here's and example of a simple list component using a render function.</p>
<pre class="hljs"><code data-language="html"><span class="hljs-tag"><<span class="hljs-name">h2</span>></span><span class="hljs-tag"><<span class="hljs-name">slot</span>></span><span class="hljs-tag"></<span class="hljs-name">slot</span>></span><span class="hljs-tag"></<span class="hljs-name">h2</span>></span>
<span class="hljs-tag"><<span class="hljs-name">ul</span>></span>
<span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">webc:type</span>=<span class="hljs-string">"render"</span> <span class="hljs-attr">webc:is</span>=<span class="hljs-string">"template"</span>></span><span class="javascript">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">renderItems</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">const</span> numbers = [<span class="hljs-string">'One'</span>, <span class="hljs-string">'Two'</span>, <span class="hljs-string">'Three'</span>]
<span class="hljs-keyword">return</span> numbers.map(<span class="hljs-function"><span class="hljs-params">number</span> =></span> <span class="hljs-string">`<li><span class="hljs-subst">${number}</span></li>`</span>).join(<span class="hljs-string">''</span>)
}
</span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
<span class="hljs-tag"></<span class="hljs-name">ul</span>></span></code></pre>
<h2>Concepts I need a bit more time with</h2>
<p>It usually takes me a while to really wrap my head around new concepts like this. I'm sure I'll get there once I have the chance to work through some real-world use cases, but are some things that aren't quite clicking for me yet.</p>
<h3>Gimme some sugar</h3>
<p>I LOVE the idea of 11ty single file components, but it would rule to have some syntactic sugar for flow control similar to Vue, Svelte, etc. Also a mustache-like syntax for expressions that render data, variables, etc would be very handy. I could just be missing something here though.</p>
<p>Maybe a lot of folks are going to prefer what feels, in my opinion, more like JSX with render functions and the <code><script></code> tag plus <code>webc:type="render"</code> approach shown above. I dunno? The good news is that it looks like flow control features <a href="https://github.com/11ty/webc/issues/28">are being considered</a> for the future.</p>
<h3>Data with props and attributes</h3>
<p>In this example I'm trying to pass some front matter data from a template to a <code>@heading</code> prop on a component and render that data in the component. <code>someData</code> ends up passed as a string instead of being rendered as the value of the <code>someData</code> variable from the front matter. I'm almost sure I'm missing something here as it relates to <code>@propName</code> vs. dynamic attributes (ex. <code>:attributeName</code>).</p>
<h4>Test case</h4>
<p>I put the test repo I've been working out of with these <a href="https://github.com/levimcg/11ty-webc-sandbox">examples up on GitHub</a>.</p>
<p>Given this component:</p>
<pre class="hljs"><code data-language="html"><span class="hljs-comment"><!-- list-component.webc --></span>
<span class="hljs-tag"><<span class="hljs-name">h2</span>></span>
<span class="hljs-tag"><<span class="hljs-name">slot</span>></span><span class="hljs-tag"></<span class="hljs-name">slot</span>></span>
<span class="hljs-tag"></<span class="hljs-name">h2</span>></span>
<span class="hljs-tag"><<span class="hljs-name">ul</span>></span>
<span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">webc:type</span>=<span class="hljs-string">"render"</span> <span class="hljs-attr">webc:is</span>=<span class="hljs-string">"template"</span>></span><span class="javascript">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">renderItems</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">const</span> numbers = [<span class="hljs-string">'One'</span>, <span class="hljs-string">'Two'</span>, <span class="hljs-string">'Three'</span>]
<span class="hljs-keyword">return</span> numbers.map(<span class="hljs-function"><span class="hljs-params">number</span> =></span> <span class="hljs-string">`<li><span class="hljs-subst">${number}</span></li>`</span>).join(<span class="hljs-string">''</span>)
}
</span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
<span class="hljs-tag"></<span class="hljs-name">ul</span>></span></code></pre>
<p>And that component used in a template like this everything works great. "Eleventy front matter data" is rendered in the default <code><slot></code> as a child of the <code><h2></code>.</p>
<pre class="hljs"><code data-language="html">---
layout: base.webc
someData: "Eleventy front matter data"
---
<span class="hljs-comment"><!-- This works in a page-level template --></span>
<span class="hljs-tag"><<span class="hljs-name">list-component</span>></span>
<span class="hljs-tag"><<span class="hljs-name">template</span> @<span class="hljs-attr">html</span>=<span class="hljs-string">"someData"</span> <span class="hljs-attr">webc:nokeep</span>></span><span class="hljs-tag"></<span class="hljs-name">template</span>></span>
<span class="hljs-tag"></<span class="hljs-name">list-component</span>></span></code></pre>
<p>But given this WebC component:</p>
<pre class="hljs"><code data-language="html"><span class="hljs-comment"><!-- list-component.webc --></span>
<span class="hljs-tag"><<span class="hljs-name">h2</span> @<span class="hljs-attr">html</span>=<span class="hljs-string">"this.heading"</span>></span><span class="hljs-tag"></<span class="hljs-name">h2</span>></span>
<span class="hljs-tag"><<span class="hljs-name">ul</span>></span>
<span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">webc:type</span>=<span class="hljs-string">"render"</span> <span class="hljs-attr">webc:is</span>=<span class="hljs-string">"template"</span>></span><span class="javascript">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">renderItems</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">const</span> numbers = [<span class="hljs-string">'One'</span>, <span class="hljs-string">'Two'</span>, <span class="hljs-string">'Three'</span>]
<span class="hljs-keyword">return</span> numbers.map(<span class="hljs-function"><span class="hljs-params">number</span> =></span> <span class="hljs-string">`<li><span class="hljs-subst">${number}</span></li>`</span>).join(<span class="hljs-string">''</span>)
}
</span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
<span class="hljs-tag"></<span class="hljs-name">ul</span>></span></code></pre>
<p>And that component used in a layout like this:</p>
<pre class="hljs"><code data-language="html">---
layout: base.webc
someData: "Eleventy front matter data"
---
<span class="hljs-comment"><!--
This DOES not work in a page-level template.
The heading prop gets render as the string "someData".
--></span>
<span class="hljs-tag"><<span class="hljs-name">list-component</span> @<span class="hljs-attr">heading</span>=<span class="hljs-string">"someData"</span>></span><span class="hljs-tag"></<span class="hljs-name">list-component</span>></span></code></pre>
<p>The <code>@heading</code> prop is rendered as the string "someData" instead of the value of the front matter variable passed in as the <code>@heading</code> prop on the <code><list-component></code> element.</p>
<p>I <em>think</em> <a href="https://www.11ty.dev/docs/languages/webc/#front-matter">this bit from the docs</a> on the 11ty site may have something to do with this:</p>
<blockquote>
<p>Notable note: front matter (per standard Eleventy conventions) is supported in page-level templates only (.webc files in your input directory) and not in components...</p>
</blockquote>
<h3>Render function all the things?</h3>
<p>Maybe everything in the WebC component just needs to be a render function if you want to pass dynamic props in from external data sources? This may be super obvious to some folks, but I feel like I might be missing something here.</p>
<p>I guess you could go this route, but I'm going go ahead and say I'd rather wait and see on the potential enhancements to templating/flow control syntax mentioned above for now.</p>
<h3>Picking the "right" kind of component</h3>
<p>I need more experience to figure out the boundary between HTML only and a full-on Web Component (CustomElement + shadowDOM). As soon as you put a script or style tag in a .webc file it will render the custom element but you can use the <code>webc:nokeep</code> attribute you get rid of the custom element wrapper/parent.</p>
<p>On the other hand you can register full-on interactive and encapsulated Web Components (ex. <code>CustomElements.define(...)</code>) from single file WebC components, which is very cool. I think mixing interactive Web Components/CustomElements with render functions, slots, and HTML-only components has tons of potential to create a great workflow for building stuff.</p>
<h2>Wrapping up for now</h2>
<p>People much smarter than me are already writing posts and tutorials on WebC components. Here are some resources I found super helpful in getting started with WebC components in Eleventy.</p>
<h3>Resources</h3>
<ul>
<li><a href="https://11ty.rocks/posts/introduction-webc/">Introduction to WebC</a>— High-level intro on 11ty Rocks!</li>
<li><a href="https://11ty.rocks/posts/understanding-webc-features-and-concepts/">Understanding WebC Features and Concepts</a> — A great primer on 11ty Rocks!</li>
<li><a href="https://www.11ty.dev/docs/languages/webc/">11ty docs on WebC Templates</a></li>
<li><a href="https://11ty.webc.fun/">11ty & WebC</a> — a great set of quick tips and tutorials</li>
</ul>
<p>There is so much potential here and I look forward to seeing how webC progresses over the next few months. I'm planning to try and keep this post updated as have more time to dig in and new WebC features roll out.</p>Twenty-five more yearshttps://levimcg.com/blog/twenty-five-more-years2022-10-20T00:00:00-06:002023-01-27T18:43:44-07:00Swapping out some parts on a guitar i've had for years made me think about some things<p>I Gave my old bass a makeover today—a quick list of what I upgraded. New pickup (Seymour Duncan Quarter Pound), Fender Hight Mass bridge, new pickguard, chrome knobs, strap buttons</p>
<p>I bought this guitar in 1997 with a few paychecks from my first real job at Taco Bell in high school. I put a lot of miles on this thing in basements/garages/VFWs/coffee shops/bars over the years. It still rips.</p>
<p>Twenty five years ago Musician's Friend didn't have a website. It was a printed catalog made of paper with pictures, specs, and prices of gear that came to your house once a month. I gave my Mom the cash and then called a real person on the phone to use her credit card to place my order.</p>Wrap element snippethttps://levimcg.com/blog/wrap-element-snippet2022-06-22T16:35:00-06:002023-01-27T18:43:52-07:00A small JavaScript snippet that wraps an existing HTML element with a new element<p>I've found myself needing to do this on a couple of projects lately so it seemed like a good idea to write it down here as a snippet to reference later.</p>
<p>This sort of a vanilla replacement for <a href="https://api.jquery.com/wrap/">jQuery's <code>.wrap()</code> method</a></p>
<pre class="hljs"><code data-language="js"><span class="hljs-comment">/**
* Helper function to wrap one element with a new element
*
* <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> <span class="hljs-variable">wrapWith</span></span> - the name the element to create as the wrapper
* <span class="hljs-doctag">@param <span class="hljs-type">{Element}</span> <span class="hljs-variable">toWrap</span></span> - The existing element to wrap
* <span class="hljs-doctag">@param <span class="hljs-type">{Object}</span> <span class="hljs-variable">attributes</span></span> - An object of key value pairs used to create HTML attributes
*/</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">wrap</span>(<span class="hljs-params">wrapWith, toWrap, attributes</span>) </span>{
<span class="hljs-keyword">const</span> wrapperElement = <span class="hljs-built_in">document</span>.createElement(wrapWith);
<span class="hljs-keyword">if</span> (attributes) {
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> attr <span class="hljs-keyword">in</span> attributes) {
wrapperElement.setAttribute(attr, attributes[attr])
}
}
toWrap.parentNode.insertBefore(wrapperElement, toWrap)
wrapperElement.appendChild(toWrap)
}</code></pre>The right path at the wrong timehttps://levimcg.com/blog/the-right-path-at-the-wrong-time2022-06-19T01:00:00-06:002023-01-27T18:44:01-07:00The right path isn't always clear, sometimes you have to go backwards to go forward<blockquote>
<p>It can take a while to realize the path you were on was the right one at the wrong time.</p>
</blockquote>
<p>I love everything Dan Sinker writes and <a href="https://www.esquire.com/sports/a39008053/cm-punk-aew-profile/">this profile of wrestler CM Punk</a> is no exception. This bit right here about understanding how the right path takes time got me.</p>Spring 2022 garden planshttps://levimcg.com/blog/spring-2022-garden-plans2022-05-17T16:35:00-06:002023-02-16T15:08:54-07:00I got a good start on my 2022 backyard garden<p>I'm glad the weather is finally starting to turn and it really feels like Spring. We've had a run of warm days and nights this last week and all my transplants and seedlings are starting to take off. I love all the potential of this time of year.</p>
<p>Here's what I've got that's either already planted or ready to.</p>
<ul>
<li>10 pepper plants</li>
<li>13 tomato</li>
<li>3 kale</li>
<li>2 rows of beets (~ 8-12 beets)</li>
<li>3 swiss chard</li>
<li>4 cilantro</li>
<li>3 thyme</li>
<li>2 oregano</li>
<li>4 basil</li>
<li>2 dill</li>
<li>10 lettuce</li>
<li>5 arugula</li>
<li>3 purple broccoli</li>
<li>3 marigold</li>
<li>2 nasturtiums</li>
<li>4 fennel</li>
<li>lots of radish</li>
</ul>Corita Kent on makinghttps://levimcg.com/blog/corita-kent-on-making2022-02-18T16:30:00-07:002023-02-16T14:56:12-07:00Wise words from Corita Kent on what it means to make stuff<p>I’m consistently inspired by the work of <a href="https://www.corita.org/">Corita Kent</a>. I love this quote. We are living through a slow-moving, totally avoidable tragedy. Any constructive acts we can muster are defiance. </p>
<blockquote>
<p>Doing and making are acts of hope.</p>
</blockquote>
<p>The <a href="https://instagram.com/coritaartcenter">Corita Art Center</a> Instagram account is definitely worth a follow.</p>Designers and redesignershttps://levimcg.com/blog/designers-and-redesigners2022-02-11T16:30:00-07:002023-02-16T14:56:19-07:00Some of us are comfortable creating something from nothing others excel at revising<p>I read <a href="https://danmall.com/articles/its-easier-to-revise-than-create/">this article by Dan Mall</a> last week that really nails why it's important to not dwell in the creation phase of a project because it's easier to revise and improve once you get the initial ideas on paper.</p>
<p>There are designers that are comfortable creating something from nothing. Others excel at revising or redesigning. I think both are important and I often wonder which mode I should focus energy on.</p>Howard Finster and Paradise Gardenhttps://levimcg.com/blog/howard-finster-and-paradise-garden2021-03-26T13:00:00-06:002023-02-16T14:56:25-07:00A quick trip to Chattanooga with an unexpected detour at this fascinating place in rural Georgia<p>We traveled last week for first time this year. It was a quick trip down to Chattanooga to spend some time with my partner’s sister, brother-in-law, and our niece. We had a great time out hiking in the mountains, and eating some of the <a href="https://pigeonmtcountrystore.webs.com/">best chicken wings</a> I’ve ever had.</p>
<p>Before heading back to Chattanooga, my brother-in-law suggested we stop at <a href="https://paradisegardenfoundation.org/">Howard Finster’s Paradise Garden</a> in nearby Summerville, Georgia. Finster was a fascinating and prolific artist who found some success in the art world later in life after claiming to receive a message from God in 1976 to create 5000 "sacred works" at the age of 59. He ended up going on to produce nearly 47,000 works of art before his death in 2001!</p>
<p>I happened to catch five minutes of a documentary that they play on a loop during the tour of the property. In the documentary Kieth Haring visited the property late in his life after developing a relationship with Finster. There is even a piece of Haring's own work on the property today (pictured below), but I'm not exactly sure how that came to be. REM also shot one of their music videos here in the too!</p>
<p>Here are some random photos I shot on my phone during our visit. I'm horrible at taking photos. I should remember to try and do a better job snapping photos for posts like this in the future. This place is definitely worth a detour if you find yourself traveling through north Georgia!</p>The lowest highshttps://levimcg.com/blog/the-lowest-highs2021-03-11T21:00:00-07:002023-02-16T14:56:30-07:00My second pandemic birthday is tomorrow and I wasn't ready for it<p>I turn 40 tomorrow and pandemic life continues. My immediate family and I all fall within the range of people who have no idea when they'll be able to get vaccinated. I'm doing my best to show up to work every day and pretend to be productive. It's all exhausting.</p>
<p>My partner and I both turned 40 during the pandemic. We did our best to celebrate our 15th wedding anniversary too. It was July 31, 2020, so it happened to fall right at the start of the major second wave of COVID-19 infections here in the US. I don't remember that day or what we did at all. Probably mostly the same thing we did July 30. Nothing, really.</p>
<p>My son had his 13th birthday last March. Another big milestone come and gone without being able to celebrate. No sleepovers with his friends, or even a meal out at his favorite restaurant. He turns 14 later this month. It will be the second pandemic birthday of his teenage years.</p>
<p>We've experienced tragedy too. Not really COVID-related tragedy, but the normal everyday kind of tragedy that everyone experiences in "normal" times. We lost a beloved family pet Rocky in a horrific accident. I held him as in my arms as he died, and it's a trauma that will be with me forever. We've had to watch from far away as close friends and family members suffered through extreme and deadly illnesses. No hospital or home visits because of the other extreme and deadly illness floating in the air all around us.</p>
<p>What I keep thinking about is how compressed the difference between highs and lows are through all of this. The highest highs, like birthdays and anniversaries, don't really seem to feel that different from everyday stuff, but the lows, they seem to last forever and can be debilitating. I hope you're all hanging in there. ✌️</p>Visually hidden snippethttps://levimcg.com/blog/visually-hidden-snippet2021-03-02T16:30:00-07:002023-02-16T14:56:39-07:00I constantly find myself copying and pasting this snippet into projects<p>There are several ways to hide content using CSS. For example using <code>display: none</code>, or <code>visibility: hidden</code>. Each way of hiding elements is useful in different situations, but this snippet is specifically for visually hiding elements while keeping the accessible to assistive technologies like screen readers.</p>
<pre class="hljs"><code data-language="css"><span class="hljs-selector-class">.visually-hidden</span><span class="hljs-selector-pseudo">:not(</span><span class="hljs-selector-pseudo">:focus)</span><span class="hljs-selector-pseudo">:not(</span><span class="hljs-selector-pseudo">:active)</span> {
<span class="hljs-attribute">clip</span>: <span class="hljs-built_in">rect</span>(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span>);
<span class="hljs-attribute">clip-path</span>: <span class="hljs-built_in">inset</span>(<span class="hljs-number">50%</span>);
<span class="hljs-attribute">height</span>: <span class="hljs-number">1px</span>;
<span class="hljs-attribute">overflow</span>: hidden;
<span class="hljs-attribute">position</span>: absolute;
<span class="hljs-attribute">white-space</span>: nowrap;
<span class="hljs-attribute">width</span>: <span class="hljs-number">1px</span>;
}</code></pre>
<p><a href="https://www.scottohara.me/">Scott O'Hara</a> <a href="https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html">wrote about this in 2017</a> including a deep dive into all the different ways you can hide stuff and when to use which method. I'd definitely recommend checking out that article too.</p>Smaller Better Thingshttps://levimcg.com/blog/smaller-better-things2021-03-01T10:00:00-07:002023-02-16T14:56:43-07:00This mini documentary has me thinking about doing less, but better work<p>I watched <a href="https://twitter.com/FoodInsider/status/1364454054332612608?s=20">this mini documentary</a> about a French butter maker the other day that makes high-end butter with old techniques that very few places use anymore.</p>
<p>I really like this quote from the owner where he explains that he could make a lot more butter (and more money) if he using newer more automated techniques, but instead he prefers to focus on quality and do less—to make smaller things.</p>
<blockquote>
<p>Me, I’m a little good man and I make little things. </p>
</blockquote>
<p>I think about this concept a lot these days. The larger a project gets the quicker I find myself losing interest. It’s not that I fear hard work it’s that I just want to do smaller, better work.</p>
<p>Anyway, watch this video about making butter. It appears that it only exists as this Tweet. I couldn't find it on Insider site, so I've embedded it here. Enjoy!</p>
<blockquote class="twitter-tweet" data-dnt="true" data-theme="light"><p lang="en" dir="ltr">Brittany is a region in France that's known for their butter 🧈 <a href="https://t.co/rG6clAFNQa">pic.twitter.com/rG6clAFNQa</a></p>— Food Insider (@FoodInsider) <a href="https://twitter.com/FoodInsider/status/1364454054332612608?ref_src=twsrc%5Etfw">February 24, 2021</a></blockquote>Copyright Snippethttps://levimcg.com/blog/copyright-snippet2020-12-15T17:00:00-07:002023-02-16T14:56:47-07:00A small JavaScript code snippet that helps keep your footer copyright up to date. (with bonus CustomElement)<p>It's that time of year again, so I thought I'd do a quick post on the simple bit of JavaScript I use to keep the copyright date current in the static sites I build. Add the <code>hidden</code> attribute to your container element markup so that if JS fails for some reason it won't display.</p>
<pre class="hljs"><code data-language="js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CopyrightElement</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
connectedCallback() {
<span class="hljs-keyword">this</span>.innerHTML = <span class="hljs-string">`&copy; <span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getFullYear()}</span>`</span>;
}
}
<span class="hljs-keyword">if</span> (<span class="hljs-string">'customElements'</span> <span class="hljs-keyword">in</span> <span class="hljs-built_in">window</span>) {
<span class="hljs-built_in">window</span>.customElements.define(<span class="hljs-string">'copyright-element'</span>, CopyrightElement);
}</code></pre>
<p>As a bonus, here's a tiny custom element I made that does the same thing. This way you can just wrap your static text in a <code><copyright-element></code> tag and as long as the browser is capable and you scripts loaded, you get an upgraded, up-to-date footer copyright date.</p>
<pre class="hljs"><code data-language="js">(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'DOMContentLoaded'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-comment">// Update this to whatever selector you want.</span>
<span class="hljs-keyword">const</span> element = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'[data-copyright]'</span>);
<span class="hljs-keyword">if</span> (element) {
element.removeAttribute(<span class="hljs-string">'hidden'</span>);
element.innerHTML = <span class="hljs-string">`&copy; <span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getFullYear()}</span>`</span>;
}
});
})();</code></pre>We Are The Songwritershttps://levimcg.com/blog/we-are-the-songwriters2020-10-24T16:20:00-06:002023-02-16T14:56:53-07:00I recently discovered Nick Cave's wonderful newsletter called The Red Hand Files<p>The urge to create can feel like a struggle sometimes. Throw in feelings of isolation and hopelessness brought on by living through a global pandemic and trying to make something to put out into the world can seem impossible some days.</p>
<p>I recently discovered Nick Cave's The Red Hand Files via <a href="https://austinkleon.com/newsletter/">Austin Kleon's newsletter</a>. I spent a few hours reading through a dozen or so posts. It gave me a real rush of emotion that I think I needed. The questions submitted through the site and his answers to them are heartbreaking, hilarious, and inspiring.</p>
<p>I found the answer to this one called "What do you do when the lyrics just aren’t coming?" very comforting in a lot of ways. This last line at the end put a lump in my throat.</p>
<blockquote>
<p>We are all of these things — we are the songwriters.</p>
</blockquote>
<p>If you do anything creative for fun, or for a living—and I tend to think lots of folks fall into at least one of those categories—I'd highly recommend <a href="https://www.theredhandfiles.com/the-lyrics-just-arent-coming/">giving it a read</a>.</p>Working and Mourninghttps://levimcg.com/blog/working-and-mourning2020-04-13T16:20:00-06:002023-02-16T14:56:57-07:00Trying to stay motivated to get work done during a worldwide pandemic is kinda hard.<p>I’m not religious, but when I looked out my front window yesterday morning at the church across the street from my house I felt my heart sink a little when I saw a completely empty parking lot. It was Easter morning.</p>
<p>We're all mourning something right now. Losing the ability to see friends, of being able to shake a hand, to give a hug. The loss of freedom to do whatever we want. I'm really lucky that this virus hasn't directly touched my life, other than the being stuck in my house for weeks on end. None of my family or friends have contracted it and the town I live in has seen very few confirmed cases. And no deaths, yet.</p>
<p>It might seem weird when we look back one day at how we even cared about trying to keep building web pages when the world was such a mess.</p>Accordion Container Web Componenthttps://levimcg.com/blog/accordion-container-web-component2020-01-18T16:20:00-07:002023-02-16T14:57:04-07:00A Custom Element wrapper that turns a set of headings into an accessible accordion widget<p>One of the things that interests me about Web Components is that they make it possible to build components that can be progressively enhanced. <a href="../progressive-enhancement-doesnt-have-to-be-hard/">I recently wrote about</a> how I've been making progressive enhancement more of a priority in my work, and the idea putting that into practice with Web Components has me feeling pretty excited.</p>
<p>After read <a href="https://daverupert.com/2019/12/why-details-is-not-an-accordion/">this post about by Dave Rupert</a> about his experiment building an accordion Web Component out of the native <code><details></code> element, it got me thinking about how one might go about building an accessible accordion as a Web Component.</p>
<p>A while back <a href="../focus-trapping-web-component/">I wrote a post</a> about the idea of using Web Components as high-level wrappers that handle things like interactivity and accessibility requirements, but that aren't concerned with rendering the component's markup/DOM. Web component features like the <code><slot></code> element make this possible and are built to make progressive enhancement easier.</p>
<p>When I think about what an ideal <code>accordion</code> HTML element might look like, I would expect to see something like this.</p>
<pre class="hljs"><code data-language="html"><span class="hljs-tag"><<span class="hljs-name">accordion</span>></span>
<span class="hljs-tag"><<span class="hljs-name">accordion-panel</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"First summary title for toggle"</span>></span>
Panel content...
<span class="hljs-tag"></<span class="hljs-name">accordion-panel</span>></span>
<span class="hljs-tag"><<span class="hljs-name">accordion-panel</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Second summary title for toggle"</span>></span>
Panel content...
<span class="hljs-tag"></<span class="hljs-name">accordion-panel</span>></span>
<span class="hljs-tag"><<span class="hljs-name">accordion-panel</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Third summary title for toggle"</span>></span>
Panel content...
<span class="hljs-tag"></<span class="hljs-name">accordion-panel</span>></span>
<span class="hljs-tag"></<span class="hljs-name">accordion</span>></span></code></pre>
<p>In fact, this was the first approach I took when I started to build an accordion component. But, as Dave pointed out in his post, it's important to get heading semantics right in an accordion. The ARIA authoring practices guide has pretty clear requirements about how all toggles should be wrapped in elements that have a <em>heading</em> role. Following the approach above could be difficult since I would need to make sure all the appropriate roles (button, heading, etc.) were added manually since custom elements don't have any inherent roles as far as the browser is concerned. I decided to go a different route, especially after reading Léone Watson's <a href="https://www.24a11y.com/2019/web-components-and-the-aom/">great article</a> about some of the limitations around Web Component accessibility.</p>
<h2>Container components</h2>
<p>While doing more research <a href="https://github.com/github/tab-container-element/">I came across this</a> <code><tab-container></code> element by the folks at GitHub. As I talked about with my <code><focus-trapper></code> example earlier. This kind of <em>wrapper</em> approach makes a lot of sense to me. I kind of like to think of this approach as regular old vanilla JavaScript widgets or plugins, except they can be used declaratively in your HTML. So instead of initializing like a normal JS plugin:</p>
<pre class="hljs"><code data-language="js"><span class="hljs-keyword">const</span> myAccordion = <span class="hljs-keyword">new</span> Accordion({
<span class="hljs-comment">// Some options or whatever</span>
});</code></pre>
<p>You could maybe just put it in a <code><script></code> tag and then use it in your HTML.</p>
<pre class="hljs"><code data-language="js"><script type=<span class="hljs-string">"module"</span> src=<span class="hljs-string">"path/to/component.js"</span>>
<span class="xml"><span class="hljs-tag"><<span class="hljs-name">accordion-container</span>></span>
<span class="hljs-comment"><!-- Put your HTML here and the component wrapper will take care of the rest--></span>
<span class="hljs-tag"></<span class="hljs-name">accordion-container</span>></span></span></code></pre>
<h2>My accordion-container element</h2>
<p>This is the approach I took for the <code><accordion-container></code> Custom Element I build over my holiday break. It fully follows the ARIA Authoring Practices <a href="https://w3c.github.io/aria-practices/#accordion">recommendations for accordions</a> and is progressively enhanced so if JS isn't available or fails for some reason, you still will see all of the accordion content. <a href="https://github.com/levimcg/accordion-container-element">The source is available on GitHub</a> where you can find documentation about how to use it, and here's a quick CodePen demo.</p>
<p class="codepen" data-height="431" data-theme-id="13463" data-default-tab="result" data-user="levimcg" data-slug-hash="ZEYapRY" style="height: 431px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Accordion container element demo">
<span>See the Pen <a href="https://codepen.io/levimcg/pen/ZEYapRY">
Accordion container element demo</a> by Levi McGranahan (<a href="https://codepen.io/levimcg">@levimcg</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>Progressive enhancement doesn’t have to be hardhttps://levimcg.com/blog/progressive-enhancement-doesnt-have-to-be-hard2019-12-15T22:00:00-07:002023-02-16T14:57:27-07:00Implementing a strategy for progressive enhancement might be easier than you think<p>I maintain a lightweight JavaScript theme switcher library called <a href="https://github.com/levimcg/themur"><em>Themur</em></a>. The other day I was adding a new feature that automatically detects a user’s OS theme settings by looking at the <code>prefers-color-scheme</code> media query and got to thinking about what would happen to Themur if for some reason JS wasn't available or the script failed to load. I'll be the first to admit that a lot of the JavaScript I've written in the past has been pretty poor in terms of considering progressive enhancement, but it's and area where I want to do better. As I made this somewhat un-related update to my code, I also took the opportunity to consider how I might progressively layer on the functionality that Themur provides.</p>
<h2>How does it fail?</h2>
<p>If you don't know what <em>progressive enhancement</em> is, it means considering what the baseline experience of your webpage is without JavaScript and then layering functionality on once you know JS is available. It's wild because in engineering terms this question, <em>how does it fail?,</em> should be the first one we ask, but oftentimes it is never even considered in front-end development. A good example is most client-side JS frameworks that render the entire UI in the browser, how would your app or site fail in that situation? How would Themur fail if javascript wasn’t available or there was an error loading the script?</p>
<h2>Enhancing Themur</h2>
<p>Themur works by taking in a <code>toggleElement</code> option (generally a button) and then listening for clicks on that button element. The button element then toggles a CSS class on the <code>document.body</code> element or any other element the user supplies via the <code>containerElement</code> option. So, if something went wrong loading the Themur script, you'd end up with a toggle button on the page that did nothing, which is not a great user experience. But, how would I progressively enhance a library like Themur? The answer turned out to be pretty dang easy!</p>
<p>You can look more closely at <a href="https://github.com/levimcg/themur">the source code</a> if you want, but in a nutshell, Themur does some initial setup where it adds the correct HTML attributes to the theme toggle button and the document body via a function called <code>setUpInitialState</code>. This turned out to be the perfect place to layer on some functionality as long as JavaScript was available. The solution I landed on was deceptively simple. It looks like this.</p>
<ol>
<li>Hide the toggle button using the <code>hidden</code> attribute in your button markup by default</li>
<li>Remove the <code>hidden</code> attribute in the <code>setUpInitialState</code> function once you're sure JavaScript is available</li>
<li>That's it!</li>
</ol>
<pre class="hljs"><code data-language="html"><span class="hljs-comment"><!-- Toggle button markup --></span>
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"my-toggle-button"</span> <span class="hljs-attr">hidden</span>></span>Toggle theme<span class="hljs-tag"></<span class="hljs-name">button</span>></span></code></pre>
<pre class="hljs"><code data-language="js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Themur</span> </span>{
<span class="hljs-keyword">constructor</span>(options) {
<span class="hljs-comment">// Call our set up function</span>
<span class="hljs-keyword">this</span>.setUpInitialState();
}
setUpInitialState() {
<span class="hljs-comment">// Other implementation details...</span>
<span class="hljs-keyword">this</span>.toggleElement.removeAttribute(<span class="hljs-string">'hidden'</span>);
}
}</code></pre>
<p>This approach works really well for Themur because it's non-essential functionality. That means it's not required for a user to interact with your page—if it's not there, no one will know the difference. It will be as if you never used Themur in the first place, which is totally fine and an acceptable baseline experience.</p>
<p>In this case, Themur itself is an enhancement, but when you think about it so are a lot of the common UI patterns that we don't provide fallbacks for e.g. tabs, accordions, etc. This approach to progressive enhancement won't necessarily work for every situation, but it's got me thinking more about how the interfaces I'm building fail and how to make them more resilient using progressive enhancement.</p>Focus trapping web componenthttps://levimcg.com/blog/focus-trapping-web-component2019-06-09T22:00:00-06:002023-02-16T14:57:44-07:00A Web Component wrapper that makes it easier to manage focus trapping<p>I was looking through Microsoft's design system <a href="https://developer.microsoft.com/en-us/fabric#/">Fabric</a> the other day and came across an interesting component in the <em>Utilities</em> section called <a href="https://developer.microsoft.com/en-us/fabric#/controls/web/focustrapzone">FocusTrapZone</a>. FocusTrapZone is a React component that is used to trap focus inside any HTML element. </p>
<h2>What does <em>trapping focus</em> mean?</h2>
<p>Trapping focus means that when a person uses the <kbd>Tab</kbd> key to navigate through focusable elements inside an area, focus will always be moved from the last focusable element back to the first, and from the first focusable element to the last when using <kbd>Shift + Tab</kbd> to navigate backwards. Trapping focus effectively means creating a loop where a user can only tab to any focusable element within a given container element, for example a modal dialog.</p>
<h2>Why would you want to create a focus trap?</h2>
<p>Focus trapping is a technique used to help restrict navigation to a particular region for people who use a keyboard to navigate. Take the modal dialog example I just mentioned. When a user activates a dialog, focus should be moved to the dialog element and subsequent presses of <kbd>Tab</kbd> should move focus to the next focusable element inside the dialog and so on. </p>
<p>But, what happens when the used reaches the last focusable element in the dialog? The answer is that the dialog remains active/open in the middle of the screen with the underlying content dimmed, while focus moves outside the dialog to the next focusable element in the document. That's not a great user experience, which is why restricting or <em>trapping</em> focus is important.</p>
<p>For more info on focus requirements for dialogs and other common UI components, I highly recommend digging into the <a href="https://www.w3.org/TR/wai-aria-practices-1.1/">ARIA Authoring practices</a>.</p>
<h2>Focus Trapper Web Component</h2>
<p>I've been spending a lot of time learning about <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">Web Components</a> recently and have been itching to build one that was actually useful. So, I decided to see what it would take to bundle up focus trapping functionality into my own Web Component from scratch.</p>
<p>The demo below shows how to use the <code>focus-trapper</code> element. When the <code>trapped</code> attribute is added, focus will cycle through all the focusable elements inside.</p>
<pre class="hljs"><code data-language="html"><span class="hljs-comment"><!-- User the <focus-trapper> element in your HTML --></span>
<span class="hljs-tag"><<span class="hljs-name">focus-trapper</span> <span class="hljs-attr">trapped</span>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>></span>Button one<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>></span>Button two<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>></span>Button three<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"></<span class="hljs-name">focus-trapper</span>></span>
<span class="hljs-comment"><!-- Import focus trapper and register it with the browser --></span>
<span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>></span><span class="javascript">
<span class="hljs-keyword">import</span> FocusTrapper <span class="hljs-keyword">from</span> <span class="hljs-string">'./path/to/FocusTrapper.js'</span>;
customElements.define(<span class="hljs-string">'focus-trapper'</span>, FocusTrapper);
</span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
<p>The idea of being able to build functionality that makes it easier to create more accessible interfaces into a Web Component is super interesting to me. Abstracting away all keyboard event listening and focus management into a declarative component that I can use in my HTML sounds like an ideal solution. I'm really interested to explore other tricky keyboard navigation concepts crucial to making UI components accessible that could similarly be rolled up into a Web Component.</p>
<h3>Focus trapper on GitHub</h3>
<p>I put the code for my <code>focus-trapper</code> Web Component up on GitHub. I'm sure there are some bugs, and it's mostly an experiment at this point, but I'd love to hear any feedback.</p>
<ul>
<li><a href="https://github.com/levimcg/focus-trapper">Github repository</a></li>
<li><a href="https://levimcg.github.io/focus-trapper/">Demo (with a dialog/modal)</a></li>
</ul>Sharpening the sawhttps://levimcg.com/blog/sharpening-the-saw2019-04-19T22:00:00-06:002023-02-16T14:57:56-07:00How to stay sharp when you're feeling stuck<p>I saw <a href="https://www.jamesvictore.com/">James Victore</a> (I'm pretty sure it was him) speak at a conference a long time ago where he talked about <em>sharpening the saw</em>. The idea being that you need to take the time to rest, to read books, and practice your craft. </p>
<p>I think about this a lot when it comes to design and development. As you move through your career there will be times where your responsibilities start to change. This means that sometimes it will be your job to do other things besides design screens or write code.</p>
<p>I'm experiencing that right now in my career. My day-to-day mostly involves meetings, delegating work, and writing proposals and meeting agendas. This means that I don't get a lot of time to actually write code, try out new technologies, or learn about new techniques. If I'm totally honest, this bums me out a little bit. </p>
<p>The good news is I always have my own personal site to tinker with. I tend to use it as a playground where I can try out new tools and techniques I want to learn about. Awhile back I decided to add a theme switcher to my site. This was mainly and excuse to learn a little bit more about <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/--*">CSS custom properties</a> and using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage">localStorage API</a>.</p>
<p>I probably could have just found some code to swipe and use on my own site. It's kind of a trivial problem really, but I chose to instead figure it out. First, I wrote some sloppy code that toggled a CSS class on the <code>body</code> element of my site. Once that was working, I added some functionality that stored the toggled state when navigating from page to page. After that, I added the ability customize the name of the class used to apply the theme, and so on. </p>
<p>What I ended up with was a pretty useful piece of code for adding a theme switcher to your site. Since most of the work was already done, I decided to turn it into a little <a href="https://github.com/levimcg/themur">JavaScript library</a>. Less than a day's worth of work and it gave me the opportunity to practice all of these:</p>
<ul>
<li>Refactoring my code</li>
<li>Writing useful documentation (this is a really important skill to practice)</li>
<li>Learning the ins and outs of using Rollup to create different types of bundles</li>
</ul>
<p>Now, I realize that toggling a CSS class is not rocket science, but for me this was a clear opportunity to <em>sharpen the saw</em> a little bit.</p>Universal Designerhttps://levimcg.com/blog/universal-designer2019-02-14T21:25:00-07:002023-02-16T14:58:00-07:00If you can design one thing, you can design most things<p>I made this shitty stool in design school. It was supposed be able to hold the weight of a person. It did not. I don't know why, or how I ended up hanging on to it for all these years. I was a bad designer and I’m not quite sure how I graduated. </p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/posts/my-stool.jpg" alt="Small meta stool with a plan setting on it" /></p>
<p>But, I ended up doing ok. My career took a winding path. I got jobs selling TVs and making coffee. Then some jobs designing logos and brochures, and then eventually websites. When I got those design jobs I was aware enough to remember the important stuff I learned in school—have empathy for the people you’re designing for, talk to them, try to find out how the thing you’re designing can make their life easier, or better.</p>
<p><a href="https://en.wikipedia.org/wiki/Massimo_Vignelli">Massimo Vignelli</a> said this:</p>
<blockquote>
<p>If you can design one thing, you can design everything.</p>
</blockquote>
<p>I believe in that. It's what allowed me to have a career. I’m lucky to do what I do.</p>Rockyhttps://levimcg.com/blog/rocky2019-01-20T21:20:00-07:002023-01-27T18:40:54-07:00The day we brought home Rocky<p>We brought home a new family member last night—Rocky. He needed a new home and we were lucky enough to be able to adopt him. I think he and Murray (our other dog) are going to be great friends! They've not stopped wrestling and playing since they met. He's a good boy 😀.</p><figure class="" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/rocky/da4bfc3b2e-1673190247/rocky.jpg" alt="Rocky, a Boston Terrier sitting" loading="lazy" decoding="async">
</figure>
<figure class="" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/rocky/d2293614f3-1673190247/rocky-and-murray.jpg" alt="Murray and Rocky, two Boston Terriers setting and looking at the camera" loading="lazy" decoding="async">
</figure>
My 2019 Todo Listhttps://levimcg.com/blog/my-2019-todo-list2019-01-02T21:20:00-07:002023-02-16T14:58:06-07:00Plans for 2019<p>I used the last few pages of my most recent notebook to write down all the stuff I want to do this year. It’s a lot. I’ll be happy if I can get at least a few of them right.</p>
<h2>2019 Todos</h2>
<ul>
<li>Drink more water</li>
<li>Drink less <em>everything else</em></li>
<li>Sweat more</li>
<li>Worry less</li>
<li>Be more kind to your family</li>
<li>Listen more</li>
<li>Talk less</li>
<li>Have fun</li>
<li>Don't forget where you come from</li>
<li>Help others grow</li>
<li>Call your mom</li>
<li>Call your dad (maybe)</li>
<li>Go for a walk when you feel stuck</li>
<li>Ask more people if you can pet their dog</li>
<li>Ask for help when you need it</li>
<li>Say no if you need to</li>
<li>Ride a bike</li>
<li>Use all your vacation time</li>
<li>Play more music</li>
<li>Cook food for your friends</li>
<li>Listen to more music</li>
<li>Tell your old friends that you still think about and miss them</li>
<li>Finally get your wisdom teeth removed</li>
<li>Read a few books</li>
<li>Finish the books you've already started</li>
<li>Focus a little more on where you are than where you're going</li>
</ul>Bye, 2018https://levimcg.com/blog/bye-20182018-12-31T21:20:00-07:002023-02-16T14:58:10-07:00My wrap-up post for 2018<p>It's almost 4pm on the last day of 2018 and after reading so many wonderful end-of-year wrap up posts and <a href="https://twitter.com/adamjk/status/1075078948235599872">top three accomplishments tweets</a> I'm motivated enough to at least record some of my thoughts about this past year, even if it's just for me.</p>
<p>This year has seen lots of highs and lows for my family and me. In early February <a href="/blog/bowie-mcg/">we lost one of our dogs Bowie</a>. A little over six months later we lost <a href="/blog/i-will-never-be-good-at-this/">our other dog Malcolm</a>. We love our dogs like family and that's a lot of loss to deal with in one year. I still miss them both a lot and we still talk about them quite often as a family. All that said, we did bring a new puppy into our home named Murray. He'll be a year old this January 31 and <a href="https://www.instagram.com/explore/tags/murray_mcg/">he's the best</a>.</p>
<p>Professionally, this has been the most successful year of my career both in terms of projects and finances. In April we launched the v1 of the <a href="https://rivet.iu.edu/">design system</a> I've been working on and I was officially made the lead of the project. We've had some decent success with adoption and have been given funding to actually build a team around the project so we can better serve the developers and designers who use it. I've worked very hard on this project for the last year and a half and I couldn't be more excited that I get to help build the team that will move it forward.</p>
<p>My family and I <a href="/blog/i-felt-more-permanent-today/">bought a house this summer</a>! After, four and a half years of living in apartments this was a big step for us. We love our new home and have settled in nicely. We even hosted our first Thanksgiving dinner in our new place this year. We're happy here in Bloomington and are excited to put down some roots in this community.</p>
<h2>Hey, 2019</h2>
<p>2018 was a hell of a year, but I gotta admit—I'm tired. I've never pushed myself this hard in trying to move my career forward. I'm still dealing with a lot of issues around how to balance that drive with being happy, healthy, a good husband, and a good dad. I don't really have any specific resolutions for 2019 but those three areas are where I'm going to put my focus.</p>
<p>Happy New Year! 🎉</p>The HTML Straw Man Fallacyhttps://levimcg.com/blog/the-html-straw-man-fallacy2018-12-08T21:15:00-07:002023-02-16T14:58:19-07:00You don't need to worry about the size of your HTML if you're using tons of JavaScript<p>When I write CSS I generally favor clarity over brevity when choosing the way I name classes/components. I will admit that this can make for some pretty long class names and can look a little weird if you're using a naming convention like BEM.</p>
<p>For example:</p><pre class="hljs"><code data-language="plaintext"><div class="profile profile--compact">
<div class="profile__image profile__image--large">
<img src="path/to/image">
</div>
<div class="profile__bio">
<!-- Profile content -->
</div>
</div></code></pre>
<p>To me those class names are perfectly descriptive and easy to remember, especially after six months away from a project when I have to come back and work on this code. Yes, class names like this cause a few extra keystrokes for developers, but I tend to feel the extra effort is worth it. Also, we have great tools that do things like <a href="https://marketplace.visualstudio.com/items?itemName=Zignd.html-css-class-completion">auto-complete class names for us</a>.</p>
<p>Recently, a couple of developers have tried to make the argument to me that long class names make the HTML sent over the network larger in file size and therefore slower. What this argument really boils down to is prioritizing convenience and developer experience over clarity and maintainability.</p>
<p>The idea that CSS class names are making your app slow is, of course, a <a href="https://en.wikipedia.org/wiki/Straw_man">straw man</a>, as more and more developers are using massive amounts of JavaScript, <a href="https://speedcurve.com/blog/your-javascript-hurts/">the most CPU-intensive asset</a> for web browsers to process, to render the entire UI of their app. If you are not <em>actually sending HTML</em> over the network (i.e. it's being built on the client), can you really argue that the size of the HTML is making your app slow?</p>
<p>I dunno, I could be wrong, but maybe we should address the giant elephant in the room. Maybe the size of the giant JavaScript bundles we're sending over the network, creating one single point of failure for our entire UI, should be the first thing we talk about when making arguments for better performance?</p>I’ll never be good at thishttps://levimcg.com/blog/i-will-never-be-good-at-this2018-09-23T21:15:00-06:002023-02-16T14:58:23-07:00Dealing with death and loss of our dog Malcolm<p>We lost <a href="../bowie-mcg">our dog Bowie</a> earlier this year. It was totally unexpected, and I wrote about the helpless feeling that comes along with death. If you've ever been robbed or had something stolen from you, it feels the same.</p>
<p>I took our dog Malcolm to the vet last week to get a bump on his rib cage checked out. It turns out that bump is a tumor, and after a quick x-ray, the vet discovered multiple others including one in each lung the size of a tennis ball.</p>
<p>He's still happy, and doesn't appear to be suffering at all, but we have a plan for when he starts to show more severe symptoms. That's a different feeling all together—having <em>a plan</em>. Its a different kind of guilt and sadness that comes with planning <em>for</em> a death.</p><figure class="" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/i-will-never-be-good-at-this/c1c0e7a535-1673190245/malcolm.jpg" alt="Malcolm and me in bed the morning after his vet visit" loading="lazy" decoding="async">
</figure>
<p>I was tore up the day I found out, but I've had some time to think and realized that this is probably the best-case scenario. We have some time to enjoy, and I plan to. I'll never get any better at dealing with this, but I also hope I never do.</p>Skyscraper or garden?https://levimcg.com/blog/skyscraper-or-garden2018-09-05T13:00:00-06:002023-02-16T14:58:35-07:00Some thoughts on how we define and then evolve design standards<p>I've been the lead on a <a href="https://rivet.iu.edu/">design system project</a> that serves dozens of development teams across a fairly large organization for a little over a year now. I've done a lot of work defining and documenting design standards in that time and have really enjoyed it.</p>
<p>As the project grows and real humans start to use it (sometimes abuse it), and improve it in ways I couldn't have imagined, I've been thinking a lot more about how the system needs to adapt.</p>
<p>I worked in a brand identity agency years ago and remember reading and article back then (I can't for the life of me find a link 😬, I'm sorry 🙏) about how companies tend to think of their brand as a solid structure like a skyscraper—something rigid that will never change. But really successful brands are more like gardens—you plant them and they grow and bloom. Sometimes they turn into something more beautiful than you could have imagined, and sometimes they become overgrown and need to be pruned back.</p>
<p>Maybe this is just a overblown metaphor for saying "Plan for change", but <em>I think</em> there's more to it than that. Before you fire up Sketch, write a line of CSS, or pick which front-end JavaScript framework you're going to use, I suggest you zoom out and spend some time <em>designing</em> how you'll manage the growth and ultimately, pruning of the system over time.</p>
<p>I don't have universal answers to how you design those processes, or what they look like, but I <em>do</em> have a handful of questions to get started.</p>
<h2>Partial design system kick-off checklist</h2>
<p>Looking back, here are few questions I wish I would've thought more deeply about before I jumped into building a design system.</p>
<ul>
<li><strong>How will you build a community around your design system?</strong><ul>
<li>Do you have a clear process for contributing to your system?</li>
<li>How do you recognize teams/individuals that are using the system well?</li>
<li>How do you get teams to share work back with you?</li>
</ul>
</li>
<li><strong>What happens when designers/developers deviate form the system?</strong><ul>
<li>What if the result of their deviation is good and would be a valuable addition to the system?</li>
<li>What if it's really inaccessible, or bad UX design?</li>
</ul>
</li>
<li><strong>How will you define the lifecycle of your design system?</strong><ul>
<li>How often will you do releases?</li>
<li>How do you retire components from your design system?</li>
<li>How do you deal with breaking changes?</li>
</ul>
</li>
<li><strong>How will you demonstrate the value of your design system?</strong><ul>
<li>How do you talk with change-averse folks who don't what to learn something new?</li>
<li>How do you talk to leadership about the value of a design system?</li>
</ul>
</li>
</ul>I felt more permanent todayhttps://levimcg.com/blog/i-felt-more-permanent-today2018-09-03T21:10:00-06:002023-02-16T14:58:40-07:00Writing down some thoughts about moving to a new house<p>My partner and I bought a house this summer in the town where we've lived and worked for the past five years. She, our son, and I just finished moving this past week and are enjoying our first weekend in the new place. Yesterday I mowed the lawn for the first time. It was a really hot day (90ºF) and the beer I had right after (a humble Corona), was among the top five best-tasting beers I've ever drank.</p>
<p>I bought <a href="https://www.weber.com/US/en/grills/gas-grills/spirit-ii-series/44010001.html?cgid=22956#start=1">a new grill</a> this weekend and last night Lincoln and I grilled up some burgers and sweet corn. Sam made rice crispy treats. She followed the recipe on the back of the cereal box exactly and they were so tasty.</p>
<p>All this is to say that I feel really good right now—more settled, less transient, permanent.</p>
<p>Here's a shoddy photo I snapped with my phone of move-in day.</p><figure class="" data-ratio="auto">
<img src="https://levimcg.com/media/pages/blog/i-felt-more-permanent-today/07067cb2e3-1673190245/more-permantent-moving.jpg" alt="Our new house with the moving truck parked in front" loading="lazy" decoding="async">
</figure>
Burnout and knowing when to walk awayhttps://levimcg.com/blog/burnout-and-knowing-when-to-walk-away2018-06-17T21:10:00-06:002023-02-16T14:58:44-07:00A short note about recognizing burnout<p>I've been feeling a lot of the tell-tale signs of burnout. I can't stay focused on complex tasks, it feels like I won't ever finish anything, and I keep working when I know I shouldn't.</p>
<p>It's that last one that is most cruel. <em>The worst thing you can do is try to force the work</em>. When you're burned out, there's a little voice telling you that if you just work for another hour, or open your laptop on Sunday night that you'll have a breakthrough and magically just...not feel burned out anymore.</p>
<p>I've been in this situation enough times to know what works for me. <strong>Walk away</strong>. Step back and think about something else for the weekend. You're smart, and good at what you do, and you'll figure out that problem you are trying to solve. But first, give yourself a break.</p>Bowie McGhttps://levimcg.com/blog/bowie-mcg2018-02-03T10:00:00-07:002023-02-16T14:58:50-07:00We lost our dog Bowie this week and it's been rough<p>Our dog Bowie passed unexpectedly on Thursday, February 1 2018. As far as we (and the vet) could tell he had a pretty rough life before he came to live with us almost two years ago. I think we were able to give him a pretty good last couple of years of life.</p>
<p>Dealing with any kind of death is such a helpless and frustrating experience and it never gets easier. We miss him and we're still feeling pretty low, but I'm thankful for the time he got to live with us and the great memories we have to share forever.</p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/posts/bowie/bowie-first-day.jpg" alt="Bowie's first day with us" /></p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/posts/bowie/bowie-mcg-watercolor-800.jpg" alt="Amazing watercolor portrait by our friend Priscilla" /></p>Slow Driphttps://levimcg.com/blog/slow-drip2017-11-26T21:05:00-07:002023-02-16T14:58:54-07:00I've only made a handful of art pieces since January, but it's something<p>A little while ago I wrote about how <a href="../whats-next">I started a new job</a> at the beginning of this year. About a month after that I had my first solo <a href="../deep-cuts">art show</a>. Work has kept me pretty busy and to be honest, I've barely had the energy to create much art in what spare time I've had.</p>
<p>I don't think there will ever be a time where I don't have the urge to make <em>some</em> kind of art. Creative fulfillment is something that I need in my life, and the urge to pursue it never really goes away. I'm lucky because even though my job doesn't involve making art, I <em>do</em> get a lot of the same sense of fulfillment out <a href="../whats-next/#design-systems">the work I'm doing</a>.</p>
<p>I still carve out a little time here and there to make new stuff. It's a slow drip, but here are some new pieces I've made over the last few months.</p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/posts/slow-drip/mcg-slow-drip-shape-studies-1.jpg" alt="This is an image of three colorful abstract compositions" /></p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/posts/slow-drip/mcg-slow-drip-shape-studies-2.jpg" alt="" /></p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/posts/slow-drip/mcg-slow-drip-shape-studies-3.jpg" alt="" /></p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/posts/slow-drip/mcg-slow-drip-group-shot.jpg" alt="This is an image of a lot of colorful abstract compositions next to each other." /></p>
<h2>Small Works Holiday Group Show</h2>
<p>I'll be showing a handful of pieces (maybe some of these new ones ☝️) at the Blueline Gallery again this coming month. It's a <a href="https://www.visitbloomington.com/event/small-works-holiday-show/36492/">group show</a> with a few other local artists and it'll be up until December 29.</p>Summer Seventeen EPhttps://levimcg.com/blog/summer-seventeen-ep2017-11-21T20:00:00-07:002023-02-16T14:59:04-07:00I played drums on my first studio recording this last September<p>I played drums on my first studio recording a couple of months ago. I've been in a handful of bands over the years and have played guitar and bass on studio records, but this was a totally new (slightly stressful) experience for me. We booked a Saturday back in September at <a href="http://www.russianrecording.com/">Russian Recording</a> and it ended up being such a great experience. Our goal was to get three songs recorded in one day, but we ended up finishing six! <strong>Here's the result</strong>:</p>
<p>
<iframe style="border: 0; width: 100%; height: 340px; margin-top: 2rem; margin-bottom: 2rem;" src="https://bandcamp.com/EmbeddedPlayer/album=2760125649/size=large/bgcol=ffffff/linkcol=0687f5/artwork=small/transparent=true/" seamless><a href="http://meandlevi.bandcamp.com/album/summer-seventeen">Summer Seventeen by Me & Levi</a></iframe></p>
<p>I've mostly played with the same group of people for a while. A few years ago, our drummer decided he was no longer able to commit the time to rehearsing or playing shows. I've always like messing around on a drum set so I volunteered to quit playing guitar and become the drummer. This made more sense than looking for another person who's schedule we'd have to try and make work. We used the band money we had saved up to buy a cheap drum set and I started learning how to play.</p>
<p>Life can get pretty hectic with having kids, starting businesses, and changing jobs. Three years later, it's just me and my friend Alan left from the original band of four. We've mostly scrapped all the old songs we wrote during the first few years of me learning how to play drums. After a four month break at the beginning of 2017 we started rehearsing again as a two-piece. By the end of the summer we came away with an EP's worth of material and then some.</p><figure class="" data-ratio="auto">
<img src="https://s3.amazonaws.com/static.levimcg.com/notes/summer-seventeen/meandlevi-logo.png" alt="Two version of a logo that looks like an ampersand with a snake's head" loading="lazy" decoding="async">
</figure>
<p>Our current set up is just vocals, a baritone guitar, and drums. There are lots of things I miss about a more traditional instrumentation, but I'm really enjoying how the minimal amount of instruments forces you to work harder on dynamics and phrasing. We've written about half of a new EP since recording this one. We're planing to play a few shows and then go back into the studio in the spring. I feel really positive about the new stuff we are writing and can't wait to share more.</p>What is nexthttps://levimcg.com/blog/what-is-next2017-11-10T21:00:00-07:002023-02-16T14:59:10-07:00Taking a quick look at where I started as I move to the next thing<p>I graduated college with a degree in industrial design at the beginning of 2004. I got my first design (unsurprisingly not in industrial design) job in 2006 designing menu screens for DVDs. It was just a few short years before the financial crisis would hit full-swing. Two years later I was told my department was being shut down. I had four months to find a job.</p>
<p>By this point I had some experience and thought getting another DVD job was possible. It wasn't. It turns out that there are like, three places in the US that have DVD menu designers. I just happened to end up at one that was on the way out.</p>
<p>I needed an online portfolio to apply for jobs so I bought a book about HTML & CCS and spent a month's worth of nights and weekends learning how to make a website. I was lucky in a way because this was before Squarespace, or any of those pre-built solutions. Wordpress was kind of an option, but looking at PHP files gave me the sweats. HTML and CSS made sense to me. They are more visual and I could instantly see my progress in the browser. I liked that. I still do. Coding my own website forced me to learn how to actually write HTML and CSS.</p>
<p>I was nearly out of a job, but I had a portfolio website that I built by hand, from scratch. And It worked. I found another job—<strong>as a print designer</strong>.</p>
<p>I was happy to have a job and I threw myself into learning everything I could about designing for print. I learned about prepress, typography, color, and white space. I learned how to deal with clients, and meet deadlines, and work within a budget. I started designing logos, and websites for friends and family. They recommended me to the people they knew that need logos and websites. I started to ask those people for money to make things for them. And they didn't say no.</p>
<h2>Another what next</h2>
<p>I've had a couple of jobs since then and I've become pretty decent at designing things for screens. In a roundabout way I found a career that I love, and I can't really see myself doing anything in the future that doesn't involve making things for the web.</p>
<p>I do feel something changing in the way I approach my work though. I started a new position at the beginning of the year that has me doing a lot less day-to-day production design work and instead thinking about higher-level problems. Specifically, how to create more consistent and usable experiences across a disparate set of systems within an organization.</p>
<p>I spend a lot of time supporting other designers and developers that are building/rebuilding these systems. Here are a couple of things I've been thinking a lot about:</p>
<h3>Design systems</h3>
<p>Designing a reusable system of components that can be applied to all the products across your organization is smart. It saves time and money and creates familiarity for your users. I'm working on one now and here's what I've learned. <strong>The code is a formality</strong>. Put all your time into documentation and building a community around it.</p>
<h3>Facilitating design</h3>
<p>In working mostly with other designers and developers, I've been thinking a lot more about giving and receiving feedback, helping others work through design problems, and helping less experienced designers learn. I get a lot of enjoyment out of this stuff on a personal level, but it's also important to share what you know. It makes you and (if you do it right) the ones you're sharing with smarter.</p>
<p>I'm definitely in the middle of some sort of transition in my career and I'm excited. Seems like a good time to take a quick look at how I got here and write it down. Mostly as a reminder to be open to change and the opportunity to move on to what's next.</p>Deep Cutshttps://levimcg.com/blog/deep-cuts2017-02-12T21:00:00-07:002023-02-16T14:59:15-07:00I had my first solo art show opening last week<p>Last Friday, Febreuary 3 was the opening for my show called Deep Cuts at <a href="http://thisisblueline.com/">Blueline Gallery</a>. It's a collection of my work from the last year or so that includes a bunch of new original artwork and prints, icluding some from my <a href="http://relatives.levimcg.com/">Relatives project</a>.</p>
<p>I didn't get a ton of photos from the event, but here's one of the shots of the main wall I grabbed with my phone on the night. I know it's kinda blurry.</p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/notes/deep-cuts/deep-cuts-opening.jpg" alt="Deep cuts opening" /></p>
<h2>Sketchbook video</h2>
<p>I also made this video of the sketchbook I've kept for the last few months. There was tons of great work in here that I wanted to show, but didn't really want to rip out the pages. This video was the solution. It ran in a loop on a big monitor during the opening reception.</p>
<iframe width="853" height="480" src="https://www.youtube.com/embed/VjveggFpDTI" frameborder="0" allowfullscreen></iframe>Family Portraits August 2016https://levimcg.com/blog/family-portraits-august-20162016-08-28T14:00:00-06:002023-02-16T14:59:19-07:00My wife has been asking me to do some portraits of our family and I finally got around to it.<p>My wife and son were out of town for the weekend visiting some family, so I got a house all to myself. I spent all of Friday night drawing and listening to records. It was so much fun and totally refreshing.</p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/notes/family-portraits/portraits-combined.png" alt="Family Portraits" /></p>
<p>My wife has been asking me to sketch some portraits of her, my son, and myself for a while and she was pretty pleased with the results. I used some new brushes and ink I had bought earlier and realized how much I like working in this loose sketchy style.</p>Potting Plantshttps://levimcg.com/blog/potting-plants2016-05-15T04:55:00-06:002023-02-16T14:59:23-07:00I recently became a plant owner for the first time in my life<p>My mom came down and spent the weekend with us to celebrate a belated Mother's Day. We spent some time a the local <a href="http://www.maysgreenhouse.net/">greenhouse</a> picking out a few new house plants that we potted yesterday.</p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/notes/plants/plants-web.png" alt="Plants paper cut out" /></p>Paper Cut Upshttps://levimcg.com/blog/paper-cut-ups2016-05-05T21:25:00-06:002023-02-16T14:59:26-07:00I did some quick paper cut compositions last week.<p>I did some quick paper cut compositions last week. It's always great to get back to actually using your hands every once in a while.</p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/notes/paper-cut-ups/salad-web.jpg" alt="image" /></p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/notes/paper-cut-ups/waves-web.jpg" alt="image" /></p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/notes/paper-cut-ups/checks-web-alt.png" alt="image" /></p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/notes/paper-cut-ups/eyes-web.png" alt="image" /></p>January 2016https://levimcg.com/blog/january-20162016-01-09T21:25:00-07:002023-02-16T14:59:31-07:00A brief overview of what I'm working on and what's happening in my life. This is my first version of the "Now" page on my site.<p>It's January 2016. I love this time of year and the sense of optimism it brings. That's what resolutions are all about—looking at the future and deciding that we want to be a better version of ourselves.</p>
<p>Like many others I've decided that I want to be more healthy this year and just generally treat myself better. Im interested in understanding the science behind what happens to food after it goes into my body instead of arbitrarily cutting some things out of my diet and adding others. This, of course, means being more conscious of the things I eat and drink (alcohol).</p>
<h2>Relatives</h2>
<p>On January 1 I started a new project called <a href="http://relatives.levimcg.com/">Relatives</a>. I’m trying to go a whole month posting one abstract composition everyday. There are two ideas driving this. First, I thought it would be good to start of the year with a creative cleansing of sorts.</p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/relatives/relatives-logo--large.jpg" alt="Relatives Logo" /></p>
<p>I’ve set try strict constraints on this project. Limited color palette, only simple primitive shapes, and no literal forms i.e. only abstract form(s)—that’s it. Second, it’s a commitment. I’m looking to build up the muscle memory for creating more often whether that’s images or words, or both.</p>
<h2>JavaScript</h2>
<p>I’ve flirted with the idea of learning a programming/scripting language for a few years now. I’ve started a few different languages (a few different times) and have given up each time I’ve gotten tired of writing code that only prints text to the console instead of learning some sort of real-world application or, how to build something. This time I chose JavaScript. In the past JavaScript has been a strictly front-end language that developers used to add interactivity to webpages. However, in recent years it become a full-fledge scripting language in it’s own right, capable of powering server-side applications.</p>
<p>I started back in December and things are going well. I’m almost through with a book called <a href="http://javascriptbook.com/"><em>JavaScript & jQuery Interactive Front-end Web Development</em></a>. It’s a fantastic book for those looking to step up their front-end development game. Not only that, it’s by far the best (and most gentle) introduction to programming I’ve every read.</p>
<p>There’s something that feels different about this time. I can see the light at the end of the tunnel and I’m already reaping the benefits in my day-to-day work as a front-end developer and designer.</p>
<h3>My Now Page</h3>
<p>This is a re-post of the <em><a href="/now">now</a></em> page that I created earlier this year. You can <a href="http://nownownow.com/about">go here </a>to read a little more about it.</p>Dr. Oliver Sackshttps://levimcg.com/blog/dr-oliver-sacks2015-09-02T21:25:00-06:002023-02-16T14:59:38-07:00I was sad to hear of the passing of Oliver Sacks. I'm currently reading his recently-released autobiography. What wonderful human being.<p>I was sad to hear of the passing of Oliver Sacks. I'm currently reading his recently-released autobiography <a href="http://www.amazon.com/On-Move-Life-Oliver-Sacks/dp/0385352549"><em>On The Move</em></a>. What wonderful human being.</p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/notes/sacks/sacks--small.png" alt="Sack drawing" /></p>Messengerhttps://levimcg.com/blog/messenger2015-05-08T21:20:00-06:002023-02-16T14:59:42-07:00Another fake poster commemorating the end of MESSENGER’s mission<p><img src="https://s3.amazonaws.com/static.levimcg.com/notes/messenger/messenger--large.jpg" alt="The End of MESSENGER" /></p>
<p>Another fake poster commemorating the end of <a href="https://en.wikipedia.org/wiki/MESSENGER">MESSENGER</a>’s mission. I’m channeling my inner Paul Rand on this one. Having some fun with tearing and cutting paper.</p>Eye on The Universehttps://levimcg.com/blog/eye-on-the-universe2015-04-28T21:20:00-06:002023-02-16T14:59:45-07:00A fake poster for the 25th anniversary of the Launch of Hubble<p>I’m a child of the space shuttle era and I have such vivid and exciting memories from that time. Here’s a fake poster for the 25th anniversary of the Launch of Hubble.</p>
<p><img src="https://s3.amazonaws.com/static.levimcg.com/notes/hubble/hubble-eye--large.jpg" alt="Eye On Universe" /></p>