Tyler Gaw's blog2023-10-24T00:00:00Zhttps://tylergaw.com/Tyler Gawme@tylergaw.comComplex MPA View Transitions2023-10-24T00:00:00Zhttps://tylergaw.com/blog/complex-mpa-view-transitions/<p class="note-special">
<strong>Note:</strong> At the time of this post, view transitions are still an emerging standard. Examples may not work for you. To use in Chrome or Arc, enable two feature flags:
<code>chrome://flags#view-transition</code> and <code>chrome://flags#view-transition-on-navigation</code>
</p>
<p class="entry-intro">
In my <a href="https://tylergaw.com/blog/view-transitions-first-experiments-mpa/">previous post</a> on the topic, I wrote about what I was learning about view transitions and shared <a href="https://github.com/tylergaw/mpa-view-transitions-sandbox">examples</a> of different transitions. At the end of that post, I also wrote about wanting to come up with interesting transitions on this site. A few months back, I spent a few hours across a couple days adding fun transitions to most of the project pages and the about page. They’re live now, if you’re using a browser with view transitions supported and enabled.
</p>
<p>For this post, I’ll talk about these more complex page transitions by going into detail about the <a href="https://tylergaw.com/work/streetcred">StreetCred</a> page, and share tricks I’m picking up along the way.</p>
<p>Before getting into it, here’s a video showing all the transitions.</p>
<figure>
<video src="https://stuff.tylergaw.com/post-complex-view-transitions/view-transitions-preview.mp4" controls=""></video>
<figcaption>fig 1: A preview of each page’s transitions.</figcaption>
</figure>
<p>As you can see in the video, the transitions are on the hero section of each page. Those hero sections are some of the most visually interesting sections of the site, and since they’re at the top, it makes them an ideal candidate for layering on view transitions.</p>
<p>Full page transitions are OK, but they also make a site feel like a PowerPoint. The only time I’m likely to use full page transitions is if I’m making a web-based slide deck. Even then, using only full page animations can feel flat. Animating multiple elements in an orchestrated way is a better way to use motion to create interesting effects. So, that’s what I’ve done. Let’s get into it.</p>
<h2>Setting the stage</h2>
<p>First, we need to enable view transitions by adding the <code>meta</code> element to the <code>head</code> of all pages:</p>
<pre><code class="language-html"><meta name="view-transition" content="same-origin"></code></pre>
<p>Next, since we want don’t want the default full-page fades, we turn them off with this CSS:</p>
<pre><code class="language-css">::view-transition-old(*),
::view-transition-new(*) {
animation: none;
mix-blend-mode: normal;
}</code></pre>
<p>That snippet clears the browser defaults for both the old and new transition snapshots. This gets us to a baseline where we’ve enabled transitions, but they don’t have a visual effect. I see this snippet, or one very near it, ending up in most of our normalize or reset styles.</p>
<h2>Building the animation</h2>
<p>Looking at the StreetCred page hero, we can already see opportunities for motion. I’ve had some type of motion in mind for it since I designed it years back. I could see each element moving independently in a way that gave each its own kind of story.</p>
<figure>
<picture>
<img src="https://stuff.tylergaw.com/post-complex-view-transitions/streetcred-transitions-overview.jpg" alt="The hero section of tylergaw.com/work/streetcred showing the elements we'll animate." />
</picture>
<figcaption>fig 2: The StreetCred hero at rest.</figcaption>
</figure>
<p>Let’s get the styles in place that will allow us to animate each element when the page loads. There are six elements we want to animate: the logo, the main device, the app screen, and the three smaller app details positioned above the main device. In the markup, each of those is a separate element with a class.</p>
<p>For each of those rulesets, we’ll apply a unique <code>view-transition-name</code>. For example:</p>
<pre><code class="language-css">.streetcred__graphic-main {
...
view-transition-name: streetcred-graphic-main;
}</code></pre>
<p>We repeat that for each element. I’ll leave out the other elements here for brevity. The full stylesheet is <a href="https://github.com/tylergaw/tylergaw.com/blob/main/src/css/modules/pages/work/streetcred.css">available on Github</a>.</p>
<p>When adding motion, it’s important to work in a way where the motion isn’t required. Animations should be additive. All content and graphics must be available without transitions or animations. That could be because the current browser doesn’t support them, or because the user has opted to reduce motion on their machine. Either way, we’ll use a <code>prefers-reduced-motion: no-preference</code> media query to only apply animations if the user hasn’t opted out.</p>
<p>To get a foundation in place, let’s add a basic fade-in for all elements by targeting the <code>view-transition-new</code> pseudo element.</p>
<pre><code class="language-css">@keframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@media (prefers-reduced-motion: no-preference) {
::view-transition-new(streetcred-title),
::view-transition-new(streetcred-graphic-main),
::view-transition-new(streetcred-graphic-main-img),
::view-transition-new(streetcred-graphic-board),
::view-transition-new(streetcred-graphic-poi),
::view-transition-new(streetcred-graphic-cubes) {
animation: fadeIn 0.5s ease;
}</code></pre>
<figure>
<video src="https://stuff.tylergaw.com/post-complex-view-transitions/work-transitions-0.mp4" controls=""></video>
<figcaption>fig 3: The foundation, a basic fade-in transition.</figcaption>
</figure>
<p>Nothing spectacular, but we have a working transition for each element. Now, we can break this up by element to create a composition. We’ll do this in parts.</p>
<p>For the first part, we’ll animate the main device and app screen as follows:</p>
<ol>
<li>No elements are visible as the page loads</li>
<li>The main device fades in and floats up into position. On the device we see the purple background / StreetCred logo splash screen</li>
<li>The app screen fades in with a subtle scale up</li>
</ol>
<p>Here’s the completed animation with all other elements hidden.</p>
<figure>
<video src="https://stuff.tylergaw.com/post-complex-view-transitions/work-transitions-1.mp4" controls=""></video>
<figcaption>fig 4: The main device and app screen animation.</figcaption>
</figure>
<p>That looks solid. Notice we navigated to the StreetCred page from another page. Look at what happens if we refresh the page though.</p>
<figure>
<video src="https://stuff.tylergaw.com/post-complex-view-transitions/work-transitions-1-refresh.mp4" controls=""></video>
<figcaption>fig 5: The main device and app screen animation on page refresh looking wrong.</figcaption>
</figure>
<p>Wait, that’s not right. See how the device and app screen are already visible. It’s like they’re at the end of their animation. We can see some movement, so we know the animation is still happening. So what’s the problem? We’re animating the <code>view-transition-new</code> snapshot, but the <code>view-transition-old</code> snapshot is still visible in its final position. This is a common problem. Here’s a trick I use to fix it that we’ll use on all the pages. When we have animations like this, where elements aren’t visible on the screen and then transition in, we have to hide the old view transition snapshot. To do that, we target the <code>::view-transition-old</code> selector for each of our named view transitions.</p>
<pre><code class="language-css">::view-transition-old(streetcred-graphic-main),
::view-transition-old(streetcred-graphic-main-img) {
display: none;
}</code></pre>
<p>We need to hide the old snapshot for all six elements. The complete ruleset ends up being:</p>
<pre><code class="language-css">::view-transition-old(streetcred-title),
::view-transition-old(streetcred-graphic-main),
::view-transition-old(streetcred-graphic-main-img),
::view-transition-old(streetcred-graphic-board),
::view-transition-old(streetcred-graphic-poi),
::view-transition-old(streetcred-graphic-cubes) {
display: none;
}</code></pre>
<p>It’s repetitive, but we set it once and forget it. This isn’t to say the old snapshot in <code>::view-transition-old</code> is useless, only that I don’t have a use for it in these transitions. I’m still figuring out exactly how to use them. I <strong>think</strong> they could be useful if I decided I wanted some type of outro transition when leaving the page.</p>
<p>Back to the rest of the animation. In our <code>prefers-reduced-motion</code> media query, we apply new animations to the <code>streetcred-graphic-main</code> and <code>streetcred-graphic-main-img</code> view transitions.</p>
<pre><code class="language-css">@media (prefers-reduced-motion: no-preference) {
::view-transition-new(streetcred-graphic-main) {
animation: graphicMain 0.5s ease;
}
::view-transition-new(streetcred-graphic-main-img) {
animation: graphicMainImg 0.9s ease;
}
}</code></pre>
<p>And the <code>graphicMain</code> and <code>graphicMainImg</code> keyframes:</p>
<pre><code class="language-css">@keyframes graphicMain {
0%, 50% {
opacity: 0;
translate: 0 100px;
}
100% {
opacity: 1;
translate: 0;
}
}
@keyframes graphicMainImg {
0%, 75% {
opacity: 0;
scale: 0.9;
}
100% {
opacity: 1;
scale: 1;
}
}</code></pre>
<p>These are both typical <code>keyframes</code> rulesets, with a small timing trick that I’ll explain shortly. <code>graphicMain</code> animates the opacity and Y translation of the main device to make it appear. <code>graphicMainImg</code> animates the opacity and scale of the app screen to give that splash-screen-to-app-initialized effect.</p>
<p>Now, the timing trick. Watch the transition again. Notice the slight delay before the main device starts to transition? I’m finding that a delay of 200 milliseconds or so helps sell these types of page transitions. Without it, the motion starts as soon as the page loads, and feels like it gets lost in the surrounding page changes. This isn’t a one-size-fits all setting, and I’m not yet convinced I have the timing right on all the pages. It’s something I’ll keep tweaking.</p>
<p>The first thing I tried to create the delay, was <code>animation-delay</code>. I found out quick that it could work, but would require more effort to use while also respecting our <code>prefers-reduced-motion</code> media query. The reason for that is we would have to apply the “off-screen” styles to the snapshot element along with the animation to make sure the snapshot isn’t visible during the delay. I tried a few different approaches with <code>animation-delay</code>, but I couldn’t make it work. Things just got...weird with it. So, I tried a different approach.</p>
<p>Notice the <code>0%,50%</code> and <code>0%,75%</code> in the keyframes. Those delay property interpolation until 50% and 75% of the animation duration, respectively. That way, at the start of the animation, the elments are invisible because they have their <code>opacity</code> set to <code>0</code>. We couple that with the duration of the animations, <code>0.5s</code> and <code>0.9s</code>. I write those as seconds in CSS, but here, easier to say <code>200ms</code> and <code>900ms</code>. So that means from <code>0ms to 100ms</code> or 50% of the total duration, and <code>0ms to 675ms</code> or 75% of the total duration, the elements are invisible, off screen. It takes a bit of math to work those out, but it also doesn’t need to be exact. When I build these, I just feel out both the percentages and the durations until I find what works.</p>
<p>Next up, we’ll transition the three smaller app details that sit above the main device. For each of these, we want them to do a fade with a bit of a float and scale up into place. Starting with the leaderboard.</p>
<figure>
<video src="https://stuff.tylergaw.com/post-complex-view-transitions/work-transitions-2.mp4" controls=""></video>
<figcaption>fig 6: The leaderboard detail transitioning into place.</figcaption>
</figure>
<p>We follow a similar pattern as with the previous elements. Again, in our <code>prefers-reduce-motion</code> media query we apply a keyframe animation to the new snapshot:</p>
<pre><code class="language-css">@media (prefers-reduced-motion: no-preference) {
...
::view-transition-new(streetcred-graphic-board) {
animation: graphicDetail var(--duration, 1.2s) ease;
}
}</code></pre>
<p>Notice this time we’re using a custom property, <code>--duration</code>, to set the animation duration. We’ll take advantage of that for later elements. Then, the <code>graphicDetails</code> animation is:</p>
<pre><code class="language-css">@keyframes graphicDetail {
0%,
80% {
opacity: 0;
transform: scale(0.5) translateY(180px);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
}
}</code></pre>
<p>Again, we’re using the <code>0%,80</code> timing trick. The property changes get our fade in, scale, and float up into place. Notice the order. The element starts scaled and translated down, then transitions to normal scale and translation. Here’s a tangent about how I think about these types of animations. Imagine you have a bowling ball attached to a rope that’s tied to a rafter in the ceiling. Then the bowling ball is hoisted up to the ceiling while keeping the rope taut and the ball is fastend to the ceiling with another piece of rope. That’s the <code>0% to 80%</code> position of the animation. To start the animation, we cut the second piece of rope that had the ball secured to the ceiling, allowing the bowling ball to swing down until comes to a resting or, <code>100%</code>, position.</p>
<p>From here, we can transition the other two app details. They’re similar to the leaderboard, so we can reuse the <code>graphicDetail</code>.</p>
<figure>
<video src="https://stuff.tylergaw.com/post-complex-view-transitions/work-transitions-3.mp4" controls=""></video>
<figcaption>fig 7: The POI and cubes details transitioning into place.</figcaption>
</figure>
<p>Notice they don’t all transition in at the same time, and they don’t wait for each other to finish before starting. That helps sell it. We want that slight overlap in timing to make the compisition more interesting. To do that, we apply the same <code>graphicDetail</code> animation to the POI and cubes details, but we customize the <code>--duration</code> custom property.</p>
<pre><code class="language-css">@media (prefers-reduced-motion: no-preference) {
...
::view-transition-new(streetcred-graphic-board),
::view-transition-new(streetcred-graphic-poi),
::view-transition-new(streetcred-graphic-cubes) {
animation: graphicDetail var(--duration, 1.2s) ease;
}
::view-transition-new(streetcred-graphic-poi) {
--duration: 1.4s;
}
::view-transition-new(streetcred-graphic-cubes) {
--duration: 1.6s;
}
}</code></pre>
<p>Since we’re using the same <code>0%,80%</code> stops on the <code>graphicDetail</code> keyframes, setting a different duration on each snapshot causes them to start and end at different times.</p>
<p>The last element we need to transition is the StreetCred title. It’s the ending, the “ta-da!”, like a movie trailer or teaser, tie a bow on the whole thing with the name.</p>
<figure>
<video src="https://stuff.tylergaw.com/post-complex-view-transitions/work-transitions-4.mp4" controls=""></video>
<figcaption>fig 8: The title transitioning into place.</figcaption>
</figure>
<p>This follows the same patterns as the other elements. Apply an animation with our timing trick to the new snapshot, set the properties we want to transition, and watch it go. It doesn’t wait for the three detail elements to complete. It starts transitioning in the final milliseconds of the last one, with a light whisping into place to complete the composition.</p>
<h2>The other pages</h2>
<p>The other project pages and the about page follow the same patterns. They’re less complex, but all have multiple elements that transition in to make an interesting composition.</p>
<figure>
<video src="https://stuff.tylergaw.com/post-complex-view-transitions/work-transitions-5.mp4" controls=""></video>
<figcaption>fig 9: The Limbo project hero transition</figcaption>
</figure>
<p>The speed and duration of each transition is meant to reflect to overall tone of the project. The Groundwork is notably less snappy than the others to give it a bit of an airy, “ahhhh”-choir feel.</p>
<figure>
<video src="https://stuff.tylergaw.com/post-complex-view-transitions/work-transitions-6.mp4" controls=""></video>
<figcaption>fig 10: The Groundwork project hero transition</figcaption>
</figure>
<figure>
<video src="https://stuff.tylergaw.com/post-complex-view-transitions/work-transitions-7.mp4" controls=""></video>
<figcaption>fig 11: The Building OS X Apps with JavaScript project hero transition</figcaption>
</figure>
<figure>
<video src="https://stuff.tylergaw.com/post-complex-view-transitions/work-transitions-8.mp4" controls=""></video>
<figcaption>fig 12: The about page hero transition</figcaption>
</figure>
<p>Worth mentioning again that this is just CSS with a touch of HTML. Also worth pointing out that very little of this is specific to CSS view transitions. 90% of this is CSS keyframes coupled with principles of good animation.</p>
Using ChatGPT to Write CloudFormation2023-10-18T00:00:00Zhttps://tylergaw.com/blog/chatgpt-cloudformation/<p><a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html">CloudFormation</a> is not fun to write. The documentation is obtuse. There’s 20 different ways to do everything. Time spent on it feels like yak shaving at best and a total waste at worst. Having said that, the infra provisioning setups you can make with it are helpful and, for most things beyond small personal projects, necessary. So, I’ve been doing what every lazy programmer does. I’m getting a computer to do the unfun work for me.</p>
<h2>The project</h2>
<p>For a client project I’m working on right now, I’m using AWS Lambda with EventBridge, Secrets Manager, and CloudWatch. It’s a small project. What I’ve built is a Lambda function that runs at scheduled times every day. Each function invocation takes a certain input that tells the function to complete a certain task. The tasks all interact with the <a href="https://www.bullhorn.com/">Bullhorn</a> API to modify various records. I’m also using <a href="https://aws.amazon.com/serverless/sam/">AWS SAM</a> on the project.</p>
<h2>The infra</h2>
<p>I’m not an expert in infra provisioning and not an expert in AWS services. But, I’ve used AWS enough over the years to have an understanding of what’s offered and, for the most part, what’s possible. Because of that, I’m able to describe the AWS setup I want in English but, I struggle at times to find the exact CloudFormation incantation I need to accomplish what I’m looking for. Again, that’s in large part because the documentation is all over the place. (<strong>Side note</strong>: I realize there are alternatives to CloudFormation, which I’ve used and will use on other projects, but for this one, I’m using CloudFormation.)</p>
<p>This is where ChatGPT is a perfect tool. It’s hoovered up all of the scattered documentation and blog posts about CloudFormation and can take my specific, but unstructured, infra description and give me back the structured CloudFormation YAML.</p>
<h2>The prompts</h2>
<p>When I started did this, I had a rough CloudFormation template that I generated with the SAM CLI and modified for the project. Here’s the first few lines of it:</p>
<pre><code class="language-yaml">AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
bullhorn-entity-updater
Globals:
Function:
Tracing: Active
Resources:
BullhornEntityUpdaterFunction:
Type: AWS::Serverless::Function
Properties:
Description: A Lambda function with multiple available tasks to update Bullhorn entities.
Runtime: nodejs18.x
# ...template continues, truncated for display
</code></pre>
<p>I fed the template into ChatGPT and told it that I needed help adding resources. There’s nothing sensitive in it, so I can share the <a href="https://chat.openai.com/share/4ce3ff0a-dece-4834-bdd4-d4a82a499c3e">full chat log</a> that I went back to over a couple days.</p>
<p>For the most part, it’s what you’d expect. After setting the scene, I started prompting with:</p>
<pre><code class="language-text">First, can we add 3 aws cloudwatch events:
- Each one can invoke the BullhornEntityUpdaterFunction
- Each one is on a cron schedule at 1am daily
- Each one sends `{"task": "noop"}` as input
</code></pre>
<p>It responded back with a brief and overly enthusiastic explanation about what it had done and then gave me the complete YAML with the events added in.</p>
<pre><code class="language-yaml">...
Events: # <-- Add this Events section
DailyTrigger1:
Type: Schedule
Properties:
Schedule: cron(0 1 * * ? *)
Input: '{"task": "noop"}'
DailyTrigger2:
Type: Schedule
Properties:
Schedule: cron(0 1 * * ? *)
Input: '{"task": "noop"}'
DailyTrigger3:
Type: Schedule
Properties:
Schedule: cron(0 1 * * ? *)
Input: '{"task": "noop"}'
...
</code></pre>
<p>Good so far. From there, I could make changes to the names, cron, and input as needed. You can see from my prompt that I understand what I want. This helps me get there.</p>
<p>Something that’s very helpful is being able to ask clarifying questions to help me gain a better understanding. For example, my next prompt:</p>
<pre><code class="language-text">If I change `DailyTrigger1` `DailyTrigger2` `DailyTrigger3`
will that change the name of the event in the AWS console?</code></pre>
<p>ChatGPT gives a detailed answer:</p>
<pre><code class="language-text">Yes, changing the names DailyTrigger1, DailyTrigger2,
DailyTrigger3 in the CloudFormation template will change the
logical IDs of those AWS CloudWatch Event rules. However, it
won't directly affect the displayed names in the AWS Console.
In AWS CloudFormation, these are known as logical IDs, which
are used to uniquely identify a resource within a template.
...answer continues</code></pre>
<p>These are the types of questions I can’t ask static documentation and that is difficult to Google for.</p>
<p>Another interesting exchange later was me asking for Lambda insights.</p>
<pre><code class="language-text">Can we update the function to enable lambda insights?</code></pre>
<p>Which produced a response and the following snippet</p>
<pre><code class="language-yaml">...
Layers:
- !Sub 'arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14' # Please note that the version number (14 here) may change
Policies:
- Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- "cloudwatch:PutMetricData"
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource: "*"
...</code></pre>
<p>This was an instance where I knew more about what I wanted and this wasn’t it. I knew there was a managed policy for this instead of listing the actions like this. So, I needed to edit my prompt to get ChatGPT closer. First I tried:</p>
<pre><code class="language-text">Can we update the function to enable lambda insights following
best practices?</code></pre>
<p>It came back with the same listed actions so I had to get more specific:</p>
<pre><code class="language-text">Can we update the function to enable lambda insights
using a managed policy?</code></pre>
<p>That’s what it needed. Along with a detailed explanation it gave back this YAML:</p>
<pre><code class="language-yaml">BullhornEntityUpdaterFunction:
Type: AWS::Serverless::Function
Properties:
...
Policies:
- arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy
Layers:
- !Sub 'arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14'
</code></pre>
<p>Again, this was me having enough knowledge about what I wanted to be able to guide ChatGPT to the best outcome. Note that in that exchange I’m editing the prompt, not creating new prompts. That’s a habit I’ve picked up from Jeremy Howard’s excellent “<a href="https://youtu.be/jkrNMKz9pWU?si=w5MH0Hh9KzGG891k">A Hackers' Guide to Language Models</a>”.</p>
<p>It’s worth reading through the <a href="https://chat.openai.com/share/4ce3ff0a-dece-4834-bdd4-d4a82a499c3e">full chat log</a>. There are more interesting little bits that show how I’m going back and forth to make sure I’m getting what I need and that I understand what we’re doing.</p>
<p>So, not the singularity, but, this is the most practical, day-to-day helpful thing I’ve done with any LLM so far. Coming up with ideas for infrastructure is fun. Writing YAML is not. Having computers to do the unfun, tedious parts of the job is what they’re here for.</p>
The Native Node Test Runner is Great2023-09-26T00:00:00Zhttps://tylergaw.com/blog/node-test-runner/<p><b>TL;DR:</b> There’s a <a href="https://nodejs.org/docs/latest-v20.x/api/test.html">native test runner in node</a>. It’s great. You should use it on your next project.</p>
<p>Things like this just feel good. Any time you can switch from using an external dependency to something built-in feels like taking off heavy winter boots. If you haven’t used it yet, node’s <a href="https://nodejs.org/docs/latest-v20.x/api/test.html">native test runner in node</a> is excellent. It’s available in node verions <code>16.17+</code>, <code>18+</code>, and <code>20+</code>. I’ve been using it on a couple small projects recently and so far it’s done everything I need a test runner to do and just feels right.</p>
<p>I set up a <a href="https://github.com/tylergaw/node-test-runner-demo">demo repo</a> to show how I’m using it. There’s not much there, and that’s the point. I have two scripts in <code>package.json</code>:</p>
<pre><code class="language-bash">"test": "node --test",
"test:watch": "node --test --watch ."
</code></pre>
<p>In <code>add.js</code> I have a single function that requires two arguments.</p>
<pre><code class="language-javascript">export default function add(a, b) {
if (!a || !b) {
throw Error("The add function requires 2 arguments");
}
return a + b;
}</code></pre>
<p>Then in <code>add.test.js</code> I import a few native modules and write tests like normal:</p>
<pre><code class="language-javascript">import { describe, it } from "node:test";
import assert from "node:assert/strict";
import add from "./add.js";
describe("The add function", () => {
it("#add throws without two arguments", () => {
assert.throws(() => {
add();
}, Error);
assert.throws(() => {
add(2);
}, Error);
});
it("#add returns the expected result", () => {
assert.equal(add(5, 4), 9);
});
});</code></pre>
<p>I use the strict assert. Which is <code>===</code> instead of <code>==</code>. That’s not a requirement, just a preference.</p>
<p>For one off tests and for a CI, I run <code>npm test</code>. When I’m working locally I keep <code>npm test:watch</code> running. The docs for the <a href="https://nodejs.org/docs/latest-v20.x/api/test.html">runner</a> and for <a href="https://nodejs.org/api/assert.html">assert</a> are clutch.</p>
<p>This just feels light and correct and I think it’s great. That’s all.</p>
CSS Trig Functions, CSS Typed OM, and Pulling on Threads2023-09-25T00:00:00Zhttps://tylergaw.com/blog/css-typed-om-thread/<p><b>TL;DR:</b> I’m contributing to a <a href="https://github.com/tylergaw/css-typed-om">CSS Typed OM polyfill</a> for browsers (and maybe jsdom).</p>
<p>It started off like it usually does; I had an idea. Dangerous, I know. I’d been learning about, tinkering with <a href="https://web.dev/css-trig-functions/">CSS Trig functions</a>. They’re very cool. Seems like there’s a lot of potential uses for them. My problem, though, is I can’t visualize them. I can put them in CSS and see the effect they have, but I have trouble being deliberate about the values I pass to them because I’m not sure what the result will be.</p>
<p>Take <code>cos(25deg)</code> for example. I know that will return a number between -1 and 1. But what number? The last time I used <code>cos</code>, <code>sin</code>, <code>tan</code>, et al was in my high school trig class over 20 years ago. I can’t picture it, so I need a way to actually see the result.</p>
<p>The idea is small. I want a tool where I can plop in any CSS trig function; <code>cos(25deg)</code>, <code>sin(pi / 5)</code>, <code>tan(0.125turn)</code>, etc. and see the number it produces. <code>0.9063077870366499</code>, <code>0.5877852522924731</code>, <code>0.9999999999999999</code>, and so on. Going further, I want the calculations to run exactly how CSS runs them. So, not a JavaScript approximation like <code>Math.cos</code>, <code>Math.sin</code>, <code>Math.tan</code>, etc. (I realize under the hood these might be similar or the same, but still)</p>
<p>Why a different tool, why not just apply functions to CSS properties and then use the inspector to see the value? Because you can’t see the exact value of just the trig function. You can do something like <code>width: calc(1px * cos(25deg))</code> then check the <code>width</code> value in the devtools computed styles panel and get close, but not exact. Also, <code>width: cos(25deg)</code> is invalid CSS and using a custom prop like <code>--v: cos(25deg)</code> doesn’t really work either because the custom prop value is stored as <code>cos(25deg)</code>. I want/need something else.</p>
<p>I started working on this. And by “started working” I mean I spent a half hour finding a domain name for this non-existent tool. At some point this thing might be at <code>https://cssfunctions.style</code>. Then, I actually built a rough proof of concept. It’s on <a href="https://codepen.io/tylergaw/pen/QWzdrWN?editors=1010">CodePen</a> at the moment. Right now, it only handles a few trig functions, but it’s enough to see that it’s possible and to see what else I need to do to get it working enough for me needs. So, success? Sorta.</p>
<h2>CSS Typed OM</h2>
<p>The key bit of code in the POC is <code>CSSStyleValue.parse</code>. That’s a feature of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSS_Typed_OM_API">CSS Typed Object Model API</a>. A not new, but not very familiar Web API. At the time of this post, CSS Typed OM is implemented in Chrome, Edge, Safari, and Opera. But, not implemented at all in Firefox. Looking at the <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1278697">Bugzilla ticket</a>, it doesn’t look like it will be implemented any time soon. The ticket was opened seven years ago and the only recent activity is it being marked as unassigned.</p>
<p>A search led me to a <a href="https://github.com/csstools/css-typed-om">typed OM polyfill</a> built by <a href="https://jonneal.dev/">Jonathan Neal</a>. He does a ton of excellent work, so I knew it was legit. And it was, but it’s dated. The last work was done 2018. Pull requests from over three years ago are still open. Then there’s the <a href="https://github.com/csstools/css-typed-om/issues/9">familiar GitHub issue asking “Is this dead?”</a>. That issue was opened three years ago and the last comment, before mine two days ago, was in 2022. So, safe to say the project, in its orginal state, is stalled.*</p>
<p class="note-special"><b>* Note:</b> That’s not a knock against Jonathan or the current owner of the repo at all. This is common. You start a project, get busy with other more pressing things, and then just don’t have the time or energy to work on it or even follow up on issues or pull requests. Check my repos and you’ll see this exact thing.</p>
<p>I started playing around with the polyfill just to see how close it was or wasn’t to the spec. The foundation is super solid. I don’t know what the spec was like in 2018, but it seems like Jonathan got a ton of what was around then in place. Then I had another dangerous thought, maybe I can continue work on it? The code is all JavaScript and organized very well. It would be easy to see which features exist and which don’t. There are patterns in place to follow so there wouldn’t be a “blank page” problem. And there are live implementations of the API to reference.</p>
<p>So, I forked the repo, picked a small unimplemented feature and started working on an implementation. And, it worked. Which is both good and bad. Good because I may be able to get the polyfill to a point where I can use it in my trig functions tool. Bad because I’m letting myself completely nerd out on it. Which leads me to the last part of this.</p>
<h2>Pullin’ threads</h2>
<p>It’s rare that you start work on a project and work on just the project. There’s always other “stuff” that you have to get into. Sometimes it’s to fix underlying issues, sometimes to shave a yak or shed a bike. But, it’s rare–at least in my case—to have a project where I’m able to let myself pull any thread and keep pulling it. Right now, though, I’m lucky (from some angles) because I don’t have a full time job. So for this project, I get to pull the threads and do the work that follows. Since I have the time, and it’s super interesting, I'm full on nerding out on this one.</p>
<p>With that, I’m already deep into work on <a href="https://github.com/tylergaw/css-typed-om">my fork</a> of the polyfill. My goal is to get it fully functional. Or close enough. It may not have or need everything in the spec, but there’s no reason it can’t be a useful tool for unsupported browsers and, I think, jsdom. I did some early jsdom testing and it seems like it will work. There are some CSS things that just can’t happen without a browser, but most of the features can work.</p>
<p>A big chunk of work so far was documenting <a href="https://github.com/tylergaw/css-typed-om#feature-implementation-status">feature implementation status</a>. There’s still work to be done there, but I needed to have some type of roadmap. Other than that, I’m just picking off features and working on them. I’m having a lot of fun because I’m getting to try out new tools and use JavaScript in a way that I don’t normally. Which is writing code to facilitate writing different code. I’m also taking time to deep read sections of the <a href="https://drafts.css-houdini.org/css-typed-om">draft spec for typed OM</a>. Definitely not the easiest thing to follow, but interesting. Gives me an even greater appreciation for everyone that implements features from specs in browsers.</p>
<p>As always, I’ll make a fairweather attempt to write about some of the interesting things I get into here as I build out the polyfill and when I get back to the original CSS functions project.</p>
CSS Scroll-Driven Write-on Text Effect2023-07-19T00:00:00Zhttps://tylergaw.com/blog/css-scroll-driven-write-on/<p class="entry-intro">
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_scroll-driven_animations">CSS scroll-driven animations</a> are a new feature that's in early days of rolling out to browsers. In short, they’re everything we love about CSS animations, but with a timeline based on scroll position instead of automatic timeline.
</p>
<p>This is my first time tinkering with them and so far they’re super fun. Being able to accomplish these effects with zero JavaScript is huge. This type of text write-on effect is very common in animation. Trying it with scroll animations was one of the first things that popped into my head.</p>
<p class="note-special">
<strong>Note:</strong> At the time of this post, CSS scroll-driven animations support is limited to Chrome 116+ which is only in Chrome Canary.
</p>
<p>Here’s the demo. It’s not perfect, but workable. A proper write-on text effect would
have the actual stroke of the letters written out instead of just a reveal like this.</p>
<div class="codepen" data-height="600" data-default-tab="css,result" data-slug-hash="dyQeJwJ" data-user="tylergaw" style="height: 600px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 1px solid; margin: 1em 0;">
<span>See the Pen <a href="https://codepen.io/tylergaw/pen/dyQeJwJ">
CSS scroll-driven text write-on</a> by Tyler Gaw (<a href="https://codepen.io/tylergaw">@tylergaw</a>)
on <a href="https://codepen.io/">CodePen</a>.</span>
</div>
<script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
<p>And a video showing the effect if you don't want to mess with Chrome Canary.</p>
<figure>
<video src="https://p197.p4.n0.cdn.getcloudapp.com/items/RBuJmk1m/a4a420c1-4fd9-4a3b-848e-2f7582300919.mp4?source=viewer&v=bf40343d6e3cd9c35bd789a249dde455" controls=""></video>
</figure>
<p>While it takes time to figure out the nuances of how scroll animations work, this demo is fairly straightforward to accomplish. I’m animating the width of a <code>clip-path</code> <code>polygon</code> to reveal each line of text. Instead of the animation running immediately, the progress of it is tied to the scroll position of the <code>p</code> element for the line of the poem.</p>
<p>The full code is available in the <a href="https://codepen.io/tylergaw/pen/dyQeJwJ">CodePen</a>, the core bits are as follows:</p>
<pre><code class="language-css">@keyframes write {
0% { clip-path: polygon(0 0, 0 0, 0 100%, 0 100%); }
100% { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); }
}
main p {
animation: write linear both;
view-timeline-name: --written-text;
view-timeline-axis: block;
animation-timeline: --written-text;
animation-range: entry-crossing 30% contain 45%;
}</code></pre>
<ol>
<li>Declare a <code>@keyframes</code> animation named <code>write</code> that increases the width of a <code>clip-path</code> from 0 to 100%.</li>
<li>Apply the <code>write</code> animation to each <code>p</code></li>
<li>Give the timeline a unique name, <code>--written-text</code>, and set that as the <code>animation-timline</code>. This overrides the standard, automatic <code>animation</code> timeline</li>
<li>Using <code>animation-range</code>, refine how the animation progresses related to the scroll position of each <code>p</code></li>
</ol>
<p>That last point for <code>animation-range</code>, that’s where I spent the most time. And where we have the most control over each animation. There’s a ton of different possible variations. Luckily <a href="https://www.bram.us/">Bramus</a> made an <a href="https://scroll-driven-animations.style/tools/view-timeline/ranges">excellent tool to visualize it</a>.</p>
<p>This is just one effect that’s possible with scroll-driven animations. Another very welcome addition to CSS. If you’re looking for an intro to them, again Bramus has you covered with <a href="https://developer.chrome.com/articles/scroll-driven-animations/">“Animate elements on scroll with Scroll-driven animations”</a>.</p>
First Experiments with View Transitions for Multi-page Apps2023-05-26T00:00:00Zhttps://tylergaw.com/blog/view-transitions-first-experiments-mpa/<p>Like a lot of other people right now, I'm excited about the in-development <a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API">View Transitions API</a>. I'm extra excited about using it without JavaScript for multi-page apps (sites, whatever). <a href="https://daverupert.com/2023/05/getting-started-view-transitions/">Dave's excellent post</a> got me set up with the basics. I've spent the last day or so experimenting, playing, and learning as much about the topic as I can.</p>
<p>As I'm learning, I'm putting together a <a href="https://github.com/tylergaw/mpa-view-transitions-sandbox">repo of example transitions</a> and a <a href="https://mpa-view-transitions-sandbox.netlify.app/">site with the live examples</a>.</p>
<p class="note-special">
<strong>Note:</strong> At the time of this post, view transitions are still very much a work in progress. That means browser support is limited. Examples may not work for you or might break. To use in Chrome or Arc, enable two feature flags:
<code>chrome://flags#view-transition</code> and <code>chrome://flags#view-transition-on-navigation</code>
</p>
<h2>What am I learning?</h2>
<p>If you're looking for an intro to view transitions or details on initial setup, see <a href="https://daverupert.com/2023/05/getting-started-view-transitions/">Dave's post</a> and <a href="https://adactio.com/journal/20195">Jeremy's post</a>. I'm assuming some prior knowledge with this post. What I'll share here are the “ah-ha!” or “that's interesting” moments. Things that I could only discover by getting in and playing around with different use cases. For new features like this you have to get your hands on it to get a feel for what you can do.</p>
<h3>You can scope transition selectors</h3>
<p>Pseudo-elements are the CSS hook to customize transitions. There are five of them in a tree structure. <a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API#the_view_transition_process">Source</a>.</p>
<pre><code class="language-css">::view-transition
└─ ::view-transition-group()
└─ ::view-transition-image-pair()
├─ ::view-transition-old()
└─ ::view-transition-new()</code></pre>
<p>Browsers generate this set of pseudo-elements for the root and all named view transitions. I haven't done anything with <code>::view-transition-group</code> or <code>::view-transition-image-pair</code> yet so I can't speak to what's possible with them. So far, I've used <code>::view-transition-old</code> and <code>::view-transition-new</code>.</p>
<p>From the MDN docs:</p>
<blockquote>
<p><code>::view-transition-old</code> is the screenshot of the old page view, and <code>::view-transition-new</code> is the live representation of the new page view.</p>
</blockquote>
<p>In examples, I'd only seen those selectors used by themselves like:</p>
<pre><code class="language-css">::view-transition-old(root),
::view-transition-new(root) {/*...*/}</code></pre>
<p>I wondered, “can these be scoped?”. Yeah they can. We can use any id, class, or other valid attribute selector for more specific scoping/targeting. This was an “ah-ha!” I guess it shouldn't have been. These are standard pseudo-elements, like <code>::before</code> or <code>::after</code>. Something about new additions to CSS gives them a bit of mysterious vibe to me. Takes me a bit to realize I can work with them in familiar, standard ways.</p>
<p>So, why is scoping cool? In short, you can use scoping to have unique transitions. Could be unique per-page, per-project, per-brand, and so on. Unique transitions per-page was the first one I tried.</p>
<p>For this example, I wanted each of four pages to all slide in and out of the screen from and to a different direction. From right to left, bottom to top, left to right, and top to bottom. You can see a live example at <a href="https://mpa-view-transitions-sandbox.netlify.app/unique-page-slide/">mpa-view-transitions-sandbox.netlify.app/unique-page-slide/</a>. If you haven't enabled the feature flags to see that, here's a video:</p>
<figure>
<video src="https://p197.p4.n0.cdn.getcloudapp.com/items/JruejBeP/b38a000f-102d-4147-a0c5-34e311fc2043.mp4?source=viewer&v=80324ae5755f4a11b3e1c97f989e499a" controls=""></video>
<figcaption>fig 1: Live example available <a href="https://mpa-view-transitions-sandbox.netlify.app/unique-page-slide/">mpa-view-transitions-sandbox.netlify.app/unique-page-slide</a></figcaption>
</figure>
<p>This is a toy example, but it shows how powerful scoping transitions can be. Let's look at the HTML and CSS to make this happen. The <a href="https://github.com/tylergaw/mpa-view-transitions-sandbox/tree/main/unique-page-slide">full code</a> is on Github.</p>
<p>After initial setup of adding the transitions <code>meta</code> tag to every page <code><meta name="view-transition" content="same-origin" /></code>, we need an attribute to use for scoping.</p>
<pre><code class="language-html"><html data-page="home">...</html>
<html data-page="one">...</html>
<html data-page="two">...</html>
<html data-page="three">...</html></code></pre>
<p>This doesn't have to be a data attribute, only my preference. Could be a class or id or any other attribute. These will use the <code>root</code> transition, so the attribute does need to be on the <code>html</code>, or root, element.</p>
<p>With the data attributes in place, we can use them in CSS to scope our <code>-old</code> and <code>-new</code> pseudo-elements for each page:</p>
<pre><code class="language-css">[data-page="home"]::view-transition-old(root) {...}
[data-page="home"]::view-transition-new(root) {...}
[data-page="one"]::view-transition-old(root) {...}
[data-page="one"]::view-transition-new(root) {...}
/* ...and so on for the other pages */</code></pre>
<p>This lets us customize the transition in and out for every page. Let's look at Page 1. When we navigate to Page 1, we want to see the entire page slide in from the bottom to the top. We target the <code>-new</code> element for this:</p>
<pre><code class="language-css">[data-page="one"]::view-transition-new(root) {
animation: slideUp 0.3s;
}</code></pre>
<p><code>slideUp</code> is a <code>@keyframes</code> declaration that starts the page off the screen to the bottom and then animates it to the top:</p>
<pre><code class="language-css">@keyframes slideUp {
from { transform: translateY(100vh) }
to { transform: translateY(0) }
}</code></pre>
<p>When we navigate away from Page 1, we want to see it continue on its path by sliding out to the top. We target the <code>-old</code> element for this:</p>
<pre><code class="language-css">[data-page="one"]::view-transition-old(root) {
animation: slideOutUp 0.3s;
}</code></pre>
<p>Again, <code>slideOutUp</code> is a <code>@keyframes</code> declaration. I'll leave the code out for brevity. The <a href="https://github.com/tylergaw/mpa-view-transitions-sandbox/blob/main/unique-page-slide/unique-page-slide.css">full stylesheet</a> is on Github.</p>
<p>We follow this same pattern for the other pages. For each, we set a custom animation on the <code>view-transition-new</code> and <code>view-transition-old</code> pseudo-elements. The animations use a different <code>transform</code> and different values to get the transition we're after.</p>
<p>Scoping isn't limited to the <code>root</code> transition. The same concept works for named transitions. I don't have a working example, but lets say we have a content element on each page that we want to transition. The transition is the same on most pages, but we have certain ones we want a different transition. It might look something like this:</p>
<pre><code class="language-css">::view-transition-new(content) {
animation: standard 0.2s;
}
.special-page::view-transition-new(content) {
animation: special 0.4s;
}</code></pre>
<p>I haven't explored scoping named transitions much yet, but it feels like it opens up a lot of possibilities.</p>
<h3>Elements don't have to be on both pages to transition</h3>
<p>This is a “that's interesting”. Also maybe a “duh” in hindsight, but took me a bit to figure out. To get an element to transition or “morph” between two pages, you give that element a transition name:</p>
<pre><code class="language-html"><div style="view-transition-name: content" /></code></pre>
<p>If an element with that name is present on both the current page and the page you’re navigating to, the element will morph between the elements’ shape, size, color, etc. I thought this was the only way to transition elements. To have the element be on both pages and morph. That's not the case. You can transition an element in and out that only exists on the page you're navigating.</p>
<p>This is a little tough to explain in words, <a href="https://mpa-view-transitions-sandbox.netlify.app/grid-item-view/">here's an example</a>. And again, a video in case these aren't working for you yet.</p>
<figure>
<video src="https://p197.p4.n0.cdn.getcloudapp.com/items/ApugQ4b1/69486edd-3fd5-4398-b8a2-9115f14837d4.mp4?source=viewer&v=401646be1fbea0cbcdf5bdbb8641d9a6" controls=""></video>
<figcaption>fig 2: Live example available <a href="https://mpa-view-transitions-sandbox.netlify.app/grid-item-view/">mpa-view-transitions-sandbox.netlify.app/grid-item-view</a></figcaption>
</figure>
<p>The title, description, and graphic for each item exists on both the list and single item page. So, those elements morph between each page. The “All items” button is only on the single item pages. But, we're still able to give it a transition name and, in this case, a custom animation. A custom animation isn't required, but you likely don't want the default for something like this.</p>
<p>To do this, we set a <code>view-transition-name</code> in the HTML. This is a normal CSS property, so we could also set this on the <code>small</code> element in CSS. I just chose inline because I wasn't applying any other styles.</p>
<pre><code class="language-html"><small style="view-transition-name: all-items-link">
<a href="./">< All items</a>
</small></code></pre>
<p>Then, in the CSS, we create a custom animation and apply it to our <code>-new</code> element for the transition name:</p>
<pre><code class="language-css">::view-transition-new(all-items-link) {
animation: itemsLinkIn 0.3s;
}</code></pre>
<p>This gives a lot of control over how elements transition or, as I think about it, how they enter and exit the stage. <a href="https://github.com/tylergaw/mpa-view-transitions-sandbox/tree/main/grid-item-view">Full code</a> for this example on Github.</p>
<h2>What’s next?</h2>
<p>It’s early days for this. I only have a few hours of tinkering so far. It’s already clear just how powerful these can be and how well designed a spec and feature it is. A couple oddities aside, things just kind of work as expected.</p>
<p>Building these <a href="https://mpa-view-transitions-sandbox.netlify.app/">isolated examples</a> is helpful to get the feel of things, and good for sharing what’s possible. But what will really open this up is to have specific design choices to go after. Meaning, having pages designed with transitions in mind and then trying to execute on those designs. You can only learn so much with contrived examples.</p>
<p>I’ve started exploring ideas on this site. I have a lot of detailed layouts and graphics thoughout. I’m working on designing transitions that aren’t just standard fades and wipes per page, but ones tailored to the contents of individual pages. I don’t have anything live yet, but this video shows some of the early ideas I've been playing with. Nothing sticking so far, I'm doing kinda out-there, garish things right now to stress test and see what works and what doesn’t.</p>
<figure>
<video src="https://p197.p4.n0.cdn.getcloudapp.com/items/2NupJ4mG/66ea8402-dd8b-460b-b3da-6fc7aba4f2b2.mp4?source=viewer&v=4f8a9c5dd92462027ef492faac87f7d9" controls=""></video>
<figcaption>fig 3: Early transition design exploration on tylergaw.com</figcaption>
</figure>
<p>It’s easy to see how far we can push this. And all of this is with <strong>HTML and CSS, no JavaScript</strong>. As with all design elements, it will take restraint to design compelling transitions that aren’t annoying. I’m certain we’ll go too far and have to reel it back it. So it goes.</p>
<p>Lastly, this is all additive. With every new web technology, we get the chorus of; “Yeah, but when will it be supported?” Today, it’s supported today in some browsers, but not all. If you look at any of the examples on a browser that does not support them, the pages still function just fine. The transitions are an extra that’s layered on top if and when your browser supports them. Another concrete example of progressive enhancement in practice.</p>
Refreshing Colormix2023-05-16T00:00:00Zhttps://tylergaw.com/blog/colormix-refresh/<p><a href="https://tylergaw.com/blog/colormix-mvp/">Last summer</a>, I built a tool to help visualize the CSS <code>color-mix</code> function. <a href="https://www.colormix.style/">colormix.style</a> lets you mix two colors in a given color space and generates the CSS snippet needed to do that with <code>color-mix</code>.</p>
<p>For the MVP, I was focused on building the bare minimum to get the idea across. Since then, browser support for <code>color-mix</code> as improved. More people are experimenting with and <a href="https://developer.chrome.com/articles/high-definition-css-color-guide/">writing about</a> <code>color-mix</code> and the huge amount of new color-related features in CSS. And, my understanding of <code>color-mix</code> and more importantly how new CSS color capabilities work as expanded.</p>
<p>With all that, I’m taking the time to iterate <a href="https://www.colormix.style/">colormix.style</a> beyond an MVP into a more robust, more useful tool.</p>
<h2>What’s new?</h2>
<p>To be able to make the larger functionality and usability improvements I wanted, the first thing I needed to do was take another stab at the overall UI. I had an idea for what I wanted when I designed the MVP, a spartan UI where every pixel of the UI was part of the tool, free from any design embellishments. That idea can work, but I didn't execute on it well enough.</p>
<p>It also felt like my original design put too much emphasis on the color input vs the output. The two color inputs, while important, aren't the main idea. The output color of the operation is.</p>
<p>With those things in mind, I got to work reimagining and refreshing the UI.</p>
<h3>MVP design</h3>
<figure>
<picture>
<img src="https://tylergaw.com/blog/assets/post-image-colormix-mvp-screenshot.jpg" alt="A screenshot of colormix.style" />
</picture>
<figcaption>fig 1: <a href="https://www.colormix.style/">colormix.style</a> MVP design that was functional, but left a lot of room for improvement.</figcaption>
</figure>
<p>The basic utility was there, but it just wasn't what I knew it could be. As I used it more, and thought about new functionality I wanted to include, this design wasn't going to work going forward. Also, aesthetically it wasn't at the level of completeness or fidelity that I wanted.</p>
<h3>Refreshed design</h3>
<figure>
<picture>
<img src="https://tylergaw.com/blog/assets/post-image-colormix-refresh-after.jpg" alt="A screenshot of colormix.style showing the improved design" />
</picture>
<figcaption>fig 2: <a href="https://www.colormix.style/">colormix.style</a> Improved design</figcaption>
</figure>
<p>First, just aesthetically, this is easier to look at. Part of that is just due to me spending more time to get details right. And I think I think I "designed the bad" out with the MVP, which is a common exercise for me. The focus is now clearly on the output color. I have more to do here, but this sets the foundation for those future improvements.</p>
<p>I let go of trying to have every pixel be part of the functionality. There’s whitespace in the header and footer and around the color output container. It's not a ton, but just enough to help push focus to the interactive elements on the page.</p>
<p>This is a utility tool so functionality will always be key, but I also let myself get away with a couple design flourishes with the plus and arrow icons. A couple tiny details to break up the matter-of-factness of the rest of the design.</p>
<h4>Removing the percentage slider</h4>
<p>One thing I removed was the slider to control the mix percentage. The number input is the only way to control mix percentage now. We'll see if this ends up being the right decision. I took inspiration from design tools for this. I noticed that in Figma when controlling percentages like opacity, the only control is the text input. I've never once wanted a slider for that. I think that's partially because we don't tend to make large up or down changes. If we do need to go from say 100% to 10%, it's faster to backspace or just type "10".</p>
<p>One improvement I'll add here is the ability to hold the shift key with the up and down arrow keys to increment by 10. That's something I reach for in Figma and other similar tools often and feels necessary.</p>
<h2>What’s next?</h2>
<p>This UI refresh is a first and largely cosmetic step for improving the tool. I have a list of new functionality I'm working on already and will continue to work on in the coming weeks/months.</p>
<p>The most important functionality addition will be to allow for all color spaces in color inputs. Right now I only have rgb via hex. This drastically reduces the functionality and blunts the future-looking nature of the tool. The introduction of new color spaces in CSS is huge and exciting. This tool will provide a way to work with them soon.</p>
<p>You can follow the full list of what I'm up to on <a href="https://github.com/tylergaw/colormix.site/issues">Github issues</a>. If you see something broken or have an idea, feel free to add an issue there.</p>
<h2>Credits</h2>
<p>I've been taking heavy UI and functionality inspiration from Adam Argyle’s <a href="https://gradient.style/">gradient.style</a> and learning a ton about new CSS color capabilities from his article <a href="https://developer.chrome.com/articles/high-definition-css-color-guide/">"High Definition CSS Color Guide"</a>.</p>
Re-encode AVIF images for iOS 162023-01-29T00:00:00Zhttps://tylergaw.com/blog/ios-16-avif-fix/<p>In late 2022, Apple <a href="https://webkit.org/blog/13152/webkit-features-in-safari-16-0/">announced support</a> for AVIF images on iOS 16+. That was welcome news, but did lead to issues. I don't know how wide spread this issue is, but for this site, most AVIF images I had in place stopped working. If you're searching for some version of; <strong>"iOS 16 AVIF images broken"</strong>, this might help you out.</p>
<p><strong>TL;DR</strong>: Re-encode any AVIF images that aren't working. There have been AVIF spec changes and there were bugs in popular encoders that caused AVIF not to work in iOS.</p>
<h2>The problem</h2>
<p>I've been using AVIF images throughout this site since about 2020. I use the <code>source</code> element with <code>srcset</code> to provide AVIF, WEBP, and JPG or PNG formats for most images. This worked until iOS shipped support for AVIF. Before then, iOS would just not recognize the AVIF format and use WEBP instead.</p>
<p>With AVIF support, iOS started recognizing and loading AVIF images. I could confirm that was the cause using the devtools network panel. But, on the page, there would be a missing image. Either blank, in Chrome iOS, or the little blue question mark in Safari iOS.</p>
<h2>The fix</h2>
<p>Re-encode any AVIF images. For me, this meant; tracking down original images and graphics in Figma, exporting has PNG or JPG, then converting those to AVIF using <a href="https://squoosh.app/">Squoosh</a>. Once I did that, AVIF images loaded and displayed as expected.</p>
<p>I had trouble finding details on this. What led me to even try re-encoding was <a href="https://github.com/Fyrd/caniuse/issues/6505#issuecomment-1289471190">Jen Simmons' comment</a> on a related issue:</p>
<blockquote>
<p>Please do create new images for your tests. The specs for AVIF changed since the early days. Image encoders have been updated — including in Sept 2022, when AVIF bugs in several of the common encoders were found.</p>
</blockquote>
<h2>Local trouble</h2>
<p>Something to watch out for. I have not been able to get any AVIF images to work on the iOS 16 Simulator on macOS Monterey 12.6.1. This may just be because I'm not on the latest macOS. If you're using a simulator and re-encoding does not seem to be working, make sure you try a real device. That's the only place I'm seeing AVIF images work.</p>
Visualizing CSS color-mix2022-06-29T00:00:00Zhttps://tylergaw.com/blog/colormix-mvp/<p>
TL;DR: <a href="https://www.colormix.style/">colormix.style</a> and <a href="https://github.com/tylergaw/colormix.site"> github.com/tylergaw/colormix.site</a>
</p>
<p>
Way back in 2017, I <a href="https://tylergaw.com/blog/introducing-colorme/">built ColorMe</a> for working with a proposed CSS color function. That proposal didn't end up sticking, so that <code>color</code> function is no longer valid. ColorMe is still a useful color picker though.
</p>
<p>
Now, in 2022, there's a proposal for a new CSS color function. This version is named <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix"><code>color-mix</code></a>. It's part of the <a href="https://drafts.csswg.org/css-color-5/#color-mix">CSS Color Module Level 5</a> spec. It allows you to mix two colors in a given colorspace in CSS. That's very cool.
</p>
<p>
Similar to the deprecated color function, I wanted a quick way to see the results and code snippet for <code>color-mix</code>. <strong>So I built a new tool to do just that; <a href="https://www.colormix.style/">colormix.style</a></strong>.
</p>
<figure>
<picture>
<img src="https://tylergaw.com/blog/assets/post-image-colormix-mvp-screenshot.jpg" alt="A screenshot of colormix.style" />
</picture>
<figcaption>fig 1: <a href="https://www.colormix.style/">colormix.style</a> MVP</figcaption>
</figure>
<h2>What does it do?</h2>
<p>
This is an MVP and something I'll keep working on. For now, it provides the basic functionality of <code>color-mix</code>. It allows you to pick two colors, set the percentage mix for each color, select a colorspace, and see the results and CSS snippet to produce it.
</p>
<h2>Support</h2>
<p>
Unless you're in the habit of fiddling with browser feature flags, this likely won't work completely for you. <code>color-mix</code> is only available in a couple browsers and those require a feature flag to be enabled. At the time of this writing, it's latest Firefox and Safari. Check <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix#browser_compatibility">MDN Browser compatibility</a> for details on how to enable it.
</p>
<p>
As always, I have a list of to-dos I want to get done and I want to write more about what I've learned researching and working with <code>color-mix</code>. This is a prompt and reminder for myself to do that. Let me know if you use ColorMix and find it useful or run into issues.
</p>
Realign 2020: Realigned2021-02-27T00:00:00Zhttps://tylergaw.com/blog/realign-2020-realigned/<p class="entry-intro">
A year ago today I wrote <a href="https://tylergaw.com/blog/realign-2020">Realign 2020</a>. A typical designery blog post
where I declared my intent to redesign and rebuild my site and write about it along
the way.
</p>
<p>Through 2020 I did that. I did the work. It's live now. I’m super happy with
the results. I didn't write all the posts I wanted to, but that's OK. Here's the posts I did write.</p>
<ul>
<li><a href="https://tylergaw.com/blog/realign-2020-typography">Realign 2020: Typography</a></li>
<li><a href="https://tylergaw.com/blog/realign-2020-logo">Realign 2020: Logo</a></li>
<li><a href="https://tylergaw.com/blog/realign-2020-color">Realign 2020: Color</a></li>
</ul>
<p>and the posts I planned, but didn’t write</p>
<ul>
<li>Realign 2020: Content</li>
<li>Realign 2020: Netlify</li>
<li>Realign 2020: Tooling Updates</li>
</ul>
<p>I did all the work that would have been in those posts, but just didn't write about it. I made the classic mistake. I started out with small updates; type, color, logo. And published those posts after each chunk of work. But then? I made a mess. I made a git branch with the ominous name "rebuild." So, of course, that turned into an amorphous catch-all. That <a href="https://github.com/tylergaw/tylergaw.com/pull/46">pull request</a> got so far ahead of the main branch that I had to do everything in it. And that made it so I didn't want to write blog posts because I was worried about merge conflicts and just general dred of having a huge pull request. I worked in that branch from August 2020 to February 2021.</p>
<p>This was fine though. When I started the realign in February 2020, I didn’t set a launch date for myself on purpose. Just 2020. I wanted to give myself the space to think and the time to work. And I think it shows in the results.</p>
<p>2020 was also a hard year. For everyone. The trauma of living through the combination of a pandemic and insurrection and existential climate crisis makes just a teensy bit difficult to wrok. The daily dred impacts the creative process in awful ways. I don't do this work because of external pressue. I do this work because I’m compelled to do it. I <strong>have</strong> to do it. I <strong>need</strong> to do it. That compulsion to do creative work doesn't go away during hard times. But the energy to act on that compulsion does. That leads to feelings of lack of accomplishment and a constant feeling of something in the distance that you know you want to get to, but can't will yourself to move towards. I guess? I dunno? Who knows? What was I even talking about? Oh yeah. Web design.</p>
<p>What I’ve landed on with this effort feels like the most accurate picture of my work and of me as a designer that I’ve had. It’s still not the full story. There's so much more that I want to archive on this site. And I will over time. That’s what I’m doing with this site. I’m creating a space where I can house as much information about my work as possible. For myself and for anyone that’s interested.</p>
<h2>What's next?</h2>
<p>This is me making another declaration of intent. In 2021 I want to keep working and writing about this website. I want to write about this most recent chunk of work. And I have bunch of other efforts in mind. Here's a list of topics I want to explore on this blog in 2021:</p>
<ul>
<li>Switching from <a href="https://metalsmith.io/">Metalsmith</a> to <a href="https://www.11ty.dev/">Eleventy</a>. This will be light, fun nerd stuff. And I hope helpful for other folks using Eleventy</li>
<li>How I created a little design world of structured chaos on the home page. I'm using classic grid design techniques to set rules. And then bending and breaking those rules to make things interesting. I’m also using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout">CSS Grid</a> extensively and will describe my approach to it</li>
<li>Accessibility (a11y) efforts. This is a fancy, funky design, but it’s a website so it has to be accessible. I still have issues to work through, but I’ve taken a lot of care and code already and I want to share that</li>
<li>All the details! There's so many fun little things. I want to keep a ongoing series called; "Look at this!", or similar, to highlight specific areas of the design and how I built them</li>
<li>The illustrations! My portait and the Limbo elephant where huge efforts. I’ll share details about my process creating those</li>
<li>More about my approach to being creative. I'm constantly thinking about and struggling with serious vs solemn, as <a href="https://youtu.be/atn22-bmTPU">Paula Scher</a> so eloquently described</li>
</ul>
<p>and two archival efforts I've started and want to continue</p>
<ul>
<li>The Groundwork static archive. None of the work we did is still online. I’m going to archive as much of it as I can at <a href="https://theground.work/">theground.work</a> and invite the team to contribute</li>
<li>Limbo static archive. When we shut down Limbo, we took the site offline. Same as The Groundwork, I'm going to work on a static archive of the site for Chris and I</li>
</ul>
<p>That's not everything, but that's a lot. Here's to another year of enjoying the process of tending to a personal website.</p>
How to Get All Custom Properties on a Page in JavaScript2020-06-15T00:00:00Zhttps://tylergaw.com/blog/how-to-get-all-custom-properties-on-a-page-in-javascript/<p class="drop-capped">
When I have a question about how to do something with CSS, I often find the
answer on <a href="https://css-tricks.com/">CSS-Tricks</a>. I’m guessing
that’s true for a lot of you too. It’s been the go-to for CSS knowledge for as
long as I can remember.
</p>
<p>
If one day you find yourself searching for help working with CSS custom
properties in JavaScript, you might find yourself on an article on CSS-Tricks
written by me. Because, last Monday, June 8th 2020 I have an article published
on that invaluable website.
</p>
<p class="note-update">
<b>Update:</b> In early 2022, <a href="https://css-tricks.com/css-tricks-is-joining-digitalocean/">CSS-Tricks was bought by DigitalOcean</a>. Since then, they haven’t done much with it. At the time of this update, the last post was almost eight months ago. I don’t trust that they’ll be a good steward going forward. I’d saw we’ll start losing posts or the entire site will just go offline at some point in the not so distant future. I don’t want to lose the contents of my post, so I’m reposting it here, <a href="https://tylergaw.com/blog/how-to-get-all-custom-properties-on-a-page-in-javascript/#repost">down below</a>, where I know it will stay online.
</p>
<figure>
<a href="https://css-tricks.com/how-to-get-all-custom-properties-on-a-page-in-javascript/">
<picture>
<source srcset="https://tylergaw.com/articles/assets/post-image-how-to-get-all-custom-props-1.webp" type="image/webp" />
<source srcset="https://tylergaw.com/articles/assets/post-image-how-to-get-all-custom-props-1.png" type="image/jpeg" />
<img src="https://tylergaw.com/articles/assets/post-image-how-to-get-all-custom-props-1.png" alt="A screenshot of my article on CSS-Tricks.com" />
</picture>
</a>
<figcaption>
fig 1: How to Get All Custom Properties on a Page in JavaScript
<a href="https://css-tricks.com/how-to-get-all-custom-properties-on-a-page-in-javascript/">on CSS-Tricks.com</a>
</figcaption>
</figure>
<h2>The Process</h2>
<p>
I used the technique I describe in the article at work for a one-off project.
It was fun code to work on. It required a lot of following a trail of
different browser APIs. I latch on to work like that. After I had the code
working for the project I abstracted it to stand-alone examples. I started
writing a draft for my personal blog. But, I wrote too many words. I got
bogged down. The draft was over 3,000 words, wasn't finished, and wasn’t very
good. I couldn’t motivate myself to finish it. So, it sat. In a branch,
unfinished. Not the first blog post I’d abandoned.
</p>
<p>
Months later, I was listening to an
<a href="https://longform.org/posts/longform-podcast-387-eva-holland">interview with Eva Holland</a>
on the Longform Podcast. In it, she talked about how during her career she’d
cold pitched articles to editors at publications. This wasn’t the first time
I’d heard this from writers, but something about hearing it that time stuck
with me.
</p>
<p>
“<b>I should do that!</b>”, I thought. Specifically, I thought I should pitch
the custom properties article I’d given up on to the best web development
publication I could think of.
</p>
<p>
Was that a thing? Is that how articles on CSS-Tricks come to be? Turns out
that’s one way. I’d always assumed the only way to get an article on a site
like CSS-Tricks was to get an invitation to do so. That is not the case. They
have a
<a href="https://css-tricks.com/guest-posting/">pitch form</a> available to
everyone.
<a href="https://chriscoyier.net/">Chris even asks you to write a guest post on his site.</a>
</p>
<p>
So, I pitched the article idea. It wasn’t perfect and the draft wasn’t done.
Chris got back to me in a few days and said, “go for it.” It took me about a
month to get back up to speed on the code and write a full draft. I’m a slow
writer. Within a few days of submitting the draft,
<a href="https://geoffgraham.me/">Geoff</a> began the editing process. We did
a few rounds of copy edits, small style updates, and checks to make sure we
had consistent and accurate code samples and CodePen links. June 8th was the
day that fit the publishing calendar. And now I’m super excited to have it
live.
</p>
<p>
A post like this doesn’t get a million comments or make the front page of
Hacker News. That’s not its purpose. A post like this exists for the long
term. It’s a utility that will sit in wait until the day you need it. On that
day, it’ll be waiting for you, ready to help.
</p>
<h2 id="repost">Full article reposted from CSS-Tricks</h2>
<p class="note-special">
Originally published June 8, 2020 at <a href="https://css-tricks.com/how-to-get-all-custom-properties-on-a-page-in-javascript/">https://css-tricks.com/how-to-get-all-custom-properties-on-a-page-in-javascript/</a>
</p>
<p>We can use JavaScript to get the value of a CSS custom property. Robin wrote up a detailed explanation about this in <a href="https://css-tricks.com/get-a-css-custom-property-value-with-javascript/">Get a CSS Custom Property Value with JavaScript</a>. To review, let’s say we’ve declared a single custom property on the HTML element:</p>
<pre><code class="language-css">html {
--color-accent: #00eb9b;
}</code></pre>
<p>In JavaScript, we can access the value with <code>getComputedStyle</code> and <code>getPropertyValue</code>:</p>
<pre><code class="language-javascript">const colorAccent = getComputedStyle(document.documentElement)
.getPropertyValue('--color-accent'); // #00eb9b</code></pre>
<p>Perfect. Now we have access to our accent color in JavaScript. You know what’s cool? If we change that color in CSS, it updates in JavaScript as well! Handy.</p>
<p>What happens, though, when it’s not just one property we need access to in JavaScript, but a whole bunch of them?</p>
<pre><code class="language-css">html {
--color-accent: #00eb9b;
--color-accent-secondary: #9db4ff;
--color-accent-tertiary: #f2c0ea;
--color-text: #292929;
--color-divider: #d7d7d7;
}</code></pre>
<p>We end up with JavaScript that looks like this:</p>
<pre><code class="language-javascript">const colorAccent = getComputedStyle(document.documentElement).getPropertyValue('--color-accent'); // #00eb9b
const colorAccentSecondary = getComputedStyle(document.documentElement).getPropertyValue('--color-accent-secondary'); // #9db4ff
const colorAccentTertiary = getComputedStyle(document.documentElement).getPropertyValue('--color-accent-tertiary'); // #f2c0ea
const colorText = getComputedStyle(document.documentElement).getPropertyValue('--color-text'); // #292929
const colorDivider = getComputedStyle(document.documentElement).getPropertyValue('--color-text'); // #d7d7d7</code></pre>
<p>We’re repeating ourselves a lot. We could shorten each one of these lines by abstracting the common tasks to a function.</p>
<pre><code class="language-javascript">const getCSSProp = (element, propName) => getComputedStyle(element).getPropertyValue(propName);
const colorAccent = getCSSProp(document.documentElement, '--color-accent'); // #00eb9b
// repeat for each custom property...</code></pre>
<p>That helps reduce code repetition, but we still have a less-than-ideal situation. Every time we add a custom property in CSS, we have to write another line of JavaScript to access it. This can and does work fine if we only have a few custom properties. I’ve used this setup on production projects before. But, it’s also possible to automate this.</p>
<p>Let’s walk through the process of automating it by making a working thing.</p>
<h3>What are we making?</h3>
<p>We’ll make a color palette, which is a common feature in pattern libraries. We’ll generate a grid of color swatches from our CSS custom properties.</p>
<p>Here’s the <a href="https://codepen.io/tylergaw/pen/57ccb4a5dfbadc7b32a225272994dc3d">complete demo</a> that we’ll build step-by-step.</p>
<figure>
<picture>
<img src="https://stuff.tylergaw.com/post-get-custom-properties-js/fig1.webp" alt="A preview of our CSS custom property-driven color palette. Showing six cards, one for each color, including the custom property name and hex value in each card." />
</picture>
<figcaption>fig 1: Here's what we're aiming for.</figcaption>
</figure>
<p>Let’s set the stage. We’ll use an unordered list to display our palette. Each swatch is a <code><li></code> element that we’ll render with JavaScript.</p>
<pre><code class="language-html"><ul class="colors"></ul></code></pre>
<p>The CSS for the grid layout isn’t pertinent to the technique in this post, so we won’t look at in detail. It’s available in the <a href="https://codepen.io/tylergaw/pen/57ccb4a5dfbadc7b32a225272994dc3d">CodePen demo</a>.</p>
<p>Now that we have our HTML and CSS in place, we’ll focus on the JavaScript. Here’s an outline of what we’ll do with our code:</p>
<ol>
<li>Get all stylesheets on a page, both external and internal</li>
<li>Discard any stylesheets hosted on third-party domains</li>
<li>Get all rules for the remaining stylesheets</li>
<li>Discard any rules that aren’t basic style rules</li>
<li>Get the name and value of all CSS properties</li>
<li>Discard non-custom CSS properties</li>
<li>Build HTML to display the color swatches</li>
</ol>
<p>Let’s get to it.</p>
<h3>Step 1: Get all stylesheets on a page</h3>
<p>The first thing we need to do is get all external and internal stylesheets on the current page. Stylesheets are available as members of the global document.</p>
<pre><code class="language-javascript">document.styleSheets</code></pre>
<p>That returns an array-like object. We want to use array methods, so we’ll convert it to an array. Let’s also put this in a function that we’ll use throughout this post.</p>
<pre><code class="language-javascript">const getCSSCustomPropIndex = () => [...document.styleSheets];</code></pre>
<p><a href="https://codepen.io/tylergaw/pen/2163695e55e0ab5e339675582cbb2462">Demo on CodePen</a></p>
<p>When we invoke getCSSCustomPropIndex, we see an array of <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet">CSSStyleSheet</a> objects, one for each external and internal stylesheet on the current page.</p>
<figure>
<picture>
<img src="https://stuff.tylergaw.com/post-get-custom-properties-js/fig2.webp" alt="The output of getCSSCustomPropIndex, an array of CSSStyleSheet objects" />
</picture>
<figcaption>fig 2: The output of getCSSCustomPropIndex</figcaption>
</figure>
<h3>Step 2: Discard third-party stylesheets</h3>
<p>If our script is running on https://example.com any stylesheet we want to inspect must also be on https://example.com. This is a security feature. From the <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet%23Notes">MDN docs for CSSStyleSheet</a>:</p>
<blockquote>
<p>In some browsers, if a stylesheet is loaded from a different domain, accessing cssRules results in SecurityError.</p>
</blockquote>
<p>That means that if the current page links to a stylesheet hosted on https://some-cdn.com, we can’t get custom properties — or any styles — from it. The approach we’re taking here only works for stylesheets hosted on the current domain.</p>
<p>CSSStyleSheet objects have an href property. Its value is the full URL to the stylesheet, like https://example.com/styles.css. Internal stylesheets have an href property, but the value will be <code>null</code>.</p>
<p>Let’s write a function that discards third-party stylesheets. We’ll do that by comparing the stylesheet’s href value to the current <code>location.origin</code>.</p>
<pre><code class="language-javascript">const isSameDomain = (styleSheet) => {
if (!styleSheet.href) {
return true;
}
return styleSheet.href.indexOf(window.location.origin) === 0;
};</code></pre>
<p>Now we use <code>isSameDomain</code> as a filter on <code>document.styleSheets</code>.</p>
<pre><code class="language-javascript">const getCSSCustomPropIndex = () => [...document.styleSheets]
.filter(isSameDomain);</code></pre>
<p><a href="https://codepen.io/tylergaw/pen/35351c6c426ecbdfd3669d7d3ce03ab6">Demo on CodePen</a></p>
<p>With the third-party stylesheets discarded, we can inspect the contents of those remaining.</p>
<h3>Step 3: Get all rules for the remaining stylesheets</h3>
<p>Our goal for <code>getCSSCustomPropIndex</code> is to produce an array of arrays. To get there, we’ll use a combination of array methods to loop through, find values we want, and combine them. Let’s take a first step in that direction by producing an array containing every style rule.</p>
<pre><code class="language-javascript">const getCSSCustomPropIndex = () => [...document.styleSheets]
.filter(isSameDomain)
.reduce((finalArr, sheet) => finalArr.concat(...sheet.cssRules), []);</code></pre>
<p><a href="https://codepen.io/tylergaw/pen/c99c6be39b1ff5f429e06f3cd7aecc11">Demo on CodePen</a></p>
<p>We use <code>reduce</code> and <code>concat</code> because we want to produce a flat array where every first-level element is what we’re interested in. In this snippet, we iterate over individual <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet">CSSStyleSheet</a> objects. For each one of them, we need its <code>cssRules</code>. <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/cssRules">From the MDN docs</a>:</p>
<blockquote>
<p>The read-only CSSStyleSheet property cssRules returns a live CSSRuleList which provides a real-time, up-to-date list of every CSS rule which comprises the stylesheet. Each item in the list is a CSSRule defining a single rule.</p>
</blockquote>
<p>Each CSS rule is the selector, braces, and property declarations. We use the spread operator <code>...sheet.cssRules</code> to take every rule out of the <code>cssRules</code> object and place it in <code>finalArr</code>. When we log the output of <code>getCSSCustomPropIndex</code>, we get a single-level array of <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSRule"><code>CSSRule</code></a> objects.</p>
<figure>
<picture>
<img src="https://stuff.tylergaw.com/post-get-custom-properties-js/fig3.webp" alt="Example output of getCSSCustomPropIndex producing an array of CSSRule objects" />
</picture>
<figcaption>fig 3: Example output of getCSSCustomPropIndex producing an array of CSSRule objects</figcaption>
</figure>
<p>This gives us all the CSS rules for all the stylesheets. We want to discard some of those, so let’s move on.</p>
<h3>Step 4: Discard any rules that aren’t basic style rules</h3>
<p>CSS rules come in different types. CSS specs define each of the types with a constant name and integer. The most common type of rule is the <code>CSSStyleRule</code>. Another type of rule is the <code>CSSMediaRule</code>. We use those to define media queries, like <code>@media (min-width: 400px) {}</code>. Other types include <code>CSSSupportsRule</code>, <code>CSSFontFaceRule</code>, and <code>CSSKeyframesRule</code>. See the <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSRule#Type_constants">Type constants section</a> of the MDN docs for <code>CSSRule</code> for the full list.</p>
<p>We’re only interested in rules where we define custom properties and, for the purposes in this post, we’ll focus on <code>CSSStyleRule</code>. That does leave out the <code>CSSMediaRule</code> rule type where it’s valid to define custom properties. We could use an approach that’s similar to what we’re using to extract custom properties in this demo, but we’ll exclude this specific rule type to limit the scope of the demo.</p>
<p>To narrow our focus to style rules, we’ll write another array filter:</p>
<pre><code class="language-javascript">const isStyleRule = (rule) => rule.type === 1;</code></pre>
<p>Every <code>CSSRule</code> has a type property that returns the integer for that type constant. We use <code>isStyleRule</code> to filter <code>sheet.cssRules</code>.</p>
<pre><code class="language-javascript">const getCSSCustomPropIndex = () => [...document.styleSheets]
.filter(isSameDomain)
.reduce((finalArr, sheet) => finalArr.concat(
[...sheet.cssRules].filter(isStyleRule)
), []);</code></pre>
<p><a href="https://codepen.io/tylergaw/pen/729393ada1807162d223c47e4207f3e3">Demo on CodePen</a></p>
<p>One thing to note is that we are wrapping <code>...sheet.cssRules</code> in brackets so we can use the array method filter.</p>
<p>Our stylesheet only had <code>CSSStyleRules</code> so the demo results are the same as before. If our stylesheet had media queries or <code>font-face</code> declarations, <code>isStyleRule</code> would discard them.</p>
<h3>Step 5: Get the name and value of all properties</h3>
<p>Now that we have the rules we want, we can get the properties that make them up. <code>CSSStyleRule</code> objects have a style property that is a <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration"><code>CSSStyleDeclaration</code></a> object. It’s made up of standard CSS properties, like <code>color</code>, <code>font-family</code>, and <code>border-radius</code>, plus custom properties. Let’s add that to our <code>getCSSCustomPropIndex</code> function so that it looks at every rule, building an array of arrays along the way:</p>
<pre><code class="language-javascript">const getCSSCustomPropIndex = () => [...document.styleSheets]
.filter(isSameDomain)
.reduce((finalArr, sheet) => finalArr.concat(
[...sheet.cssRules]
.filter(isStyleRule)
.reduce((propValArr, rule) => {
const props = []; /* TODO: more work needed here */
return [...propValArr, ...props];
}, [])
), []);</code></pre>
<p>If we invoke this now, we get an empty array. We have more work to do, but this lays the foundation. Because we want to end up with an array, we start with an empty array by using the accumulator, which is the second parameter of reduce. In the body of the reduce callback function, we have a placeholder variable, props, where we’ll gather the properties. The return statement combines the array from the previous iteration — the accumulator — with the current <code>props</code> array.</p>
<p>Right now, both are empty arrays. We need to use <code>rule.style</code> to populate props with an array for every property/value in the current rule:</p>
<pre><code class="language-javascript">const getCSSCustomPropIndex = () => [...document.styleSheets]
.filter(isSameDomain)
.reduce((finalArr, sheet) => finalArr.concat(
[...sheet.cssRules]
.filter(isStyleRule)
.reduce((propValArr, rule) => {
const props = [...rule.style].map((propName) => [
propName.trim(),
rule.style.getPropertyValue(propName).trim()
]);
return [...propValArr, ...props];
}, [])
), []);</code></pre>
<p><a href="https://codepen.io/tylergaw/pen/8ee326b200560bc5d77fe63d90b32e80">Demo on CodePen</a></p>
<p><code>rule.style</code> is array-like, so we use the spread operator again to put each member of it into an array that we loop over with map. In the map callback, we return an array with two members. The first member is <code>propName</code> (which includes <code>color</code>, <code>font-family</code>, <code>--color-accent</code>, etc.). The second member is the value of each property. To get that, we use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/getPropertyValue"><code>getPropertyValue</code></a> method of <code>CSSStyleDeclaration</code>. It takes a single parameter, the string name of the CSS property.</p>
<p>We use trim on both the name and value to make sure we don’t include any leading or trailing whitespace that sometimes gets left behind.</p>
<p>Now when we invoke <code>getCSSCustomPropIndex</code>, we get an array of arrays. Every child array contains a CSS property name and a value.</p>
<figure>
<picture>
<img src="https://stuff.tylergaw.com/post-get-custom-properties-js/fig4.webp" alt="Output of getCSSCustomPropIndex showing an array of arrays containing every property name and value" />
</picture>
<figcaption>fig 4: Output of getCSSCustomPropIndex showing an array of arrays containing every property name and value</figcaption>
</figure>
<p>This is what we’re looking for! Well, almost. We’re getting every property in addition to custom properties. We need one more filter to remove those standard properties because all we want are the custom properties.</p>
<h3>Step 6: Discard non-custom properties</h3>
<p>To determine if a property is custom, we can look at the name. We know custom properties must start with two dashes (<code>--</code>). That’s unique in the CSS world, so we can use that to write a filter function:</p>
<pre><code class="language-javascript">([propName]) => propName.indexOf("--") === 0)</code></pre>
<p>Then we use it as a filter on the props array:</p>
<pre><code class="language-javascript">const getCSSCustomPropIndex = () =>
[...document.styleSheets].filter(isSameDomain).reduce(
(finalArr, sheet) =>
finalArr.concat(
[...sheet.cssRules].filter(isStyleRule).reduce((propValArr, rule) => {
const props = [...rule.style]
.map((propName) => [
propName.trim(),
rule.style.getPropertyValue(propName).trim()
])
.filter(([propName]) => propName.indexOf("--") === 0);
return [...propValArr, ...props];
}, [])
),
[]
);</code></pre>
<p><a href="https://codepen.io/tylergaw/pen/c2b59c4754925b1ab2119515db9bd732">Demo on CodePen</a></p>
<p>In the function signature, we have <code>([propName])</code>. There, we’re using array destructuring to access the first member of every child array in props. From there, we do an <code>indexOf</code> check on the name of the property. If <code>--</code> is not at the beginning of the prop name, then we don’t include it in the <code>props</code> array.</p>
<p>When we log the result, we have the exact output we’re looking for: An array of arrays for every custom property and its value with no other properties.</p>
<figure>
<picture>
<img src="https://stuff.tylergaw.com/post-get-custom-properties-js/fig5.webp" alt="The output of getCSSCustomPropIndex showing an array of arrays containing every custom property and its value" />
</picture>
<figcaption>fig 5: Output of getCSSCustomPropIndex showing an array of arrays containing every custom property and its value</figcaption>
</figure>
<p>Looking more toward the future, creating the property/value map doesn’t have to require so much code. There’s an alternative in the <a href="https://drafts.css-houdini.org/css-typed-om-1/%23the-stylepropertymap">CSS Typed Object Model Level 1</a> draft that uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleRule/styleMap"><code>CSSStyleRule.styleMap</code></a>. The <code>styleMap</code> property is an array-like object of every property/value of a CSS rule. We don’t have it yet, but If we did, we could shorten our above code by removing the map:</p>
<pre><code class="language-javascript">// ...
const props = [...rule.styleMap.entries()].filter(/*same filter*/);
// ...</code></pre>
<p><a href="https://codepen.io/tylergaw/pen/5061057013e6ddef39cf6b54a8d6bc1f">Demo on CodePen</a></p>
<p>At the time of this writing, Chrome and Edge have implementations of <code>styleMap</code> but no other major browsers do. Because <code>styleMap</code> is in a draft, there’s no guarantee that we’ll actually get it, and there’s no sense using it for this demo. Still, it’s fun to know it’s a future possibility!</p>
<p>We have the data structure we want. Now let’s use the data to display color swatches.</p>
<h3>Step 7: Build HTML to display the color swatches</h3>
<p>Getting the data into the exact shape we needed was the hard work. We need one more bit of JavaScript to render our beautiful color swatches. Instead of logging the output of <code>getCSSCustomPropIndex</code>, let’s store it in variable.</p>
<pre><code class="language-javascript">const cssCustomPropIndex = getCSSCustomPropIndex();</code></pre>
<p>Here’s the HTML we used to create our color swatch at the start of this post:</p>
<pre><code class="language-html"><ul class="colors"></ul></code></pre>
<p>We’ll use <code>innerHTML</code> to populate that list with a list item for each color:</p>
<pre><code class="language-javascript">document.querySelector(".colors").innerHTML = cssCustomPropIndex.reduce(
(str, [prop, val]) => `${str}<li class="color">
<b class="color__swatch" style="--color: ${val}"></b>
<div class="color__details">
<input value="${prop}" readonly />
<input value="${val}" readonly />
</div>
</li>`,
"");</code></pre>
<p><a href="https://codepen.io/tylergaw/pen/57ccb4a5dfbadc7b32a225272994dc3d">Demo on CodePen</a></p>
<p>We use reduce to iterate over the custom prop index and build a single HTML-looking string for <code>innerHTML</code>. But reduce isn’t the only way to do this. We could use a <code>map</code> and <code>join</code> or <code>forEach</code>. Any method of building the string will work here. This is just my preferred way to do it.</p>
<p>I want to highlight a couple specific bits of code. In the <code>reduce</code> callback signature, we’re using array destructuring again with <code>[prop, val]</code>, this time to access both members of each child array. We then use the <code>prop</code> and <code>val</code> variables in the body of the function.</p>
<p>To show the example of each color, we use a <code>b</code> element with an inline style:</p>
<pre><code class="language-javascript"><b class="color__swatch" style="--color: ${val}"></code></pre>
<p>That means we end up with HTML that looks like:</p>
<pre><code class="language-html"><b class="color__swatch" style="--color: #00eb9b"></b></code></pre>
<p>But how does that set a background color? In the <a href="https://codepen.io/tylergaw/pen/57ccb4a5dfbadc7b32a225272994dc3d.css">full CSS</a> we use the custom property <code>--color</code> as the value of background-color for each <code>.color__swatch</code>. Because external CSS rules inherit from inline styles, <code>--color</code> is the value we set on the <code>b</code> element.</p>
<pre><code class="language-css">.color__swatch {
background-color: var(--color);
/* other properties */
}</code></pre>
<p>We now have an HTML display of color swatches representing our CSS custom properties!</p>
<p class="codepen" data-height="500" data-default-tab="result" data-slug-hash="jObmNNM" data-user="tylergaw" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
<span>See the Pen <a href="https://codepen.io/tylergaw/pen/jObmNNM">
CSS Custom Props to JavaScript - Complete</a> by Tyler Gaw (<a href="https://codepen.io/tylergaw">@tylergaw</a>)
on <a href="https://codepen.io/">CodePen</a>.</span>
</p>
<script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
<p>This demo focuses on colors, but the technique isn’t limited to custom color props. There’s no reason we couldn’t expand this approach to generate other sections of a pattern library, like fonts, spacing, grid settings, etc. Anything that might be stored as a custom property can be displayed on a page automatically using this technique.</p>
Realign 2020: Color2020-05-17T00:00:00Zhttps://tylergaw.com/blog/realign-2020-color/<p class="entry-intro">
This is part of my “<a href="https://tylergaw.com/blog/realign-2020">Realign 2020</a>” series. In it, I document my process during a content and design realign of
this site. I’ll update the site piece-by-piece as I post. The design and
content will evolve through the series and beyond.
</p>
<p>
Going into this, I didn’t know exactly what colors I wanted, but I knew I
wanted a lot. I wanted my color usage to come across as deliberate. Planned.
But at the same time unbounded. I wanted a design where no hue, shade, or tint
is off limits or unwelcome.
</p>
<p>
Along with new colors, I also added a light color scheme. Or “light mode.”
Depending on your system color mode, this text is either muddy brown on
off-white or off-white on charcoal. There's also an override button in the
footer, in case you prefer one scheme over the other. More on that later in
this post.
</p>
<h2>Choosing colors</h2>
<p>
Choosing colors for a design is a lot of play. With this design I started with
what I had in place already. A dark grey background
<code class="color-swatch-code" style="--color: #292929;">#292929</code>,
off-white text
<code class="color-swatch-code" style="--color: #f8f8f8;">#f8f8f8</code>, and
a pink accent color
<code class="color-swatch-code" style="--color: #ff607f;">#ff607f</code>.
</p>
<h3>Toxic green</h3>
<p>
The first color I worked with was the primary accent color. Its job is to make
the palette come alive. It’s also the color of text links. Because of that,
the color I used needed to meet a minimum WCAG color contrast ratio. I used
high contrast ratios as guideposts during this process. This primary accent
color was important to get just right.
</p>
<p>
From the beginning, I saw yellows, greens, yellowish greens, and blue greens
in my head. I went through the rainbow to try other hues, but it was clear
this small section of the spectrum was where I'd find the color I wanted.
</p>
<p>
The green couldn’t be a lime. That, with the off-white and charcoal, gave a
campy/horror vibe that didn’t feel right. Towards yellow felt stale. I wanted
an unnatural green. A weird green.
</p>
<p>
My hair at the time was a strange mix of blue and emerald green. The result of
one of my recent home dye jobs. I thought that could be a place to look. I
didn't have a hex value so I did an image search for “emerald green.” When I
found one that looked cool, I used an eyedropper to pick a hex. My process for
color is definitely not a science.
</p>
<p>
I don’t have the exact hex I started with, but it gave me a solid foundation
to work from. It got me to a tiny sliver of the color spectrum. I had help
getting to the final color. I mentioned before I need colors to meet certain
contrast ratios. To do that I use the
<a href="https://webaim.org/resources/contrastchecker/">WebAIM contrast checker</a>. I compared the green with the charcoal background. I adjusted the hue and
lightness by tiny amounts until I got sufficient contrast.
</p>
<p>
Letting contrast ratios influence aesthetic decisions like that can be a
little uncomfortable. As an experienced designer, I have a trained eye that I
trust to choose colors that work well and look good. But, that’s not the whole
story. My instincts towards subtlety often lead to colors that look fantastic,
but are low in contrast. Low contrast text can be difficult for people to see.
Color needs more than my instincts alone. So I let go of a bit of control.
</p>
<p>
Letting go can produce great results. Results that make a design accessible
and enjoyable to more people. And in this case, helped me get what I was
looking for. This was the first color I got excited about in this process.
This unnatural green. This weird green. This toxic green.
</p>
<figure>
<div class="realign-color-fig realign-color-primary-accent"></div>
<figcaption>
fig 1: Primary accent color for the dark scheme, #00eb9b
</figcaption>
</figure>
<h3>Boo</h3>
<p>
Having a single accent color made me feel like I painted myself into a corner.
It felt too formulaic. Also, the design before was using the accent color to
highlight links, but then sometimes only as an accent with no interactivity. I
needed to introduce at least one more accent color. One that I’d use only for
embellishment. Toxic green would only be for links and other clickable
elements.
</p>
<p>
I went through the process again. A slow trial and error. Clicking around on
the color picker. Looking for one that worked with that toxic green and the
rest of the design. I landed on a muted blue.
</p>
<p>
The blue itself had to look great, but the interplay between it and the other
colors was most important.
</p>
<figure>
<div class="realign-color-fig realign-color-primary-secondary-accent"></div>
<figcaption>
fig 2: Primary and secondary accent colors of the dark scheme; #00eb9b,
#9db4ff
</figcaption>
</figure>
<p>
From here it was color confetti. I made a spotted, star dust-looking
background texture in Sketch. At first it was all off-white flecks, but felt
like it needed more. So I started picking colors at almost random to use for
the dust. The hue is all over the spectrum, but I tried to keep them in a
similar tone. This part was just fun.
</p>
<p>
Now that I have a foundation of color in place, I can keep going with it. As I
continue developing content, I can also iterate on the design. Finding more
opportunities to introduce color as I move along.
</p>
<h2>Light mode</h2>
<p>
In the last year or so it seems almost every site and app has announced their
“dark mode.” This site has had light text on a dark background for almost a
decade now. With this realign, I also wanted to provide an alternative color
scheme. The alternative would be a light version.
</p>
<p>
Dark and light schemes shouldn’t only the inverse of the other. They should be
fully considered designs that stand on their own. The color selection process
was the same as the others. Trial and error. Make sure the colors meet WCAG
contrast ratios. Find combinations that work well together.
</p>
<figure>
<div class="realign-color-fig realign-color-light-scheme"></div>
<figcaption>
fig 3: Text, primary, and secondary colors of the light scheme; #3f3d31,
#c01458, c2d838
</figcaption>
</figure>
<p>
Like the dark scheme, the light scheme has a textured background. The shape
and placement of the flecks are the same, but I chose different colors. Colors
that fit the tone of the light accents and worked well on the off-white
background.
</p>
<h3>Your choice</h3>
<p>
A color mode you choose at your operating system level should not be the final
say you have in the mode of a website or app. Preference over text and
background color changes with context, time of day, and mood. For this site, I
prefer the dark scheme. But, I don’t use dark mode on my system, macOS. Other
folks will have different preferences.
</p>
<p>
With that in mind, I included a color scheme toggle in the footer that
remembers your preference. Not across devices, but in browser.
<span class="realign-color-scheme-toggle-invite">Give it a try with this button:
<button class="realign-color-scheme-toggle">Switch scheme</button></span>
</p>
<h3>Mode code</h3>
<p>
The code I used for this is a modified version of
<a href="https://hankchizljaw.com/wrote/create-a-user-controlled-dark-or-light-mode/">Andy Bell’s</a>. The logic for picking a color scheme goes like this. If you haven’t chosen
a color scheme using the provided button, go with the system setting. If you
have a system level dark mode set, you’ll see the dark scheme. Light mode,
light scheme.
</p>
<p>
If you’ve been to this site before and changed the scheme using the toggle
button, that will be the scheme you see. If you have a system level dark mode
set, but you’ve chosen the light scheme here, you’ll see the light scheme. The
inverse is also true. This makes your local preference take precendence over
system preference.
</p>
<p>
An interesting thing I ended up doing with the code for the scheme picker is
splitting it into two parts. I include the first part as an internal script
right after the opening
<code>body</code> tag. This code’s job is to determine the correct color
scheme on page load. It checks <code>localStorage</code> for a
<code>user-color-scheme</code> item. The value of that item will be the string
<code>"light"</code> or <code>"dark"</code>. If found, that’s the color scheme
displayed. If not, the code falls back to the system level color scheme. I’m
able to determine the system level scheme from a CSS custom property named
<code>"user-color-scheme"</code>. The default of that prop is
<code>"dark"</code>. Using a <code>(prefers-color-scheme: light)</code> media
query I set the prop value to <code>"light"</code>. This approach is from
Andy’s example.
</p>
<p>
The reason I include this as an internal script before the document loads is
to avoid a flash of the other scheme. If I included this as an external script
and waited for document load, you’d see that quick flash of incorrectly styled
content. This makes the code a tiny bit harder to maintain, but it works fine
for my purposes. I keep the code in
<a href="https://github.com/tylergaw/tylergaw.com/blob/346982b2da07b2369b5bd2101c50488dfa246157/partials/scheme.js.html">a partial</a>
that I include in my handlebars templates. This isn’t perfect. I think the
extra <code>scipt</code> is causing some other jitteriness on load, but it’ll
do for now.
</p>
<p>
The second part of the code is also in
<a href="https://github.com/tylergaw/tylergaw.com/blob/346982b2da07b2369b5bd2101c50488dfa246157/partials/scripts.html#L10">a partial</a>. I include this before the closing <code>body</code> tag. This code’s job is
to make the scheme switcher button work. Because it’s in the footer, it can
wait until the document loads. Andy’s post does a great job of explaining the
approach so I won’t go into detail here about how it all works.
</p>
<p>
Something I added in my implementation is code that responds to system level
mode changes. Andy mentions it as a potential improvement:
</p>
<blockquote>
<p>
Observe changes to the user’s dark/light mode setting, probably via the
prefers-color-scheme media query and update the default state accordingly
</p>
<cite>
“<a href="https://hankchizljaw.com/wrote/create-a-user-controlled-dark-or-light-mode/#heading-wrapping-up">Create a user controlled dark or light mode</a>”
</cite>
</blockquote>
<p>
This addition makes sure the button stays in sync with the system level
setting. Without this code, the CSS-driven parts of the display update, but
not the JS-driven parts, like the button.
</p>
<!-- prettier-ignore-start -->
<pre><code class="language-javascript">const mql = window.matchMedia("(prefers-color-scheme: light)");
mql.addListener((event) => {
console.group("System color scheme change");
const systemScheme = getCSSProp("--scheme-system");
const userScheme = localStorage.getItem(schemeKey);
console.info("System color scheme changed to", systemScheme);
if (!userScheme) {
console.info("No user color scheme chosen, using the system scheme");
} else {
console.info(`User choice takes preference, using the ${userScheme} scheme`);
}
updateSchemeBtn(userScheme || systemScheme);
console.groupEnd();
});</code></pre>
<!-- prettier-ignore-end -->
<p>
I use a
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia"><code>MediaQueryList</code></a>
event to keep an eye on <code>prefers-color-scheme</code>. When it changes,
the callback does the same <code>localStorage</code> and custom prop lookup
that happens on page load.
</p>
<p>
I left logging in place to announce changes and decisions made. I hadn’t used
<code>console.group()</code> and <code>groupEnd()</code> before. They’re super
helpful for keeping logs organized. If you want to see them in action, open
the console and make changes to the local and system color schemes.
</p>
<figure>
<picture>
<source srcset="https://tylergaw.com/articles/assets/post-image-realign-2020-color-scheme-toggle-static.png" media="(prefers-reduced-motion: reduce)" />
<img src="https://tylergaw.com/articles/assets/post-image-realign-2020-color-scheme-toggle.gif" alt="A looping animated gif where I’m changing the Appearance setting on macOS from Light to dark while looking at this site with the developer console open to show the logging output." />
</picture>
<figcaption>
fig 4: Monitoring the effects of changing the appearance on macOS
</figcaption>
</figure>
<h2>Code color</h2>
<p>
If you’re reading this post on my site, you’ll see syntax highlighted code
samples. Like the earlier <code>matchMedia</code> example.
<a href="https://prismjs.com/">Prism</a> is my highlighter of choice. I’ve had
a modified
<a href="https://github.com/ericwbailey/a11y-syntax-highlighting/blob/master/dist/prism/a11y-dark.css">dark a11y theme by Eric Bailey</a>
in place for a couple years. For this update, I combined the dark and light
a11y themes so they change with the rest of the scheme. I moved all the theme
colors to CSS custom props and used similar selectors to choose the correct
theme.
</p>
<p>
This approach results in a bit a repetition, but gets the job done. You can
see the full port on
<a href="https://github.com/tylergaw/tylergaw.com/blob/cffe306ca5447e2b17cbe5ac8669a8d909837ee3/src/css/modules/prism-a11y.css">GitHub in prism-a11y.css</a>.
</p>
<h2>Color naming</h2>
<p>
I’ll end with another fun one. Have you ever tried to name colors? There are
common names like red, green, and blue. But what’s
<code class="color-swatch-code" style="--color: #c01458;">#c01458</code>’s
name? If you’ve ever named color variables or custom properties you know the
pain of naming colors. When I’ve done this before, I start out on the right
foot. <code>red-bright</code>, <code>red-dark</code>, <code>grey</code>. But,
before long I end up with dubious names like <code>red-darker</code>,
<code>red-lighter</code>, <code>grey-mid</code>. And eventually
I-give-up-names like <code>purple-but-more-blue</code>,
<code>not-quite-green</code>. Before this realign, I had a color named
<code>darkerwhite</code>. That is…less than ideal.
</p>
<p>
Color names like this aren’t helpful. And it’s not fun coming up with them.
This time I let go. Instead of trying to come up with some magic, smart
formula for naming colors, I use word association. I look at the color and
whatever pops into my head first is the name. I follow a naming pattern
prefix, but the names are all over the place. <code>toxic</code>,
<code>boo</code>, <code>boogers</code>.
</p>
<pre><code class="language-css">--color-accent-toxic: #00eb9b;
--color-accent-wine: #c01458;
--color-accent-boo: #9db4ff;
--color-accent-pank: #f2c0ea;
--color-accent-boogers: #c2d838;
--color-accent-mellow: #f1f68e;
--color-accent-orange: #e9b581;
--color-charcoal: #292929;
--color-mud: #3f3d31;
--color-offwhite: #f8f8f8;
--color-white: #fff;
--color-eggshell: #f6f5e4;</code></pre>
<p>
Some of them are just what they are, but not most. It’s cheeky, but I’m
finding that I remember what color <code>boogers</code> is better than I would
something like <code>yellowgreen-light</code>. It’s faster for me to name new
colors and I get a good chuckle every time I do it. And these days, I’ll take
the chuckles anywhere I can get ’em.
</p>
<h2>Next</h2>
<p>
I’ve been working on and writing about this site realign since late February.
These color updates and post took a long time. I’m OK with that. I want this
to be fun, exploratory work. Not mad dash to the finish. With that in mind, I
don’t know when I’ll publish the next post in the series, but it’ll be
sometime sooner than later and I’ll give myself plenty of time to work on it.
</p>
CSS: Flexible Repeating SVG Masks2020-03-14T00:00:00Zhttps://tylergaw.com/blog/css-repeating-svg-masks/<p class="entry-intro">
This is a technique I used recently to create a flexible,
repeating pattern using SVG with CSS <code>mask</code>. There are live
examples in this post and they’re available as a
<a href="https://codepen.io/tylergaw/pen/mdJpdVm">CodePen</a>.
</p>
<p>
The need came from something I designed at <a href="https://streetcred.co/">work</a>.
I gave the bottom of the header on each page a little squiggle to make it a bit
more interesting than a straight line. Along with that, I designed a <a href="https://streetcred.co/patterns#color">color system</a>
for each type of page. Common pages parade in purple, company content is true blue,
blog posts read in red, and contests own orange. With StreetCred I’ve been
throwing a rainbow of bright colors to see what’s fun and what sticks.
</p>
<figure>
<picture>
<source srcset="https://tylergaw.com/articles/assets/post-image-repeating-masks-streetcred-banner.webp" type="image/webp" />
<source srcset="https://tylergaw.com/articles/assets/post-image-repeating-masks-streetcred-banner.png" type="image/jpeg" />
<img src="https://tylergaw.com/articles/assets/post-image-repeating-masks-streetcred-banner.png" alt="A screenshot of a purple, blue, red, and orange banner from the StreetCred website." />
</picture>
<figcaption>fig 1: Banner squiggles from <a href="https://streetcred.co/">streetcred.co</a></figcaption>
</figure>
<p>
Along with the current colors, we’re getting ready to ship new features that
use even more colors. We’ll make pages for those and they’ll need themed banner
squiggles. We need a flexible way to do that.
</p>
<h2>How Should This Work?</h2>
<p>
For the design, I needed a seamless, horizontally repeating pattern. I needed
SVG so it was crisp at any size. I wanted a single SVG image that
I could color using CSS. Because we could have n-number of colors, creating
a new SVG for every color wasn’t a future-facing option. The squiggly banner using this
approach is live on <a href="https://streetcred.co/">streetcred.co</a>. Let’s
look at an isolated demo to see how the pieces fit together.
</p>
<h2>Creating the Demo SVG</h2>
<p>
First, I made a quick doodle of a squiggly line in Sketch using the vector tool.
The important part was to make sure the line was seamless when repeated horizontally.
I did that by making sure
the first point and last point were at the same location on the y-axis. And
that the bezier curves leading to them was flat when it reached the
edge of the artboard. This is a simple shape, so it was easy to eyeball.
A more complex design would involve more work.
</p>
<figure>
<picture>
<source srcset="https://tylergaw.com/articles/assets/post-image-repeating-masks-creating-svg.webp" type="image/webp" />
<source srcset="https://tylergaw.com/articles/assets/post-image-repeating-masks-creating-svg.png" type="image/png" />
<img src="https://tylergaw.com/articles/assets/post-image-repeating-masks-creating-svg.png" alt="A screenshot in Sketch app showing the demo squiggle vector being created." />
</picture>
<figcaption>fig 2: Creating the demo SVG in Sketch</figcaption>
</figure>
<p>
To test if the shape is seamless I just duplicated the artboard and dragged it
into place. Nothing fancy again, a quick eyeball is enough for this.
</p>
<p>
When I create graphics for SVG use, I simplify them as much as possible.
For this, I expanded the stroke. That way instead of exporting a <code>path</code>
with a <code>stroke</code>, I end up with a <code>path</code> with a <code>fill</code>.
In my experience, filled shapes are more flexible when you’re working with them
in browsers. Helps avoid scaling issues with <code>stroke</code>s. SVG prep is a
deep topic that I plan to write more about in the future.
</p>
<p>
Exporting SVG from Sketch leaves a lot of crud in that we don’t need. My next
stop is <a href="https://jakearchibald.github.io/svgomg/">SVGOMG</a>. After
optimizing we end up with an SVG with a single <code>path</code> element. I
truncated the value of the <code>d</code> attribute for brevity.
</p>
<pre><code class="language-svg"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 75">
<path id="squiggle" d="M12 19.778C12 1.318..." />
</svg>
</code></pre>
<p>
We’ll use the <code>id</code> attribute as a reference in CSS. Notice we don’t need
presentational attributes like <code>fill</code>. SVG provides the
shape. CSS handles the color.
</p>
<h2>The Code</h2>
<p>
The HTML is a single <code>div</code>. The CSS is three properties.
</p>
<pre><code class="language-html"><div class="repeater"></div></code></pre>
<pre><code class="language-css">.repeater {
background-color: red;
/* This fixed height is only for demo, your use cases might not need it */
height: 75px;
mask-image: url("/path/to/repeater.svg#squiggle");
}</code></pre>
<p>
As of this writing, this CSS works in Firefox, but not in Chrome, Safari,
or Edge. WebKit/Blink browsers still require vendor-prefixed <code>mask</code>
properties. We don’t want to have to repeat the path to the image. Let’s store
it in a custom property to make it reusable.
</p>
<pre><code class="language-css">.repeater {
background-color: red;
/* This fixed height is only for demo, your use cases might not need it */
height: 75px;
--svg: url("/path/to/repeater.svg#squiggle");
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
}</code></pre>
<p>
With the prefixed version of <code>mask-image</code> in place, this works in all current
versions of Chrome, Firefox, Safari, and Edge.
</p>
<figure>
<style>
.demo-repeater {
background-color: red;
/* This fixed height is only for demo, your use cases might not need it */
height: 75px;
--svg: url("https://tylergaw-assets.s3.amazonaws.com/inline-masks/repeater.svg#squiggle");
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
}
</style>
<div class="demo-repeater"></div>
<figcaption>fig 3: A live demo of the technique. The CSS is inline for inspection.</figcaption>
</figure>
<h3>What did we do?</h3>
<p>
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/mask-image"><code>mask-image</code></a>
is doing the heavy lifting here. We’re giving it a reference to an external
SVG file and the <code>id</code> attribute of the <code>path</code> we want.
Mask is hiding anything in our <code>div</code> that doesn’t intersect
with that shape. So we see the <code>background-color</code> only where the
<code>path</code> is.
</p>
<p>
<code>mask</code> is like CSS <code>bacground</code>. It has a <code>mask-repeat</code>
property that defaults to <code>repeat</code>. Check the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/mask-repeat">MDN docs</a>
for more that.
</p>
<h3>What else?</h3>
<p>
With a functional foundation in place, we can have fun with it. We can style the underlying <code>div</code> any
way we want to create different effects. We can change the color.
</p>
<pre><code class="language-css">.repeater--orange {
background-color: orange;
}</code></pre>
<figure>
<style>
.demo-repeater--orange {
background-color: orange;
}
</style>
<div class="demo-repeater demo-repeater--orange"></div>
<figcaption>fig 4: A live demo of the technique showing that we can change the color with CSS.</figcaption>
</figure>
<p>
We can take that further by setting a background image.
</p>
<pre><code class="language-css">.repeater--gradient {
background: transparent linear-gradient(90deg, red, purple, blue, green);
}</code></pre>
<figure>
<style>
.demo-repeater--gradient {
background: transparent linear-gradient(90deg, red, purple, blue, green);
}
</style>
<div class="demo-repeater demo-repeater--gradient"></div>
<figcaption>fig 5: A live demo of the technique showing that we can use a background image</figcaption>
</figure>
<p>
Changing the background color and image is fun, we can also change the height
of the <code>div</code> to produce something different.
</p>
<pre><code class="language-css">.repeater--sized {
height: 18px;
}</code></pre>
<figure>
<style>
.demo-repeater--sized {
height: 12px;
}
</style>
<div class="demo-repeater demo-repeater--sized"></div>
<figcaption>fig 6: A live demo of the technique showing that we can change the size</figcaption>
</figure>
<p>
We can change the color, bacground-image, and height. Let’s introduce the
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/mask-size"><code>mask-size</code></a> property to squish the SVG into a type
of texture.
</p>
<pre><code class="language-css">.repeater--textured {
--texture-lines: 10;
--mask-size: calc(250px / var(--texture-lines));
-webkit-mask-size: var(--mask-size);
mask-size: var(--mask-size);
}</code></pre>
<figure>
<style>
.demo-repeater--textured {
--texture-lines: 10;
--mask-size: calc(250px / var(--texture-lines));
-webkit-mask-size: var(--mask-size);
mask-size: var(--mask-size);
}
</style>
<div class="demo-repeater demo-repeater--textured"></div>
<figcaption>fig 7: A live demo of the technique showing that we can use mask-size to produce a texture</figcaption>
</figure>
<p>
Again, we’re using a custom property to not have to repeat the size value. We’re
using <code>calc</code> here as a convenience. <code>250px</code> is the height
of the <code>viewbox</code> of the SVG. We divide that by the number of rows
we want to make sure we don’t have partial rows.
</p>
<p>
We can also change the position of the mask using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/mask-position"><code>mask-position</code></a>.
And, if we can change a value with CSS, that means we can change it over time.
Change over time equals motion.
</p>
<pre><code class="language-css">@keyframes move {
to {
--pos: 150%;
-webkit-mask-position: var(--pos);
mask-position: var(--pos);
}
}
.repeater--animated {
animation: move 0.6s infinite linear alternate;
}</code></pre>
<style>
@keyframes move {
to {
--pos: 150%;
-webkit-mask-position: var(--pos);
mask-position: var(--pos);
}
}
.demo-repeater--animated {
animation: move 0.6s infinite linear alternate;
}
</style>
<figure>
<div class="demo-repeater demo-repeater--animated"></div>
<figcaption>fig 8: A live demo of the technique showing that we can animate the mask position.</figcaption>
</figure>
<p>
These are toy examples, but they show that with a few building blocks we can
construct a lot of variations. With more complex SVG images and more CSS, we can use this
technique to produce all sorts of fun stuff. And be future-flexible while
doing it.
</p>
Realign 2020: Logo2020-03-07T00:00:00Zhttps://tylergaw.com/blog/realign-2020-logo/<p class="entry-intro">
This is part of my “<a href="https://tylergaw.com/blog/realign-2020">Realign 2020</a>” series. In it, I document my process during a content and design realign of
this site. I’ll update the site piece-by-piece as I post. The design and
content will evolve through the series and beyond.
</p>
<p>
Eight letters form my first and last name. My title, 16. An ampersand for
flair. That’s 25 characters that made up my logo. That’s how it felt. When I
looked at my logo, I saw 25 characters standing alone next to each other. It
felt like 25 solos. My goal with this iteration was to make those characters
feel like an ensemble cast. A cast of characters that work together to convey
more meaning than their underlying shapes could alone. This is what I came up
with.
</p>
<figure>
<picture>
<source srcset="https://tylergaw.com/blog/assets/post-image-realign-2020-logo-before-after.svg" media="(min-width: 47.5em)" />
<img src="https://tylergaw.com/blog/assets/post-image-realign-2020-logo-before-after-vertical.svg" alt="Before and after realign of my logo" />
</picture>
<figcaption>fig 1: Logo before and after</figcaption>
</figure>
<h2>The Feeling</h2>
<p>
Sturdy. Dense. Heavy. Kinetic. Those are adjectives I wanted to use to
describe my logo. I wanted this logo to feel like it was about to leap off the
screen, burst through your wall, and crash onto your floor. I kept seeing a
flag blowing from West to East. An object in constant, fluid motion. How a
waving flag fits with dense and heavy, I don’t know either, but that’s where
my head was. So, I started there.
</p>
<h2>The Work</h2>
<p>
I’ve written about this process before. In “<a href="https://tylergaw.com/blog/a-logo-design-process/">A Logo Design Process</a>” I wrote about iterating 2011’s logo into 2014’s logo. I went through a
similar process progressing 2014’s logo to 2018’s, but didn’t write about it.
My logo is like this site. I wrote in
<a href="https://tylergaw.com/blog/realign-2020/">this series’ opener</a>;
“…I remember thinking this would be my last site…that I had a solid foundation
that I could build on for a long time.” I feel the same about the logo. Since
the 2011 version, I’ve worked with the same vectors and morphed them into the
next incarnation.
</p>
<p>
This time around went fast. Odd fast. I didn’t sketch anything. I could see
the chunkiness, the togetherness that I wanted in my head. I created a new
Illustrator file and pasted the 2018 vectors in. Then got to work.
</p>
<p>
The first thing I did was remove the “New York NY U.S.A.” banner. I’m a New
Yorker. I’ve been here for almost 12 years and I ain’t leavin’. But, that part
of the logo didn’t feel necessary anymore. It’s been a fun, maybe a little
corny, part of logo, but it was time for it to go to let my name and title
have full focus.
</p>
<p>
From there, I reshaped each of the 25 characters point-by-point. There are 566
individual vector points. I positioned every one of them.
</p>
<figure>
<picture>
<source srcset="https://tylergaw.com/blog/assets/post-image-realign-2020-logo-points.webp" type="image/webp" />
<source srcset="https://tylergaw.com/blog/assets/post-image-realign-2020-logo-points.jpg" type="image/jpeg" />
<img src="https://tylergaw.com/blog/assets/post-image-realign-2020-logo-points.jpg" alt="A screenshot showing the 566 vector points and outline of the logo." />
</picture>
<figcaption>fig 2: The 566 points I positioned to make the logo</figcaption>
</figure>
<p>
I started with the “T” and worked from left to right. The way I got the
interplay I was looking for was by imagining the letters where physical
objects made of clay. I also imagined the clay had a narrow forcefield around
it. That meant one letter couldn’t meld with another, but it could push other
letters to reshape them. This approach created interesting dents and divots.
It smashed ascenders and stretched descenders.
</p>
<p>
To create a feeling of motion I made sure the line between my name and title
didn’t go in one direction for too long. The line follows a flow created by
the push and pull of the heavier characters.
</p>
<p>
The process stretched across three or four days. Those weren’t full working
days. I would get a hour or so session in the evenings after the day job.
</p>
<h3>Little Flat Feet</h3>
<p>
In “<a href="https://tylergaw.com/blog/realign-2020-typography/">Realign 2020: Typography</a>” I said I took inspiration from Proto Grotesk for parts of the logo. The
descenders of Proto Grotesk on the “y” and “j” are what I pulled into the
logo.
</p>
<figure>
<picture>
<img src="https://tylergaw.com/blog/assets/post-image-realign-2020-logo-feet.svg" alt="A detail of the logo highlighting descenders that took inspiration from Proto Grotesk" />
</picture>
<figcaption>fig 3: Proto Grotesk-inspired descenders in the logo</figcaption>
</figure>
<p>
They’re not an exact copy, but a riff on the original. They were fun to work
with because they gave another detail that effected neighboring characters.
Because I thought of them as feet, I let that idea keep going. I imagined the
“Y”, “R”, and “A” stomping on the characters of the title below them.
Flattening their heads, squishing their bodies, and skewing the baseline.
</p>
<h2>The Rest</h2>
<p>
When I look at the before and after I see something other than aesthetic
improvements. I see personal progress. When you’ve been doing this work as
long as I have, it’s easy to forget that you’re still capable of progression.
When I look at 2018 vs. 2020 I can see how much I’ve learned. I’ve learned to
use my tools better. I’ve learned how to see better. I’ve moved a couple steps
closer to being able to trick computers into showing the full picture that I
can see in my head. It feels great to know I’m still learning. Still growing.
Still going.
</p>
<p>
I’ll continue this series in a few days with “<a href="https://tylergaw.com/blog/realign-2020-color">Realign 2020: Color</a>.”
</p>
Realign 2020: Typography2020-03-01T00:00:00Zhttps://tylergaw.com/blog/realign-2020-typography/<p class="entry-intro">
This is part of my “<a href="https://tylergaw.com/blog/realign-2020">Realign 2020</a>” series. In it, I document my process during a content and design realign of
this site. I’ll update the site piece-by-piece as I post. The design and
content will evolve through the series and beyond.
</p>
<p>
The fonts I’ve chosen for this site have run the gamut over the years.
Handwritten, illustrated, web-safe only, monospaced, serif, questionable, and
so on. The last few years I’ve leaned on chunky, sturdy fonts. Fonts that—to
me—say; “hard work gets done here.” That fits my approach to design and
engineering.
</p>
<p>
For this iteration, I wanted that same sturdiness. I also wanted more room to
play. I wanted to use more weights than I have in the past and, when possible,
I wanted to make things strange.
</p>
<p>
After a long search and plenty of playing with different faces, I’ve settled
on a font that will serve the design and content well for at least a couple
years.
</p>
<h2>
Proto Grotesk
</h2>
<p>
If you’re reading this on my site, these sentences are now set in
<a href="https://www.productiontype.com/family/proto_grotesk">Production Type’s lovely Proto Grotesk</a>. I’ve chosen the light weight for the base body copy. The regular weight
feels too heavy for large swaths of copy.
</p>
<figure>
<img src="https://tylergaw.com/blog/assets/post-image-realign-2020-typography-proto-grotesk.png" alt="A sampling of Proto Grotesk in various weights and sizes" />
</figure>
<p>
I’m using the bold weight for certain headings and navigation items. I have
small doses of all caps here and there.
</p>
<p>
The extra light weight is coming in handy for blog post titles. I want those
to be commanding without hitting you over the head as much as a bold or
heavier weight can.
</p>
<p>
As I iterate, I’m looking forward to experimenting more with weights and sizes
and styles. Proto Grotesk also has fun alternate numerals and other alt glyphs
that I’m hoping to make use of when appropriate.
</p>
<h3>
Why this typeface?
</h3>
<p>
I learned about Proto Grotesk via Bethany Heck’s
<a href="https://fontreviewjournal.com/proto-grotesk/">excellent Font Review Journal</a>.
A lot of what she detailed in the review is what draws me to the typeface.
Its idiosyncrasies stick out enough to make it playful, without reducing
reading comfort.
</p>
<p>
The designer’s description is a perfect summary of what I’m looking for.
</p>
<blockquote>
<p>
…there’s no reason type can’t be both steadfast and peculiar…its posture is
nothing less than sturdy and forthright. Proto Grotesk is strange but
steady.
</p>
<cite>
<a href="https://www.productiontype.com/family/proto_grotesk">Production Type - Proto Grotesk</a>
</cite>
</blockquote>
<p>
One detail I really dig are the little “flat feet”, as I’ve been referring to
them. The lowercase “y” and “a” are the best examples. Something about that
abrupt, a little too long bit catches my eye. I took cues from it as I was
updating my logo. I’ll write about that in a future post.
</p>
<p>
Along with all that, I think Proto Grotesk just looks super cool. Sometimes
that’s enough.
</p>
<h3>
Cost
</h3>
<p>
This was the most expensive font I’ve bought for a personal project. My bill
was about $400 USD. That bought me all four weights with a desktop and web
license. That’s a big buy for me, but it feels worth it and it’s cool to
directly support a type design studio.
</p>
<p>
Production Type offers trial downloads of their fonts. Being able to try the
font in design programs and in browsers made all the difference. By the time I
clicked “purchase”. I’d been using Proto Grotesk in real settings for weeks.
</p>
<p>
Those trials also helped me experiment with other fonts and weed out ones that
weren’t working. There were a few that looked great on Production’s site, but
didn’t work in the context of my design.
</p>
<h2>
Implementation
</h2>
<p>
I’ve used Typekit on this site for about eight years. It’s served me well, but
I don’t want to load fonts from a third party anymore. I want to self-host.
That was a smaller reason to go with Proto Grotesk too. Not that it’s the only
font I could self-host, but it checked that box.
</p>
<p>
Because I’m self-hosting, I need to load fonts in a performat way. I spent
time catching up on font loading methods. They move fast. Zach’s post “<a href="https://www.zachleat.com/web/comprehensive-webfonts/">A Comprehensive Guide to Font Loading Strategies</a>” is <strong>the</strong> place for that info.
</p>
<p>
I didn’t end up using one of Zach’s gold standards. I wasn’t ready to
introduce JavaScript to assist in font loading. I wanted an HTML/CSS only
approach for now. I have other performance improvements to make in the future,
so I’ll revisit font loading then.
</p>
<p>
I went with a combination of approaches in the post. I’m using; standard
<code>@font-face</code>, <code>font-display</code>, and preloading. In the
<code>head</code> of my pages I preload two of the four weights I use.
</p>
<pre><code class="language-html"><link rel="preload" href="/fonts/protogroteskweb-light.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fonts/protogroteskweb-bold.woff2" as="font" type="font/woff2" crossorigin>
</code></pre>
<p>
I don’t preload the regular and extra light weights because I don’t use them
in critical styles. They can load later via <code>@font-face</code> without
bothering anything.
</p>
<p>
From there, I include a standard <code>@font-face</code> rule for each weight
in my critical inline styles.
</p>
<pre><code class="language-css">/* repeated for each font weight */
@font-face {
font-display: swap;
font-family: "protogrotesk-bold";
src: url("/fonts/protogroteskweb-bold.woff2");
}</code></pre>
<p>
Using <code>swap</code> for <code>font-display</code> helps avoid a flash of
invisible content. There’s a slight shimmy when the custom font loads, but I
can deal with that.
</p>
<h3>Custom properties</h3>
<p>
I use CSS custom properties for font stacks. Again in my critical inline
styles, I have a property for each font weight.
</p>
<pre><code class="language-css">--font-proto-extralight: "protogrotesk-extralight", helvetica, sans-serif;
--font-proto-light: "protogrotesk-light", helvetica, sans-serif;
--font-proto-regular: "protogrotesk-regular", helvetica, sans-serif;
--font-proto-bold: "protogrotesk-bold", helvetica, sans-serif;</code></pre>
<p>
Declaring those stacks as custom props makes it so I don’t have to repeat
myself. It also lets me type less when using each weight:
</p>
<pre><code class="language-css">font-family: var(--font-proto-bold);</code></pre>
<h2>Next</h2>
<p>
A site’s typography is never “done”, only out there. I’ll keep making
improvements and experimenting over time. If you’re interested in a before and
after, I archived the site previous to this change at
<a href="https://v6.tylergaw.com/">v6.tylergaw.com</a>.
</p>
<p>
I’ll continue this series in a few days with “<a href="https://tylergaw.com/blog/realign-2020-logo">Realign 2020: Logo</a>.”
</p>
Realign 20202020-02-27T00:00:00Zhttps://tylergaw.com/blog/realign-2020/<p class="entry-intro">
Like other designers, I enjoy updating my personal site every so often. In my
case I do a <a href="https://tylergaw.com/about#versions">major iteration</a> every couple years
with minor tweaks between. This current incarnation is almost two years old
and I’m ready to make major changes.
</p>
<p>
When I launched this design in 2018 I remember thinking this would be my last
site. I wasn’t under any illusion that I wouldn’t update this site again in
the future. Only that I had a solid foundation that I could build on for a
long time. Even if the changes where major, they’d still be iterations of
existing content and aesthetics.
</p>
<p>
I have two main motivations for this year’s realign. First, I want to present
my work from the last few years in a more complete way. The 2018 verison of
this site listed projects, but didn’t go into much detail. There’s a lot there
I want to surface. I’m looking for my next role so I need to present my work
in a way that show’s what I'm capable of.
</p>
<p>
Second, I just want it to look cooler and weirder. Heavy emphasis on weird.
With every iteration of this site since I launched it in 2006 I’ve made
concerted efforts to make it look much different than the standard web
designer portfolio/blog website. I don’t have anything against other folks’
sites, I just want mine to look different and strange.
</p>
<h2>Writing about it</h2>
<p>
Plenty of other designers are iterating on their personal sites and writing
about their process. I enjoy reading those. So, I’m going to follow their lead
and write about my process.
</p>
<p>
I’ve already started the process in a rough way. For the last few weeks I’ve
let myself noodle on different areas of the design. I spent a lot of hours
pushing points around in Illustrator for an updated logo and some fun
typography squigglevision. I spent, I don’t know how long just fiddling with
CSS in browser. I wanted time to play a while before I set out to really get
updated content and design together.
</p>
<p>
With the initial play I have a rough idea of what I’ll write about. I’ll
update this post as I go to keep a reference. Some of the topics will change
and I’ll add more as I go, but below is a rough outline.
</p>
<h3>Realign 2020 Posts</h3>
<ul>
<li>
<a href="https://tylergaw.com/blog/realign-2020-typography">Realign 2020: Typography</a>
</li>
<li>
<a href="https://tylergaw.com/blog/realign-2020-logo">Realign 2020: Logo</a>
</li>
<li>
<a href="https://tylergaw.com/blog/realign-2020-color">Realign 2020: Color</a>
</li>
<li><a class="link--future"><s>Realign 2020: Content</s></a></li>
<li><a class="link--future"><s>Realign 2020: Netlify</s></a></li>
<li><a class="link--future"><s>Realign 2020: Tooling Updates</s></a></li>
</ul>
<p>
After the the illustration work and in browser playing, I jumped back into
Sketch to try to get at a more concrete plan and to play more. I won’t
continue in Sketch to get a “complete” design. I prefer to do a lot in CSS.
But here's a zoomed out screenshot of my current mess to get an idea of what
I’m up to.
</p>
<figure>
<img src="https://tylergaw.com/blog/assets/post-image-realign-2020-initial-sketch.png" alt="A screenshot of a messy Sketch document showing the current status of this site’s design realign" />
<figcaption>
WIP design realign
</figcaption>
</figure>
<p>
You can see shell pages started for projects. I have six main buckets of work
that I want to present in detail. There’s a lot more, but those are the ones
that I’ll have the time and energy to pull off right now.
</p>
<p>
Here we go again.
</p>
Using a Custom OAuth Provider with NetlifyCMS2019-08-18T00:00:00Zhttps://tylergaw.com/blog/netlify-cms-custom-oath-provider/<p class="entry-intro">
At <a href="https://www.streetcred.co/">work</a>, we’re in the middle of a site
realign and rebuild. As part of that effort, we’re moving to
<a href="https://www.netlifycms.org/">NetlifyCMS</a>. Implementing NetlifyCMS
was eerily easy for me. But, I did hit one snag when it came to OAuthing
with GitHub. In this post I explain the problem and what I did to get things
working.
</p>
<p>
First, the problem. I didn’t do anything fancy with
the NetlifyCMS installation. I used the standard approach described
in <a href="https://www.netlifycms.org/docs/add-to-your-site/">their docs</a>. One
requirement we had was to not use Netlify. We wanted to keep things on our
AWS infrastructure, because that’s where all our stuff is. We didn’t want
another thing to manage.
</p>
<p>
Before diving in, here are links to examples described in this post. These
might be all you need to get going if you’re building a similar thing:
</p>
<ul>
<li><a href="https://tylergaw.github.io/netlify-cms-github-oauth-provider-client-example">Live client example</a></li>
<li><a href="https://github.com/tylergaw/netlify-cms-github-oauth-provider-client-example">Client example repo</a></li>
<li><a href="https://glitch.com/edit/#!/netlify-cms-github-oauth-provider-example">Server example on Glitch</a></li>
<li><a href="https://github.com/tylergaw/netlify-cms-github-oauth-provider-server-example">Server example repo</a></li>
</ul>
<p>
For NetlifyCMS authenticaton I went with <a href="https://www.netlifycms.org/docs/authentication-backends/#github-backend">GitHub backend</a>.
I got lost there. The docs say:
</p>
<blockquote>
<p>To enable basic GitHub authentication:</p>
<ol>
<li>Follow the authentication provider setup steps in the Netlify docs.</li>
<li>Add the following lines to your Netlify CMS config.yml file:</li>
</ol>
<cite>
<a href="https://www.netlifycms.org/docs/authentication-backends/#github-backend">NetlifyCMS docs, "Authentication & Backends - GitHub Backend”</a>
</cite>
</blockquote>
<p>
I didn’t want to use Netlify. But I also went ahead and followed the docs they
referenced in step one. Even with that, I couldn’t quite figure out what
needed to happen. Step two above was fine. But, I could tell something was
missing.
</p>
<h2>The problem</h2>
<p>
When I tried to log in to the NetlifyCMS on our
deployed site, I would hit a 404. So I knew I was missing some piece, but
was unclear what that was. I’d spent a while banging my head against it so
I asked <a href="https://gitter.im/netlify/NetlifyCMS?at=5d4da39b9507182477ac29cf">a question</a>
in the NetlifyCMS Gitter room, but didn’t find help there.
</p>
<h2>The solution</h2>
<p>
<strong>To use a GitHub backend with NetlifyCMS, you have to have your own server
to handle OAuth</strong>. This is a requirement of GitHub’s authentication
flow. The good news about that, is that it’s a standard OAuth flow. The
bad news about that, is that it’s a standard OAuth flow.
</p>
<h3>Prior art</h3>
<p>
<a href="https://github.com/vencax/netlify-cms-github-oauth-provider">vencax/netlify-cms-github-oauth-provider</a>
is an example of what’s needed. It’s mostly an example of a standard OAuth flow with
a couple—very—helpful NetlifyCMS-specific bits. That got me almost there, but I still
couldn’t get everything working. I was still having trouble understanding
how all the pieces fit together.
</p>
<p>
What I needed to do was build my own server to handle the OAuth flow.
This is a thing I’ve done and <a href="https://tylergaw.com/articles/jribbble-three-and-oauth/">written about before</a>.
OAuth is like that for me. I set it up. Deploy it. Forget it. Then have to
give myself a refresher to do again. That’s what the server example in this
post is.
</p>
<h3>The server</h3>
<p>
I went with <a href="https://glitch.com/">Glitch</a> for this example. For me
it’s quickest way to get a server spun up. And the easiest way for me to share
it with you.
</p>
<p>
I put steps in the <a href="https://github.com/tylergaw/netlify-cms-github-oauth-provider-server-example#what-you-need-to-do">README</a>
to explain what you need to do to get this up and running for your own use.
</p>
<p>
The user-facing part of the <a href="https://glitch.com/edit/#!/netlify-cms-github-oauth-provider-example">server example</a>
exists, but it’s not meant for direct use. If you try to use it directly it’ll
look like it’s not doing anything. The idea is for you to copy the
example—either on Glitch or your own server—and point your NetlifyCMS client
at it using the <code>base_url</code> key described below and in the client
example.
</p>
<p>
The code is 99% standard OAuth handshake handling.
A <code>GET</code> followed by a <code>POST</code> to GitHub’s OAuth resources
to retrieve the OAuth access token. I left comments in the code to point out what’s
happening and what’s NetlifyCMS-specific. The biggest chunk of code that
NetlifyCMS needs is this;
</p>
<!-- prettier-ignore-start -->
<pre><code class="language-javascript">const postMsgContent = {
token: body.access_token,
provider: "github"
};
const script = `
<script>
(function() {
function recieveMessage(e) {
console.log("recieveMessage %o", e);
// send message to main window with the app
window.opener.postMessage(
'authorization:github:success:${JSON.stringify(postMsgContent)}',
e.origin
);
}
window.addEventListener("message", recieveMessage, false);
window.opener.postMessage("authorizing:github", "*");
})()
</script>`;</code></pre>
<!-- prettier-ignore-end -->
<p>
A couple bits of formatting aside, I took this from <a href="https://github.com/vencax/netlify-cms-github-oauth-provider/blob/master/index.js#L74">vencax/netlify-cms-github-oauth-provider</a>.
This chunk of code is a little tough to follow. The gist of it is that your
NetlifyCMS client JavaScript is gonna get your GitHub access token via
<code>postMessage</code>. I tried to find the postMessage handling code in
the NetlifyCMS code, but haven’t been able to track it down yet. If anyone
knows where that is, <a href="mailto:me@tylergaw.com">I’d love to know</a>.
</p>
<p>
We put this <code>script</code> in a string because we have the
server respond with it. You could also put this in HTML and return the
access token a different way, but this a way to make short work of it.
</p>
<h3>The client</h3>
<p>
There’s a single customization needed to make this work with the standard
GitHub backend NetlifyCMS configuration. The <code>base_url</code> key.
</p>
<p><code><pre class="language-yml">backend:
name: github
branch: master
repo: tylergaw/netlify-cms-github-oauth-provider-client-example # change this to your repo
base_url: https://netlify-cms-github-oauth-provider-example.glitch.me # change this to your OAuth server</pre></code></p>
<p>
The other items in <code>config.yml</code> are standard NetlifyCMS config
items. <code>base_url</code> needs to be a public URL to the server that
handles OAuth for your GitHub application. Like
the example in this post.
</p>
<p>
You can see in the <a href="https://github.com/tylergaw/netlify-cms-github-oauth-provider-client-example">client code</a>
that this is a no frills NetlifyCMS setup.
</p>
<h2>That’s all</h2>
<p>
We’re a similar setup at <a href="https://streetcred.co/">StreetCred</a> on
our soon-to-launch new site and so far, so good. NetlifyCMS is an excellent
project. One that I’ve been wanting for years. It checks all the boxes that
other CMSs don’t and I look forward to using it more.
</p>
<p>
If this post and/or the examples helped you, or if you try to use it and get
stuck, send me an email at <a href="mailto:me@tylergaw.com">me@tylergaw.com</a>
and let me know.
</p>
Good Things2019-08-04T00:00:00Zhttps://tylergaw.com/blog/good-things/<p class="entry-intro">
Folks in design and technology have a tendency towards negativity. Any day
of the week you can read Twitter, blog posts, and have conversations with co-workers
and hear people declare, with self-appointed authority, that anything they don’t
find value in is bad.
</p>
<p>
Here’s a non-exhaustive, unordered list of things about my work—maybe yours
too—that are good.
</p>
<ul>
<li>HTML is good</li>
<li>The WHATWG is good</li>
<li>CSS is good</li>
<li>The CSSWG is good</li>
<li>Sass is good</li>
<li>PostCSS is good</li>
<li>CSS-in-JS is good</li>
<li>CSS Modules is good</li>
<li>Sketch app is good</li>
<li>Photoshop is good</li>
<li>Illustrator is good</li>
<li>Affinity Designer is good</li>
<li>Paying for fonts is good</li>
<li>JavaScript is good</li>
<li>jQuery is good</li>
<li>React is good</li>
<li>React Native is good</li>
<li>Redux is good</li>
<li>Redux Thunk is good</li>
<li>Redux Saga is good</li>
<li>node is good</li>
<li>Babel is good</li>
<li>webpack is good</li>
<li>rollup.js is good</li>
<li>gulp is good</li>
<li>Metalsmith is good</li>
<li>Handlebars is good</li>
<li>Prettier is good</li>
<li>npm is good</li>
<li>yarn is good</li>
<li>Python is good</li>
<li>Django is good</li>
<li>Flask is good</li>
<li>PyPI is good</li>
<li>PHP is good</li>
<li>Ruby is good</li>
<li>Rails is good</li>
<li>Atom is good</li>
<li>TextWrangler is good</li>
<li>Vim is good</li>
<li>RSS is good</li>
<li>Server-side rendering is good</li>
<li>Client-side rendering is good</li>
<li>Progressive enhancement is good</li>
<li>Being positive is good</li>
<li>Being earnest is good</li>
<li>Being kind is good</li>
</ul>
<p>
There are infinite more things that are good about being a designer and
engineer. If you read my list and thought; “Thing X is also good, why isn’t it on your list?”.
It’s not that I don’t think thing X is good. It’s either that I; didn’t think
of it, or don’t have enough knowledge about it to know it’s good. For those,
I encourage you to make your own list of Good Things.
</p>
A Library2019-04-21T00:00:00Zhttps://tylergaw.com/blog/a-library/<p>
<b>Update, early 2021:</b> I changed the name and URL to <a href="https://tylergaw.com/bookshelf">Bookshelf</a>. Just seemed like a better fit.
</p>
<p>
I don’t read enough books. And when I do, I don’t read a wide enough variety
of them. I’m working to fix this by doing three things:
</p>
<ol>
<li>I’m reading more books</li>
<li>Always having a book I want to read (that I’m actually interested in, if I don’t like it, I put it down and walk away)</li>
</ol>
<p>
For step number two, I either buy two books at once or as soon as I
finish a book, I go get another one. Lately, at the excellent
<a href="https://www.mcnallyjackson.com/">McNally Jackson</a> on Prince St.
near my apartment.
</p>
<p>
The third thing I’m doing is documenting the book I’m reading and the books
I’ve read in <a href="https://tylergaw.com/library">Library</a>. Nothing fancy, just a list
book titles, authors, and the month and year I read the book. I’m not making
an attempt to list books before 2019 right now. Just starting with what’s
fresh in my mind.
</p>
<p>
I decided I wanted this after reading
<a href="https://austinkleon.com/2019/03/21/how-to-read-more-3/">Austin’s, “How to read more”</a>.
In it, he mentions <a href="https://www.artgarfunkel.com/library/list1.html">Art Garfunkel’s</a>
list of read books. That list is amazing. I hope to maintain
my list for even a portion as long as he as.
</p>
No More Google Analytics2019-02-19T00:00:00Zhttps://tylergaw.com/blog/no-more-google-analytics/<p>
This website has had Google Analytics tracking visitors for at a least a decade.
I’m not sure of the exact date I added it, but I’d guess 2007-2008. As I published this,
I removed the JavaScript for it and deleted the property in the GA dashboard.
</p>
<p>
For this site, the numbers serve no purpose. They’re a vanity metric I either
never look at or anxiously check when I have a burst of traffic for whatever
reason. <b>That anxious checking</b>. It’s one more set of numbers to obsess
over. The Unread emails, Slack notifications, Tweets, favorites, texts,
and on and on and on. Ditching one set of numbers that want everything and give
nothing feels good.
</p>
<p>
Also, I don’t want this site to track people in any way. You have enough of
that. I know my use of analytics isn’t responsible for you getting ads about
a jacket you looked at once on every other website you visit forever. But, hey
this is now one less site that’s collecting information about where you’ve been
on the Web. We could all use that.
</p>
Designing Anthologies: Initial Concepts2018-04-21T00:00:00Zhttps://tylergaw.com/blog/designing-anthologies-concepts/<p class="entry-intro">
Another day, another side project. My latest is <a href="https://anthologies.co/">Anthologies</a>. It lets you package links to share stories. Or–more accurate–that’s what it will do when I launch it. I bought the domain name two months ago and now I’m getting to work. I plan to share as much of my design and development process as possible. This post is a look at early design concepts and a peek into my thoughts on how I approach a new design.
</p>
<p>
First things first. If this idea sounds familiar, it’s because it’s been a thing before. In 2012, when I was on the Readability team, we designed and built <a href="https://web.archive.org/web/20160704152104/http://readlists.com:80/">Readlists</a>. Readlists shut down in 2016. It happens. But I miss it and I know other people do to. I don’t think it’s a huge number of people, and that’s OK. My goal for Anthologies is to build a small tool that does one thing well. If only a few people use it, that’s A-OK with me. And much cheaper. (I plan to write about my approach to building this in a cost-efficient way as soon as I figure out how to do that. I’m following the serverless path right now.)
</p>
<h2>MVP</h2>
<p>
At its core, an Anthology is a title, description, and list of urls or “entries” as I’m referring to them. For the MVP, I’m also going to expand that to include details about each entry as they’re available. Each entry can have a title, excerpt, author, publication, and image. If each entry has those or not will depend on the contents of the page at the url. I know from experience that parsing pages for those details is unpredictable. So my design needs to be flexible.
</p>
<h2>Systems Check</h2>
<p>
Like a lot of designers, I sit down and get right to work on a new design by staring at fonts for an hour or two. For Anthologies, I wanted what I usually want from a typeface. I wanted sturdiness at small sizes and a good range of weights. I wanted a super heavy weight for stand-out titles. I also had a specific type hang-up this time around; I wanted a typeface with a two-story lowercase “g”. In early logo and UI sketches I kept drawing two-story g’s so I had it stuck in my brain and didn’t see a reason to fight it. I settled on <a href="https://typekit.com/fonts/aaux-next">Aaux Next</a>. It has a beautiful two-story g. So far I’ve used the regular, medium, black, and ultra weights.
</p>
<p>
Another way I like to put off designing is to fiddle with grid configurations. Sketch makes this way too easy. After the proper amount fiddling, I landed on a grid that seems to work well. The grid is seven–191 pixels wide–columns with ten pixel gutters. I set row gutters at twenty pixels, and every now and then actually set text on them the right way.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-anthology-grid.png" alt="The seven column grid in use for the first anthologies.co concepts. It's slightly off center to the right. The gap on the left gives room for the branding banner." />
<figcaption>
The 7-column grid.
</figcaption>
</figure>
<p>
If you’re looking at the grid image you might notice it’s off-center to the right. That’s on purpose. You’ll see later that each mock-up includes a banner with branding on the left. I didn’t want that to use the grid. I only place the primary content on the grid.
</p>
<h2>The Concepts</h2>
<p>
With ample procrastination out of the way, I started designing. For content, I used <a href="https://longform.org/posts/the-longform-guide-to-sad-retired-athletes"><i>The Longform Guide to Sad Retired Athletes</i></a>. Their collections are a perfect match for the type of content I expect will make up most Anthologies.
</p>
<h3>1. Two-up</h3>
<figure>
<picture>
<source srcset="https://tylergaw.com/articles/assets/post-image-1-anthology-two-up.webp" type="image/webp" />
<source srcset="https://tylergaw.com/articles/assets/post-image-1-anthology-two-up.png" type="image/png" />
<img src="https://tylergaw.com/articles/assets/post-image-1-anthology-two-up.png" alt="A early mock-up of a single Anthology on anthologies.co" />
</picture>
<figcaption>
Iteration 1 “Two-up”: The first mock-up I got to a done-enough state.
</figcaption>
</figure>
<p>
The first go at it. A lot went into this that you don’t see in the mock-up. Much shuffling of text and images happened to get to this point. I don’t know that this is “good”, but it felt like a complete thought. It felt resolved enough. It displays the Anthology content and provides links to the articles. It accomplishes the goals.
</p>
<p>
The most valuable artifact of this iteration are the discovered interface patterns. These are building blocks for every later iteration. There’s a “cover”–made up of the title, description, and byline. The entries–made up of individual entry elements. And within those are smaller components. I think about these in <a href="http://bradfrost.com/blog/post/atomic-web-design/">Atomic design</a> terms. The cover is a organism. It includes an “intro” molecule. The intro contains title, description, and byline atoms. And so on. These might not be the exact elements I end up with, but they’re a way for me to break down the interface into distinct parts. Finding interface building blocks is a goal of my early design process.
</p>
<h4>An Organized Tangent</h4>
<p>
Keeping my artboards, groups, and layers organized in Sketch helps me focus. Naming those items also helps me get a jump on naming elements in HTML and CSS. The names I use in Sketch, I’ll use for future class names, file names, etc. When I look at the Sketch layers palette, I see the DOM.
</p>
<p>
The name of each design concept is the name I gave the artboard in Sketch. Those names might not stick and aren’t always descriptive, but they help me organize my thoughts.
</p>
<figure>
<picture>
<source srcset="https://tylergaw.com/articles/assets/post-image-anthologies-1-sketch-layers.webp" type="image/webp" />
<source srcset="https://tylergaw.com/articles/assets/post-image-anthologies-1-sketch-layers.png" type="image/png" />
<img src="https://tylergaw.com/articles/assets/post-image-anthologies-1-sketch-layers.png" alt="A screenshot of the Sketch.app layers palette for these Anthologies designs" />
</picture>
<figcaption>
The layers palette of the Sketch file for Anthologies mock-ups.
</figcaption>
</figure>
<p>
The first iteration is always the hardest. With it out of the way I started having a lot of fun with the designs. The rest of the concepts are me asking; “what if…?” and then designing answers. These aren’t meant to be separate themes to choose from. They’re exploration of possibilities. It’s me searching for interesting ways to display the same content. In that searching I find design solutions, but also more questions that lead to more design. This was also just good, fast design fun. I could do fifty of these and keep going. I won’t though, because I want to ship this thing sooner than later.
</p>
<h3>2. Grid Dark</h3>
<figure>
<picture>
<source srcset="https://tylergaw.com/articles/assets/post-image-2-anthology-grid-dark.webp" type="image/webp" />
<source srcset="https://tylergaw.com/articles/assets/post-image-2-anthology-grid-dark.png" type="image/png" />
<img src="https://tylergaw.com/articles/assets/post-image-2-anthology-grid-dark.png" alt="A early mock-up of a single Anthology on anthologies.co" />
</picture>
<figcaption>
Iteration 2 “Grid dark”: An iteration on the first mock-up where the entries float on top of the cover image.
</figcaption>
</figure>
<p>
This was a tiny step from the first concept. A little more modern, techy aesthetic. Instead of cropping the cover image, I let it fill the layout, and let the entries float above the background. A joke name I had for it was “Textflix”, because it reminded me of Netflix’s aesthetic. I’m light on words for this one because I think it’s just…fine. I could ship this and it’d work, but it doesn’t really move the needle for me. It invokes a healthy shrug.
</p>
<p>
These first two iterations were dark and felt heavy. For the next concept I lightened things up in color and design elements.
</p>
<h3>3. Text Light</h3>
<figure>
<picture>
<source srcset="https://tylergaw.com/articles/assets/post-image-3-anthology-text-light.webp" type="image/webp" />
<source srcset="https://tylergaw.com/articles/assets/post-image-3-anthology-text-light.png" type="image/png" />
<img src="https://tylergaw.com/articles/assets/post-image-3-anthology-text-light.png" alt="A early mock-up of a single Anthology on anthologies.co" />
</picture>
<figcaption>
Iteration 3 “Text light”: A light-weight, brighter concept using mostly text and only a cover image.
</figcaption>
</figure>
<p>
Along with a lighter aesthetic, in this concept I changed grid usage. In the first two concepts, I gave the cover three columns and the entries four. In this concept, I gave the cover image more prominence. When I saw that, it felt like it needed room to breathe. With that, I gave it another half column and reduced the entries to three-and-a-half columns.
</p>
<p>
Content-wise, I introduced entry excerpts. The layout of the first two concepts worked better without excerpts. It feels a bit off to exclude them for entries though. It seems like the reader needs a bit more of a hook to decide where they want to start.
</p>
<p>
In the future, excerpts could be curator-configurable per Anthology.
</p>
<h3>4. Text Light Imgs</h3>
<figure>
<picture>
<source srcset="https://tylergaw.com/articles/assets/post-image-4-anthology-text-light-imgs.webp" type="image/webp" />
<source srcset="https://tylergaw.com/articles/assets/post-image-4-anthology-text-light-imgs.png" type="image/png" />
<img src="https://tylergaw.com/articles/assets/post-image-4-anthology-text-light-imgs.png" alt="A early mock-up of a single Anthology on anthologies.co" />
</picture>
<figcaption>
Iteration 4 “Text light imgs”: A tweak on Text light. This time including an image thumbnail with each entry.
</figcaption>
</figure>
<p>
This was the first concept I tried using an image for each entry. When I saw them in place, it felt like the cover image could go away. The title and description could shoulder the work of being the cover.
</p>
<p>
For this concept I also tried an off-white background color. I pulled this from Readability, Instapaper, Pocket, et al. In those, a similar color tended to be used for a sepia theme. I’m luke-warm on it.
</p>
<p>
I explored options for the branding banner here too. I wanted to see if a stark contrast between banner and content could help the banner detach and fade away. Sometimes when an element is so much different than the rest of the design, it can make it easier to consider it, then ignore. I’m still not sure if it’s working or not, but it’s interesting.
</p>
<h3>5. Text Light Two</h3>
<figure>
<picture>
<source srcset="https://tylergaw.com/articles/assets/post-image-5-anthology-text-light-two.webp" type="image/webp" />
<source srcset="https://tylergaw.com/articles/assets/post-image-5-anthology-text-light-two.png" type="image/png" />
<img src="https://tylergaw.com/articles/assets/post-image-5-anthology-text-light-two.png" alt="A early mock-up of a single Anthology on anthologies.co" />
</picture>
<figcaption>
Iteration 5 “Text light two”: There's a lot more going on in this one.
</figcaption>
</figure>
<p>
In this concept, I let myself toss around more design elements. Scattered orange blocks here, a few tech lines there, an angle on the cover image, and so on. With the orange blocks, I was thinking about something like a 1970s jazz album cover. Or at least how I pictured them looking. I also switched the title and entry headings to the Ultra weight of Aaux Next. They felt like they needed some extra heft.
</p>
<p>
With the introduction of more design elements, it felt like the eye needed a little break. To give it that rest, I removed the image thumbnails for each entry.
</p>
<h3>6. Spartan</h3>
<figure>
<picture>
<source srcset="https://tylergaw.com/articles/assets/post-image-6-anthology-spartan.webp" type="image/webp" />
<source srcset="https://tylergaw.com/articles/assets/post-image-6-anthology-spartan.png" type="image/png" />
<img src="https://tylergaw.com/articles/assets/post-image-6-anthology-spartan.png" alt="A early mock-up of a single Anthology on anthologies.co" />
</picture>
<figcaption>
Iteration 6 “Spartan”: Just text and images-as-content
</figcaption>
</figure>
<p>
After Text Light Two with all its fiddly bits, I wanted to try one the other way. For this concept I let the content speak for itself. Text and images as content with–almost–no other design elements. I kept one element in place, the subtle border between the cover and entries. You might also notice I increased all the font sizes. The title and each entry title felt like they needed more oomph to take center stage. I was looking at–and taking cues from–the New York Times on this one.
</p>
<p>
At this point, this concept feels the strongest in terms of practicality and broad use. When I start building, I’ll most likely use this as a guide.
</p>
<h3>7. Party</h3>
<figure>
<picture>
<source srcset="https://tylergaw.com/articles/assets/post-image-7-anthology-party-1.webp" type="image/webp" />
<source srcset="https://tylergaw.com/articles/assets/post-image-7-anthology-party-1.png" type="image/png" />
<img src="https://tylergaw.com/articles/assets/post-image-7-anthology-party-1.png" alt="A early mock-up of a single Anthology on anthologies.co" />
</picture>
<figcaption>
Iteration 7 “Party 1”: Just throwing everything at the wall to see what sticks.
</figcaption>
</figure>
<p>
Oooooh shit, here we go! I needed to see one like this. I took about a day break between Spartan and this concept to clear my head. Then I threw everything at the wall. I’ve been reading posts on <a href="https://theoutline.com/">The Outline</a> and enjoying the design they’re doing. I lifted ideas wholesale from them on this concept. That’s a fun thing about doing early concepts like this, it’s OK steal. It’s more about serious play than producing a finished piece.
</p>
<p>
I don’t know if a concept like this will make it to browsers, but it could. I don’t think it’s “bad.” Depending on the tone of the Anthology, something like this could work well. I plan to explore this type of aesthetic more as I go.
</p>
<h2>HTML/CSS Here We Come</h2>
<p>
Designs like this are never “done”, but this one is ready for the next step: browsers. Using Spartan as a base, I’ll build an Anthology page using static-content. As soon as you get in a browser you learn a ton. The design will shift and shimmy from the static mocks here to meet the needs of its ultimate home.
</p>
<p>
As I mentioned before, I have more posts I want to write about my process. When I dive into these projects I let myself get pulled into the weeds in myriad areas of design and development. It’s tons of fun and it’s important to share. Keep an eye out for more.
</p>
Jribbble Three and Helping People OAuth2018-04-15T00:00:00Zhttps://tylergaw.com/blog/jribbble-three-and-oauth/<p class="entry-intro">
In December 2017, Dribbble <a href="http://developer.dribbble.com/changes/2017-12-05-api-v2/">announced</a> version two of their API. In the same post, they set March 26, 2018 as the deprecation date of version one. I’ve maintained Jribbble for almost <a href="https://github.com/tylergaw/jribbble/commit/0895bed9b12d25dbf9ec04073acf64823a512267">8 years now</a>. It’s a JavaScript library to help fetch data from the Dribbble API. In that time it’s gone through plenty of iteration to keep up with Dribbble API changes. Jribbble 3.0 continues that trend.
</p>
<p>
To date, Dribbble API changes have been additive. API features have given users more access to more of data via the API. As Dribbble explained in <a href="http://developer.dribbble.com/changes/2017-12-05-api-v2/">their post</a>, version two goes the other way, it removes a lot of functionality.
</p>
<p>
The reduced functionality seems fine though. In my time working on Jribbble, most conversations I’ve had with designers are about using it to get their own shots. That’s still available, and the focus of API version two. My guess is most users of Jribbble won’t lose out. They will need to make code changes to continue using Jribbble though.
</p>
<p>
I’ve had a few conversations with folks on <a href="https://github.com/tylergaw/jribbble/issues/18">GitHub</a> and email about updating to Jribbble 3.0. The biggest change is that Jribbble is no longer a jQuery plugin. It’s now a standalone library. I write more about the decision to remove jQuery later in this post.
</p>
<p>
The other big change is the shape of Jribbble’s public API methods. Here’s a 2.x vs 3.x example of a user getting their own shots with Jribbble:
</p>
<pre><code class="language-javascript">// Jribbble 2.x
$.jribbble.setToken("<access_token>");
$.jribbble.users("tylergaw").shots().then(function(shots) { /* Work with shots JSON */ });</code></pre>
<pre><code class="language-javascript">// Jribbble 3.x
jribbble.setToken("<access_token>");
jribbble.shots(function(shots) { /* Work with shots JSON */ });</code></pre>
<p>
The Jribbble API is no longer chainable or promise-based. Again, I detail more of the code decisions later in this post. We can still do the same thing, but now in a more concise way.
</p>
<h2 id="a-new-barrier-to-entry">A New Barrier to Entry</h2>
<p>
With Dribbble API version one, we could make requests using read-only client access tokens. When creating an application via the Dribbble UI, they would generate the client access token. We could then use that token to make requests to the API.
</p>
<p>
In version two, those easy-to-use, auto-generated client access tokens are gone. Now, all requests must use a <a href="https://stackoverflow.com/questions/25838183/what-is-the-oauth-2-0-bearer-token-exactly">bearer token</a>. To get a bearer token, you have to go through the <a href="http://developer.dribbble.com/v2/oauth/#web-application-flow">OAuth2 flow</a>.
</p>
<p>
Depending on your experience with OAuth, you might read that and think; “Sure, no problem. OAuth is easy” or “uhhhh, what’s an OAuth?” or somewhere in between. At any experience level, if you only want to make a single API request to display your Dribbble shots on your personal site, it’s a rigmarole.
</p>
<p>
When I first read about the new auth I thought, “welp, Jribbble is dead. As is all client-side JS access to the Dribbble API.” I thought this beause in most cases, you shouldn’t put OAuth-generated bearer access tokens in public code. Doing so makes them available for anyone to find and act on behalf of token owners in any–malicious–way they want.
</p>
<p>
<a href="https://twitter.com/tylergaw/status/951532274482368512">I asked the Dribbble folks</a> about this and they let me know that in this case, putting access tokens in public code would be OK because they’re read-only. A bad actor could still snag your access token and use it to abuse the API on your behalf. I’d guess by making a ton of nonsense requests. The Dribbble API has rate-limiting in place to help mitigate this. At the worst, Dribbble could revoke your access token or disable your app. You’d then need to go back though the process to get a new token and/or create a new app.
</p>
<p>
This change still didn’t sit well with me though. What are less code-savvy folks supposed to do? The main users of Dribbble and Jribbble are Designers. There’s a good chance many of them don’t have experience creating and deploying OAuth flows. That’s exactly who I’ve had conversations with over the years. And that’s who has reached out to me since the API version two announcment. Folks that know enough code to copy, paste, and tweak a snippet of JS, but not enough code to build and deploy a custom OAuth setup.
</p>
<p>
With that, I set out to come up with a way to help them. Because that’s what we do here.
</p>
<h3 id="options">Options</h3>
<p>
My first idea was to create a site where people would authenticate with
a Dribbble application that I owned. They’d show up, tap “connect”, confirm
with Dribbble, then get directed back to my site where they’d get an access token.
</p>
<p>
That would get the job done, but felt wrong. Having people authenticate through my app would mean their access tokens would be in my hands. If I deleted the
app—or my Dribbble account—their tokens would stop working. If a bad actor generated a token then used it to abuse the API, my app could get suspended. Again, taking every user’s access token with it.
</p>
<p>
I needed a way for every Jribbble user to be able to generate access tokens using an application they owned.
</p>
<h3 id="glitch">Glitch</h3>
<p>
If you haven’t used it yet, <a href="https://glitch.com/">Glitch</a> is an excellent tool for sharing and learning code. What sets Glitch apart from similar code-sharing tools is that it gives you the ability to create and share web servers.
</p>
<p>
That’s exactly what I needed for this OAuth problem. I could write the server
necessary for the OAuth flow, then make it available for anyone to–in the parlance of Glitch–“remix” the code to get full access to it. And they can do so without even creating a Glitch account.
</p>
<p>
So that’s what I did. <a href="https://jribbble.glitch.me/">jribbble.glitch.me</a>
is template that anyone can remix to get their own OAuth flow for generating bearer tokens to use with Dribbble API version two.
</p>
<p>
I won’t say that writing a server for this is “easy” because it’s only easy if
you already know how to do it. But, I hope that this will help folks
see that it’s attainable. To demonstrate, I’m going to plop the entire server here. The code is under 100 lines. The rest is comments to help people understand what’s happening at each step along the way:
</p>
<pre><code class="language-js">const express = require("express");
const tiny = require("tiny-json-http");
const nunjucks = require("nunjucks");
const cookieParser = require("cookie-parser");
// We store the relavant Dribbble URLs here for convenience.
// authUrl is passed along to our template in the `/` handler below.
const authUrl = "https://dribbble.com/oauth/authorize";
const tokenUrl = "https://dribbble.com/oauth/token";
// The path of the Callback URL we set when creating our application on Dribbble.
// We reference this URL below in an app.get
const callbackUrl = "/oauth_callback";
// This isn't oauth or dribbble specific, but storing the value of the original
// Glitch project to check if we're seeing the original or a remix.
const ogGlitchUrl = "https://jribbble.glitch.me";
// These environment variables need to be set in the `.env` file. Look to your left 👈
// client id is safe as public value, if those are seen, it's OK.
// client_secret is not safe for public. You need to keep that private at all times.
const client_id = process.env.DRIBBBLE_APP_ID;
const client_secret = process.env.DRIBBBLE_APP_SECRET;
// When we receive an access_token from the api.dribbble.com server, we'll store it
// in this variable.
// Q: Why do we use `let` here instead of `const`?
// A: We use `let` so we can reassign `access_token` to a new value. Here, we set
// an initial value of `null`. Below in the `/oauth_callback` handler we set
// it to a new value of the access_token from the server.
let access_token = null;
// Standard express setup code.
const app = express();
app.use([express.static("public"), cookieParser()]);
// Set up our template library.
// I–Tyler–didn't look too deep into this, this block of code came from the nunjucks
// docs and got me up and running, so good enough for me at this time.
nunjucks.configure(["views", "public"], {
autoescape: true,
express: app,
});
// This is our homepage and the page that does most of the work.
app.get("/", (req, res) => {
const pageUrl = `https://${req.get("host")}`;
// Here, we'll try to set the access_token from a cookie.
// In the callback handler below, we set the access_token cookie on successful auth.
// This isn't something you need to do in your Jribbble uses.
access_token = req.cookies.access_token;
// We use render so we can pass along variables to our template.
// In index.html any time you see {{thing}} or {% %}, we're referencing
// a variable we set here.
res.render("index.html", {
authUrl,
accessToken: access_token,
clientId: client_id,
// Just in case we've hit an authentication error we'll use this to display a message in the template
error: req.query.error,
// We create new boolean value here so we don't send the actual secret to the template.
// Note: I–Tyler–am not sure this is 100% necessary, but it felt best to be overly
// cautious when our app secret. You don't want anyone to have that.
hasClientSecret: client_secret.length,
pageUrl,
isRemix: pageUrl !== ogGlitchUrl,
callbackUrl,
});
});
// This is where our Dribbble applications will come back to after a GET to authUrl
app.get(callbackUrl, async (req, res) => {
const data = {
code: req.query.code,
client_id,
client_secret,
};
try {
// We required `tiny` above in tiny-json-http
// That's a small http library I preferred to use https://github.com/brianleroux/tiny-json-http
// It's not the only way to make requests, there are many different was to accomplish
// this http post request to Dribbble
// Note we are using async/await here. If you're unfamiliar, that's OK. The number one thing
// to know is `await` makes this code act like it's pausing here and waiting for the http
// request to complete before moving on to the following lines of code.
const { body } = await tiny.post({ url: tokenUrl, data });
// As mentioned above, here we're assigning access_token a new value that is your
// shiny oauth access token that gives you public read access to your Dribbble account
access_token = body.access_token;
// NOTE: Setting a cookie want be required in your uses of Jribbble, because you will
// include the access_token in your JavaScript.
res.cookie("access_token", access_token);
// We don't want to stay on the /oauth_callback page, so redirect back home.
res.redirect("/");
} catch (err) {
// If we hit an error we'll handle that here
console.log(err);
res.redirect("/?error=😡");
}
});
app.get("/logout", (req, res) => {
res.clearCookie("access_token");
res.redirect("/");
});
app.listen(process.env.PORT);
</code></pre>
<p>
Again, you can remix this at <a href="https://jribbble.glitch.me/">jribbble.glitch.me</a>
to get full access to the code.
</p>
<p>
Another cool thing. This example is intended for the Dribbble API, but with a
few minor changes, it will work for any standard OAuth flow.
</p>
<p>
If you use that and run into trouble–or think it’s great–let me know on <a href="https://twitter.com/tylergaw">Twitter</a>.
</p>
<h2 id="the-jribbble-rewrite">The Jribbble Rewrite</h2>
<p>
The main part of this project was writing Jribbble 3.0.
<a href="https://github.com/tylergaw/jribbble/blob/v2.0.4/src/jribbble.js">Jribbble 2.x</a> has six public methods to access resources from Dribbble
API version one. Many of those methods have subresources like comments, likes, et al. It also has a chainable, promise-based interface. All these things add a non-trivial amount of plumbing code to make them possible.
</p>
<p>
Dribbble API version two reduces functionality to getting the current
user’s profile, shots, and projects. For Dribbble-approved applications you can get a user’s likes and a list of popular shots.
</p>
<p>
The reduced functionality meant I could cut most of Jribbble’s code. The first
thing I did was audit the usage of jQuery. The only jQuery methods in use were; <code>$.ajax</code>, <code>$.Deferred</code>, and <code>$.extend</code>.
Because of the limited usage, I decided Jribbble 3.0 would not use jQuery.
</p>
<p>
It also didn’t feel necessary to use any type of transpiling process. So no Babel or
TypeScript or the like. I would only write good-ole browser JavaScript. Also, I wanted 3.0 to work in as many browsers as possible, so I only used old-timey
JS. No arrow functions, no <code>let</code> or <code>const</code>, etc. It wasn’t that bad. This is a small library so
restraining myself to older JS wasn’t a problem. And writing it directly instead of relying on Babel keeps the file size smaller.
</p>
<p>
I could have used newer features and still probably ended up with wide-enough support, but it was a fun challenge. And it reminded me of writing JS in years past.
</p>
<p>
This was enough of a rewrite that I opened a new, blank file and started
writing instead of reusing 2.0 code.
</p>
<h3 id="30-details">3.0 Details</h3>
<p>
Like 2.0, this version also has six public methods;
<code>setToken</code>, <code>shots</code>, <code>user</code>, <code>projects</code>, <code>likes</code>, and <code>popular</code>. Each method is available on the window-scoped <code>jribbble</code> object.
</p>
<p>
<code>setToken</code> is the same as in 2.0. It’s how users give Jribbble
their access tokens.
</p>
<pre><code class="language-javascript">jribbble.setToken("12345");</code></pre>
<p>
For 3.0 I decided to also allow users to provide their token as an option when calling any of the other methods. For example:
</p>
<pre><code class="language-javascript">jribbble.shots({token: "12345"}, callback);</code></pre>
<p>
That accomplishes the same thing as <code>setToken</code> in a more concise way.
</p>
<p>
From here, I’ll detail three internal functions that handle the lion’s share of what Jribbble can do.
</p>
<h3 id="function-one-get">Function one: <code>get</code></h3>
<p>
I took it back in time for this one. For day-to-day work I use <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">fetch</a> or a wrapper like <a href="https://github.com/axios/axios">Axios</a> to make network requests. For Jribbble I didn’t want to use a third-party library or polyfills for requests. So instead of fetch, I went back to <code>XMLHttpRequest</code>.
</p>
<p>
All Jribbble requests have the same requirements so I was able to abstract the functionality to a common function. I use the internal <code>get</code> function for all requests to the Dribbble API:
</p>
<pre><code class="language-javascript">var get = function(path, callback) {
var url = "https://api.dribbble.com/v2/" + path;
var req = new XMLHttpRequest();
req.addEventListener("load", function() {
if (callback) {
if (typeof callback === "function") {
var ret = {};
<pre><code> if (this.status < 400) {
try {
ret = JSON.parse(this.responseText);
} catch (err) {
ret = {
error: "There was an error parsing the server response as JSON"
};
}
} else {
ret = {
error:
"There was an error making the request to api.dribble.com.",
status: this.status
};
}
callback(ret);
}
}
</code></pre>
<p>});
req.open("GET", url);
req.setRequestHeader("Authorization", "Bearer " + accessToken);
req.send();
};</p></code></pre><p></p>
<p>
Not bad. It’s a standard <code>XMLHttpRequest</code> GET request with an Authorization header. The bulk of the function is guard code to protect against type errors, JSON parsing problems, and network errors. It does what it can to fail with grace if there is a problem.
</p>
<p>
I use the Authorization header to send along the user’s Dribbble access token with every request.
</p>
<p>
The <code>get</code> function will work back to IE7. I figured It’d be safe to not include the old fork to check for <code>ActiveXObject</code>. It’s been years since I’ve typed that, it’s giving me a good chuckle to see it here.
</p>
<h3 id="function-two-createApiMethod">Function two: <code>createApiMethod</code></h3>
<p>
The <code>user</code>, <code>projects</code>, <code>likes</code>, and <code>popular</code> methods all do the same thing. They process N-number of arguments, make a request to the Dribbble API, and call a user-provided callback. The callback receives a single argument, the JSON response from the request.
</p>
<p>
Because they’re all similar, I didn’t want to have to repeat the same code when defining each method. Instead, I abstracted the functionality to <code>createApiMethod</code>.
</p>
<pre><code class="language-javascript">var createApiMethod = function(path) {
return function() {
var args = processArguments.apply(null, arguments);
get(path + args.query, args.callback);
};
};</code></pre>
<p>
The <code>path</code> parameter is passed along to <code>get</code> to build the URL to the Dribbble API.
</p>
<p>
I then define each public method as a member of the <code>api</code> object. Each is a function with a unique path based on its needs.
</p>
<pre><code class="language-javascript">var api = {
...
user: createApiMethod("user"),
projects: createApiMethod("user/projects"),
likes: createApiMethod("user/likes"),
popular: createApiMethod("popular_shots")
};</code></pre>
<p>
This doesn’t provide any extra functionality. It only makes it so I don’t
have to repeat as much code when defining methods. <code>createApiMethod</code> returns a function. Let’s look at what’s happening in the body of that function.
</p>
<pre><code class="language-javascript">var args = processArguments.apply(null, arguments);
get(path + args.query, args.callback);</code></pre>
<p>
The first line is the heavy lifting. It’s function number three that I’ll write about next. For now, it’s important to know that it returns an object with <code>query</code>, <code>callback</code>, and <code>resourceId</code> keys.
</p>
<p>
Those values, plus the <code>path</code> argument, let us build the arguments needed to make the request to Dribbble with <code>get</code>.
</p>
<h3 id="function-three-processArguments">Function three: <code>processArguments</code></h3>
<p>
For me, this is the most interesting bit of code in Jribbble. Every public Jribbble method except <code>setToken</code> uses <code>processArguments</code>. Its purpose is to inspect all arguments passed to Jribbble methods.
</p>
<p>
To show its usefulness, consider the following usage examples:
</p>
<!-- prettier-ignore-start -->
<pre><code class="language-javascript">jribbble.shots(
"456789",
{ token: "12345" },
function(shotObject) { /* Work with JSON */ }
);
jribbble.shots({
token: "12345",
page: 3,
perPage: 5
},
function(shotsArray) { /* Work with JSON */ }
);
jribbble.projects(
function(projectsArray) { /* Work with JSON */ },
{ token: "12345" }
);
jribbble.user(
function(userObject) { /* Work with JSON */ }
);</code></pre>
<!-- prettier-ignore-end -->
<p>
Notice in each I’m providing a different number of arguments of different types. And in the case of <code>projects</code> I’m providing the arguments in a different order. This type of flexibility isn’t possible with a typical method signature. One where I define each parameter when I create the method.
</p>
<p>
For example, imagine a method signature for <code>jribbble.shots</code> like this:
</p>
<pre><code class="language-javascript">var shots = function(shotId, options, callback) { /* Do the work */ };</code></pre>
<p>
That would work for the first usage example, but what about the second? What if I don’t need a single shot and I don’t need to provide an <code>options</code> argument?
</p>
<p>
This is where <code>processArguments</code> comes into play.
</p>
<p>
First, notice when I call the function in <code>createApiMethod</code> I use <code>apply</code>:
</p>
<pre><code class="language-javascript">var args = processArguments.apply(null, arguments);</code></pre>
<p>
This took me a few minutes to get my head around. It’s also hard to write about, but here goes. I need the function that <code>createApiMethod</code> returns to take zero to
three arguments. The Jribbble user, provides those. Using <code>apply</code> let me pass along the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments"><code>arguments</code></a> object received from calls to public API methods. Then, within
<code>processArguments</code> I have access to that original <code>arguments</code> object. Once I have it, I convert it to an array for manipulation:
</p>
<pre><code class="language-javascript">var args = [].slice.call(arguments);</code></pre>
<p>
An important thing to note is <code>processArguments</code> doesn’t define parameters:
</p>
<pre><code class="language-javascript">var processArguments = function() {...};</code></pre>
<p>
Again, that’s not something I can define ahead of time. This whole dance lets
the public methods be flexible in number and order of parameters. It
lets them say “yeah, whatever you got, I’ll take it.”
</p>
<p>
I mentioned earlier, <code>processArguments</code> returns an object with <code>query</code>, <code>callback</code>, and <code>resourceId</code> keys. The work of the function focuses on creating that object.
</p>
<p>
While I can’t define parameters, I do know a few things about potential arguments. I know any callback should be a function and any options should be an object. I also know <code>resourceId</code> is the identifier for a Dribbble shot. A shot id can be a string or a number.
</p>
<p>
I use that knowledge to inspect each item in the <code>args</code> array I created from the <code>arguments</code> object. Depending on the type I assign the item’s value to a local variable.
</p>
<pre><code class="language-javascript">...
var resourceId = null;
var opts = {};
var callback = function() {};
...
for (var i = 0; i < args.length; i += 1) {
switch (typeof args[i]) {
case "string":
case "number":
resourceId = args[i];
break;
case "object":
opts = args[i];
break;
case "function":
callback = args[i];
}
}</code></pre>
<p>
That snippet sets two out of three variables this function needs to return. <code>resourceId</code> and <code>callback</code> are ready to to go. I only use <code>resourceId</code> in <code>jribbble.shots</code>. The Dribbble API has different paths if you’re requesting a single shot or a list of shots. I use the value–or lack of a value–of <code>resourceId</code> to <a href="https://github.com/tylergaw/jribbble/blob/3.0.0/src/jribbble.js#L104">determine the path</a>.
</p>
<p>
What about the third item, <code>query</code>? Also what is this <code>opts</code> object in favor of?
</p>
<p>
In earlier examples, I showed providing an object with token, page, and per_page keys to public methods. Token is the most important one. If a user doesn’t provide an access token, they can’t make requests. After the for loop I check for a token key on the <code>opts</code> object:
</p>
<pre><code class="language-javascript">if (opts.token) {
accessToken = opts.token;
}</code></pre>
<p>
I define <code>accessToken</code> at the root level of the main Jribbble function. This is how I provide the flexibility of setting token with the <code>setToken</code> method or via an options object.
</p>
<p>
At this point in <code>processArguments</code>, if there’s no value for <code>accessToken</code> there’s no reason to continue. I throw an error and let the user know they need to update their code.
</p>
<pre><code class="language-javascript">if (!accessToken) {
throw new Error(
"jribbble needs a valid access token. You can either include this as an option: jribbble.shots({token: '1234'}) or with jribbble.setToken('1234')"
);
}</code></pre>
<p>
If the user provided <code>page</code> or <code>per_page</code>, those keys will also be on the <code>opts</code> object. <code>query</code> needs to be a string that I can append to the URL of any request. So, I need to create that string if necessary. I know that “page” and “per_page” are the only query parameters allowed because they’re <a href="http://developer.dribbble.com/v2/#pagination">documented</a>. I can use that knowledge to build the string based on the user-provided values.
</p>
<pre><code class="language-javascript">var params = ["page", "per_page"]
.map(function(p) {
return opts[p] ? p + "=" + opts[p] : null;
})
.filter(function(i) {
return i;
})
.join("&");</code></pre>
<p>
For each item in the array check to see if that key exists in <code>opts</code>. If it does, return a string beginning with the item plus an equals sign plus the value in <code>opts</code>. If the key isn’t in <code>opts</code> return <code>null</code>.
</p>
<p>
At this point, we could have an array that looks like:
</p>
<pre><code class="language-javascript">["page=4", null]</code></pre>
<p>
That’s if the user set a <code>page</code>, but not a <code>per_page</code> value.
</p>
<p>
Next, I use <code>filter</code> to remove any <code>null</code> values. And finally join the items of the array with an ampersand between each. In the above example we’d end up with “page=4”.
</p>
<p>
<code>processArguments</code> is ready to return an object with the three keys needed. I also need to do one last thing for <code>query</code>. If the user doesn’t need a query, return an empty string. If they do, prefix the <code>params</code> string with a “?” so it’s ready to append to a URL.
</p>
<pre><code class="language-javascript">return {
resourceId: resourceId,
callback: callback,
query: params ? "?" + params : ""
};</code></pre>
<p>
And that’s <code>processArguments</code>. Syntax and even code-wise it’s not fancy, but it does a job and does it in what I think is a clear way.
</p>
<h2>One more iteration complete</h2>
<p>
This was another iteration on a little library I keep going. It has a small userbase, but I know it provides value for them when it’s needed. As the Dribbble API changes, I’ll keep Jribbble in sync as best I can.
</p>
<p>
Again, the OAuth flow project is at <a href="https://jribbble.glitch.me/">jribbble.glitch.me</a> or just <a href="https://jribbble.com/">jribbble.com</a>. The Jribbble source is <a href="https://github.com/tylergaw/jribbble">available on GitHub</a>. If you run into trouble, please open an issue or submit a pull request.
</p>
Dynamic Redux Reducers2018-01-09T00:00:00Zhttps://tylergaw.com/blog/dynamic-redux-reducers/<p>
This post is specific to a need I had on recent React / Redux project. It’s a
common need and one I’d run into before, but this was the first time I needed to
come up with a solution for it. This was difficult for me. I had to slow down
and take time to internalize what I was trying to do and all the pieces involved.
My hope is that this post will help someone else also working to figure
this out.
</p>
<p>
I’ll detail my process in this post. Here’s a
<a href="https://qk3n9xmm3w.codesandbox.io/">live demo</a> and an
<a href="https://codesandbox.io/s/qk3n9xmm3w">editable sandbox</a>.
The best way to see the effects is to use the <a href="https://github.com/zalmoxisus/redux-devtools-extension">Redux DevTools Chrome extension</a>.
</p>
<p>
If you’re reading this I’m going to assume you have knowledge of Redux and
are using it with React by way of <code>react-redux</code>. I'm also going to
assume you’re looking for a solution to a similar problem.
</p>
<h2>What am I trying to do and why?</h2>
<p>
In standard Redux usage, you provide reducer functions at the time you create
the store with <code>createStore</code>. I wanted a way to add reducer functions
later, on demand.
</p>
<p>
A lot of folks need this because their reducers are not available at
<code>createStore</code> time due to code-splitting. That’s a perfect use for
dynamic reducers.
</p>
<p>
My project doesn’t use code-splitting. For this, dynamic reducers were a preference. I
didn’t want to spread info about modules throughout the project structure.
I wanted each feature to live in a directory, isolated as much as possible.
That meant co-locating reducers, components, styles, and so on. I could do
that and still import the reducers to the main reducer creation, but that
would couple module reducers to the main reducer.
</p>
<h2>Existing solutions</h2>
<p>
In my Googling for an existing solution I landing on
<a href="https://stackoverflow.com/a/33044701">this Stack Overflow question and answer</a>.
The answer is from Dan Abramov so I knew it was a way to go. My solution uses
most of the code from that answer.
</p>
<p>
In Dan’s answer, it all made sense to me until his example of how to inject
reducers. I’m using React Router, but I don’t define routes the way he described. I didn’t
want to have to change how I defined my routes for this. I also couldn’t find
official documentation for methods he used in his example so I wanted to avoid
copy / paste. I also wanted to fully understand the code I was adding to my project.
</p>
<p>
It’s worth mentioning two projects I came across in my search.
<a href="https://github.com/ioof-holdings/redux-dynamic-reducer">redux-dynamic-reducer</a>
and <a href="https://github.com/asteridux/paradux">paradux</a>. I didn’t try
either of them because I didn’t see the need in adding another dependency, but
they might work for you.
</p>
<h2>What the demo shows</h2>
<p>
The <a href="https://qk3n9xmm3w.codesandbox.io/">demo</a> shows a simple page
with a link to <code>/records</code>. When page loads, the Redux state tree
contains two keys. One for each reducer function introduced at store creation.
</p>
<p>
There’s a link to the <code>/records</code> page. When you navigate to that
page, I add another reducer function for Records. In the rest of this post
I’ll decribe how I do that.
</p>
<h2>The code</h2>
<p>
You can follow along in the <a href="https://codesandbox.io/s/qk3n9xmm3w">CodeSandbox</a>.
I’ll start with creating the root reducer in <code>/rootReducer.js</code>.
</p>
<pre><code class="language-javascript">import { combineReducers } from "redux";
import layout from "./reducers/layout";
import home from "./reducers/home";
/**
* @param {Object} - key/value of reducer functions
*/
const createReducer = asyncReducers =>
combineReducers({
home,
layout,
...asyncReducers
});
export default createReducer;</code></pre>
<p>
I pulled this code from Dan’s <abbr title="Stack Overflow">SO</abbr> answer.
It has two reducer functions; <code>layout</code> and <code>home</code>.
They’re global reducers, not module level, so they fit well in the root reducer.
</p>
<p>
The key detail here is the <code>asyncReducers</code> parameter. Adding the
contents of it to the object given to <code>combineReducers</code> is
how we add reducers later.
</p>
<p>
Next up is store creation in <code>/initializeStore.js</code>. Again, most
of this code is from Dan’s example.
</p>
<pre><code class="language-javascript">import { createStore } from "redux";
import createReducer from "./rootReducer";
const initializeStore = () => {
const store = createStore(createReducer());
store.asyncReducers = {};
store.injectReducer = (key, reducer) => {
store.asyncReducers[key] = reducer;
store.replaceReducer(createReducer(store.asyncReducers));
return store;
};
return store;
};
export default initializeStore;</code></pre>
<p>
The first line of <code>initializeStore</code> is where we create the Redux
store with the initial reducers from <code>createReducer</code>. In standard
Redux usage, this is all you’d need. The store is set up and ready with the
<code>home</code> and <code>layout</code> reducers.
</p>
<p>
<code>createStore</code> returns a plain object, so we’ll take advantage of that
by tacking helpful items onto it. We’ll use <code>store.asyncReducers</code> to house
our dynamic reducers. With <code>store.injectReducer</code> I deviate from
Dan’s example. The function does the same thing as his <code>injectAsyncReducer</code>,
but I attach it to the <code>store</code> object for convenience that I’ll
show later.
</p>
<p>
<code>injectReducer</code> has two responsibilities. First, store all dynamic
reducers in <code>asyncReducers</code>. This ensures that each time we invoke
<code>injectReducer</code> we don’t lose other dynamic reducers. Next up
is the main work. <a href="https://redux.js.org/docs/api/Store.html#replaceReducer"><code>replaceReducer</code></a>
isn’t custom, it’s part of Redux. It does does what it says on the tin.
Invoking it replaces the reducer function with one you give it.
</p>
<h3>Where things got tricky for me</h3>
<p>
At this point everything seemed straightforward to me, but then I got lost fast. I
have a store, I have a function to add new reducers. But where can I access
that function to invoke it? In all my frantic Googling, I couldn’t find an
example that worked for my setup. So, I sat down to figure out a solution.
</p>
<p>
It took me a while to figure out where I could access that <code>store</code>
object. I had clues though. In my entry point file <code>/index.js</code> I
use the <code>Provider</code> component. This is standard for React / Redux projects.
</p>
<!-- prettier-ignore-start -->
<pre><code class="language-javascript">import React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import initializeStore from "./initializeStore";
import App from "./App";
const store = initializeStore();
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);</code></pre>
<!-- prettier-ignore-end -->
<p>
Giving the <code>store</code> to <code>Provider</code> makes it available to
all child components by way of the <code>connect</code> function. I read more about it and learned that <code>store</code>
is also available in the <code>context</code> of each component.
If you’ve read anything about
React <code>context</code>, you’ve read that you probably shouldn’t use it.
For my purposes here it seemed isolated enough to be OK. Time will tell if that’s correct or not.
More details on my <code>context</code> usage to later.
</p>
<h2>Putting the pieces together</h2>
<p>
I want to use as little code as possible to add reducers. I do that with a
<a href="https://reactjs.org/docs/higher-order-components.html">higher-order component</a>
in <code>/withReducer.js</code>.
</p>
<pre><code class="language-javascript">import React from "react";
import { object } from "prop-types";
const withReducer = (key, reducer) => WrappedComponent => {
const Extended = (props, context) => {
context.store.injectReducer(key, reducer);
return <WrappedComponent {...props} />
};
Extended.contextTypes = {
store: object
};
return Extended;
};
export { withReducer };</code></pre>
<p>
And example usage in <code>routes/Records/Records.js</code>:
</p>
<pre><code class="language-javascript">import { withReducer } from "../../withReducer";
import reducer from "./ducks";
const Records = () => (...);
export default withReducer("records", reducer)(Records);</code></pre>
<p>
I’ll start with usage in <code>Records.js</code>. I import the records
reducer from <code>routes/Records/ducks/index.js</code>. The reducer doesn’t
do much. It sets hard-coded initial state, then returns it as-is.
The component acts like a container component.
I could <code>connect</code> it, but for the purposes of this
demo, left it out.
</p>
<p>
The pertinent bit is the last line. There I invoke <code>withReducer</code> and
provide it a key of “records” and the record reducer. Then I invoke the
returned function, providing the <code>Records</code> component.
</p>
<p>
<code>Records</code> is a React component I import to use as
the value of the <code>component</code> property of a React Router <code><Route /></code>.
</p>
<h3>The withReducer component</h3>
<p>
<code>withReducer</code> is a <a href="https://reactjs.org/docs/higher-order-components.html">Higher-Order Component</a>.
The <code>key</code> parameter becomes the key in the Redux state tree.
The <code>reducer</code> parameter is the reducer to add. It returns a function
that accepts a single parameter, <code>WrappedComponent</code>. That’s expected
to be a valid React component. In the earlier usage example, that’s the
<code>Records</code> component.
</p>
<p>
I’ll jump ahead to an important part of <code>withReducer</code> that was
new to me and might be confusing.
</p>
<pre><code class="language-javascript">...
Extended.contextTypes = {
store: object
};
...</code></pre>
<p>
<code>Extended</code> is a stateless component, so it must define
a <code>contextTypes</code> property to gain access to <code>context</code>.
From the React docs:
</p>
<blockquote>
<p>
Stateless functional components are also able to reference context if contextTypes is defined as a property of the function.
</p>
<cite>
<a href="https://reactjs.org/docs/context.html#referencing-context-in-stateless-functional-components">reactjs.org/docs/context.html#referencing-context-in-stateless-functional-components</a>
</cite>
</blockquote>
<p>
In <code>contextTypes</code> I defined the property I want to access in the
component, <code>store</code>. That uses the
<code>object</code> type from the <code>prop-types</code> library.
</p>
<p>
When a component defines a <code>contextTypes</code> property, it receives
a second parameter, <code>context</code>. That’s visible in
the <code>Extended</code> signature:
</p>
<pre><code class="language-javascript">...
const Extended = (props, context) => {...}
...</code></pre>
<p>
<code>Extended</code> now has access to the <code>store</code> object. That’s
because <code><Provider store={store}></code> in <code>/index.js</code>
makes it available to all child components via <code>context</code>.
</p>
<p>
This happens in the <code>Provider.js</code> source with
<a href="https://github.com/reactjs/react-redux/blob/master/src/components/Provider.js#L26"><code>getChildContext</code></a>
and <a href="https://github.com/reactjs/react-redux/blob/master/src/components/Provider.js#L52"><code>childContextTypes</code></a>.
That code is good reading if you’re looking for examples of <code>context</code> usage.
</p>
<p>
In <code>initializeStore.js</code> I created a function on the store object,
<code>store.injectReducer</code>. Now, I use that to add the new reducer:
</p>
<pre><code class="language-javascript">...
const Extended = (props, context) => {
context.store.injectReducer(key, reducer);
return <WrappedComponent {...props} />;
};
...</code></pre>
<p>
The orginal component doesn’t change. <code>Extended</code> only returns
it with any original properties.
</p>
<h2>How to see this working</h2>
<p>
At this point, the code works. But, this type of change can be difficult to
visualize. As mentioned earlier, the <a href="https://github.com/zalmoxisus/redux-devtools-extension">Redux DevTools Chrome extension</a>.
works best for me.
In the <a href="https://qk3n9xmm3w.codesandbox.io/">demo</a> I included the
devtools snippet when creating the store. If you install the extension and
view the Redux panel, you can see new reducers change the state tree.
</p>
<figure>
<img src="https://d3vv6lp55qjaqc.cloudfront.net/items/1n0J3V3G0j1X2a1M1C00/Screen%20Recording%202018-01-07%20at%2009.52%20PM.gif" alt="Animated gif showing a new reducer added in Redux devtools Chrome extension." />
<figcaption>
Demo of the records reducer being added when navigating to the /records route.
</figcaption></figure>
<p>
To further show the results in the demo, I <code>connect</code>ed the record
route to display record data from the store.
</p>
<!-- prettier-ignore-start -->
<pre><code class="language-javascript">...
const mapStateToProps = (state, props) => {
const { match: { params: { id } } } = props;
return {
recordId: id,
record: state.records[id] || {}
};
};
export default connect(mapStateToProps)(Record);</code></pre>
<!-- prettier-ignore-end -->
<p>
The full code is in <code>/routes/Records/routes/Record.js</code>.
</p>
<h2>A solution</h2>
<p>
As I mentioned earlier, this is a common need in React/Redux projects for
different reasons. I’ve used other, similar methods for dynamic
routes in the past. Other folks have different approaches. With that, this is <em>a</em>
solution, not necessiarly <em>the</em> solution.
</p>
<p>
If this is helpful and you use it as-is or change it to fit your needs, let
me know. There’s always room for improvement.
</p>
Building My First PWA2017-11-20T00:00:00Zhttps://tylergaw.com/blog/building-my-first-pwa/<p class="entry-intro">
I’ve been looking for time and a sandbox to sit down and learn how to build offline-capable/first web sites or “Progressive Web Apps” (PWA) for a while. I learn best with a hands-on approach. <a href="https://colorme.io/" target="_blank" rel="noopener">ColorMe</a> is a site I maintain and a perfect candidate for offline experimentation. This post details the steps I took, the issues I ran into, and things I learned building my first PWA.
</p>
<p>
Quick note. This isn’t a general “How to make a PWA” post. It may not even be a good intro. There are plenty of articles and tutorials to get the basics. This post is specific to the work involved in making ColorMe a PWA.
</p>
<p>
In a lot of what I’ve read about PWAs, there’ve been common steps for building them. A simple-on-the-surface looking process. Here’s one list from Jeremy:
</p>
<blockquote>
<ol>
<li>switch over to HTTPS,</li>
<li>add a JSON manifest file with your metacrap, and</li>
<li>add a service worker.</li>
</ol>
<cite>
<a href="https://adactio.com/journal/12461">Jeremy Keith, “Progressing the Web.”</a>
</cite>
</blockquote>
<p>
OK, that seems easy enough. Except that last step. That seems like it could be a lot to unpack. He does offer a disclaimer:
</p>
<blockquote>
<p>
That last step can be tricky if you’re new to service workers, but it’s not unsurmountable.
</p>
</blockquote>
<p>
I’ll ignore the service worker part of the process for now and focus on the first two items.
</p>
<h2>Using HTTPS</h2>
<p>
ColorMe already has HTTPS in place. I host it on S3 and serve it through CloudFront. I used <a href="https://aws.amazon.com/certificate-manager/">Amazon’s Certificate Manager</a> to add an SSL certificate. HTTPS is in place and has been since launch.
</p>
<h2>Adding a Manifest</h2>
<p>
This is where things got more interesting. I built ColorMe with <a href="https://github.com/facebookincubator/create-react-app">Create React App</a> (CRA) so I made a <code>manifest.json</code> file in the <code>public</code> directory. The same directory as the favicon and <code>index.html</code>. Anything in the <code>public</code> directory gets copied to the <code>build</code> directory as is. That’s what we need for <code>manifest.json</code>.
</p>
<h3>Doesn’t CRA do PWA stuff out of the box?</h3>
<p>
Yes. CRA added built-in support for PWAs in version <code>1.0.0</code>. ColorMe is still on version <code>0.8.4</code>. That was the latest version when I created the project and haven’t had a reason to update.
</p>
<p>
I could have updated CRA to use the built-in PWA, but I didn’t want to miss the opportunity to learn step-by-step. Doing this myself, taking the long, “dumb” way, helped internalize the why and the how of each step. On future CRA-built projects, I’ll use the latest version with built-in PWA support. With that out of the way, here’s all my “metacrap.”
</p>
<p>
I knew there was “stuff” that went in the manifest, but I wasn’t sure about specifics. What keys can I use? What are example values for each key? And what key-values am I supposed to have? The best resource I found for questions number one and two is the MDN <a href="https://developer.mozilla.org/en-US/docs/Web/Manifest">Web App Manifest documentation</a>. It lists available keys and example values.
</p>
<h3>Lighthouse</h3>
<p>
For the third question, I turned to <a href="https://developers.google.com/web/tools/lighthouse/">Lighthouse audits</a> in Chrome dev tools. Before adding anything to the manifest, I ran a PWA audit. It reported items needed to meet the minimum requirements for a PWA (according to the audit).
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-pwa-audit-before.png" alt="" />
<figcaption>
A pre-pwa audit of colorme.io
</figcaption></figure>
<p>
With the audit report as a starting point, I hit each item on the list. Most important, I referenced the manifest in the <code>head</code> of <code>index.html</code>. This uses the CRA-specific <code>%PUBLIC_URL%</code>.
</p>
<pre><code class="language-javascript"><link rel="manifest" href="%PUBLIC_URL%/manifest.json"></code></pre>
<p>
Most items in the manifest are straightforward enough so I won’t go line-by-line. But I will call out a couple items that took a bit more work. You can see the complete file <a href="https://github.com/tylergaw/colorme/blob/develop/public/manifest.json">on GitHub</a> and below:
</p>
<pre><code class="language-javascript">{
"background_color": "#ffffff",
"theme_color": "#B50003",
"display": "standalone",
"short_name": "ColorMe",
"name": "ColorMe",
"start_url": "/",
"icons": [
{
"src": "launcher-icon-48x48.png",
"type": "image/png",
"sizes": "48x48"
},
{
"src": "launcher-icon-96x96.png",
"type": "image/png",
"sizes": "96x96"
},
{
"src": "launcher-icon-192x192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "launcher-icon-256x256.png",
"type": "image/png",
"sizes": "256x256"
},
{
"src": "launcher-icon-384x384.png",
"type": "image/png",
"sizes": "384x384"
},
{
"src": "launcher-icon-512x512.png",
"type": "image/png",
"sizes": "512x512"
}
]
}</code></pre>
<h3>Icon sizes</h3>
<p>
As you can see in the manifiest, I included six different icon sizes. The audit requires two sizes; 192x192 and 512x512. The former for the homescreen icon on Android, the latter as an icon for a splash screen on Android.
</p>
<p>
I’m not sure if the other four sizes are necessary, but I saw those in examples so I figured it wouldn’t hurt to include them.
</p>
<h3>Theme color</h3>
<p>
A requirement–per the audit–was to add a <code>theme-color</code> meta tag to <code>index.html</code>:
</p>
<pre><code class="language-javascript"><meta name="theme-color" content="#B50003"></code></pre>
<blockquote>
<p>
The theme-color meta tag ensures that the address bar is branded when a user visits your site as a normal webpage.
</p>
</blockquote>
<h3>noscript</h3>
<p>
Another failing audit was “Contains Some Content When JavaScript Is Not Available.” For that, I added <code>noscript</code> content. It doesn’t do anything except apologize for not working without JavaScript. It would be better to have some type of useful experience without JS, but I’ll save that for another time. It’s interesting to think how to make a site like this provide value without JavaScript.
</p>
<h3>“According to the audit”</h3>
<p>
I’m using specific language like; “per the audit” and “according to the audit” on purpose here. Some of these aren’t universal. For example, the <code>theme_color</code> property and the <code>theme-color</code> meta tag have no effect on Mobile Safari or Mobile Chrome on iOS as far as I can tell. For the purposes of this exercise, I’m working towards 100% on the audit. I’m sure not every project needs every item. As usual, it depends.
</p>
<h3>The Chrome Dev Tools Manifest Tab</h3>
<p>
Every time I’d make a change to the manifest I’d re-run the PWA audit to check the results. This was slow. I didn’t realize there was a tab in Chrome dev tools for inspecting the <code>manifest.json</code> results. I found it early enough in the process that it helped speed things along. It also has an “Add to homescreen” button to test that mechanism. That’s much appreciated because I don’t have access to an Android device for proper testing. I wrote this in case someone else also doesn’t know about the manifest tab in Google Chrome Dev Tools.
</p>
<p>
At this point I still don’t have a PWA. The audit turns up one last failure:
</p>
<blockquote>
<p>
Failures: Site does not register a Service Worker, Manifest start_url is not cached by a Service Worker.
</p>
</blockquote>
<p>
With the basics of the manifest in place, I turned my attention to the service worker.
</p>
<h2>The Service Worker</h2>
<p>
Like I mentioned above, this was the biggest mystery for me. I understood the general concept of service workers, but I didn’t understand what the goals of a service worker for a PWA were. Sure, it’s JS, it runs in the background, but what’s that JS supposed to <em>do</em>? After spending time with tutorials, examples, and fiddling, I got a clearer picture.
</p>
<h3>The goals of ColorMe’s service worker:</h3>
<ul>
<li>
Store the site’s static files–HTML, CSS, JavaScript, and images–in the <code>window.caches</code> object
</li>
<li>
Intercept all network requests. If the name of the requested file is in <code>window.caches</code>, respond with the cached file instead of making a request to the server
</li>
<li>
Delete stale caches when the cache key changes
</li>
</ul>
<p>
One thing that stuck out early was the <code>caches</code> member on the global <code>window</code> scope. When I first saw <code>caches</code> in use in example service workers, I thought it was a global only in the service worker context. That’s not the case. <code>window.caches</code> is available from any JS.
</p>
<p>
Here’s a quick example of <code>caches</code>. Go to <a href="https://colorme.io/">colorme.io</a>. Open the developer console and run this snippet:
</p>
<pre><code class="language-javascript">caches.keys().then(names => {console.log(names)});</code></pre>
<p>
That should output <code>["colorme-v7"]</code> (the version number might be different). Not much to look at, but you can see that <code>window.caches</code> is a thing in this context. That means you can access caches from any client side JavaScript, not only service workers. That’s pretty cool.
</p>
<h3>Goal 1: Cache Static Files</h3>
<p>
For ColorMe to work offline it needs to cache all critical static files. It’s a single page site so there are only a few; <code>index.html</code>, <code>main.css</code>, <code>main.js</code>, <code>manifest.json</code>, an svg image, and a Google Fonts stylesheet.
</p>
<p>
The CRA build process creates or renames the CSS, JS, and image files. That made things difficult and I’ll describe my process for fixing it later. For now, I’ll pretend the file names are what they are and walk through the code.
</p>
<p>
I created <code>service-worker.js</code> in the <code>public</code> directory. The full file is available <a href="https://github.com/tylergaw/colorme/blob/03946e9540a031075f3f691356d7aa3f4e457a2d/public/service-worker.js">on GitHub</a>.
</p>
<pre><code class="language-javascript">const STATIC_CACHE_NAME = "colorme-v1";
const STATIC_URLS = [
"/",
"/index.html",
"/manifest.json",
"/static/css/main.css",
"/static/js/main.js",
"/static/media/bgTransparent.svg",
"https://fonts.googleapis.com/css?family=Cousine:400|Karla:400,700"
];
self.addEventListener("install", event => {
event.waitUntil(
caches.open(STATIC_CACHE_NAME).then(cache => {
return cache.addAll(STATIC_URLS);
}).then(() => self.skipWaiting())
);
});</code></pre>
<p>
<code>STATIC_CACHE_NAME</code> is a unique key for this cache. <code>STATIC_URLS</code> is the list of files to cache. I’ll explain how I update this list later to account for dynamic file names.
</p>
<p>
In broad strokes, the next lines say:
</p>
<ol>
<li>when the service worker finishes the install process,</li>
<li>find or create a cache with our name,</li>
<li>and put the files we specified in that cache.</li>
</ol>
<p>
There are full descriptions of the install event and <code>waitUntil</code> available. <a href="https://developer.mozilla.org/en-US/docs/Web/API/InstallEvent">MDN</a> is a great one.
</p>
<h4>Skip Waiting?</h4>
<p>
I’ve read docs about <code>skipWaiting</code>, but I’m still a bit hazy on what it does or if I even need it in this context. Enough examples I saw recommended it, so I went with it for now. I’ll learn more about it as I work with it.
</p>
<p>
With those lines, ColorMe’s static assets are snug in a cache. There’s still work to be done though.
</p>
<h3>Goal 2: Serve Cached Files</h3>
<p>
Putting static files in <code>caches</code> isn’t enough on its own. For ColorMe to work offline, we need to tell the browser to look in the cache for those files.
</p>
<pre><code class="language-javascript">self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});</code></pre>
<p>
This snippet listens for all HTTP requests from the user’s browser. <code>event.respondWith</code> prevents the browser’s default <code>fetch</code> handling. That allows us to check if the requested URL–<code>event.request</code>–is in the cache. If it is, respond with the cached file. If it’s not cached, continue with the request to the server using <code>fetch()</code>.
</p>
<p>
ColorMe now works with or without an Internet connection.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-pwa-audit-100-percent.png" alt="" />
<figcaption>
A post-pwa audit of colorme.io
</figcaption></figure>
<h3>Goal 3: Delete Stale Caches</h3>
<p>
If I stopped here ColorMe works offline, but I’d have no way to release updates to users. Cached items have to be deleted, they never expire. The service worker should remove stale caches.
</p>
<pre><code class="language-javascript">self.addEventListener("activate", event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(name => name.includes("colorme") && name !== STATIC_CACHE_NAME)
.map(name => caches.delete(name))
)
}).then(() => self.clients.claim())
);
});
</code></pre>
<p>
This is the most involved-looking code in the service worker, but it doesn’t do much.
</p>
<ol>
<li>get an array of all cache keys with <code>caches.keys()</code>,</li>
<li>remove any keys that don’t contain our name and remove a cache with the exact <code>STATIC_CACHE_NAME</code>,</li>
<li>for each of the remaining keys, delete that cache.</li>
</ol>
<p>
When the service worker activates–every time the page loads–check to see if there are stale caches. If there are, delete them. I determine if a cache is stale with #2 above.
</p>
<p>
In the first code snippet I defined the cache key; <code>const STATIC_CACHE_NAME = "colorme-v1"</code>. When I make changes to any of the cached files and deploy the site, I also change that version number. If a user visited the site when <code>colorme-v1</code> was latest, then visits again when <code>colorme-v7</code> is the latest, the service worker deletes v1 and caches v7.
</p>
<h2>Caching Dynamic Filenames</h2>
<p>
At this point, I’ve met all the goals for ColorMe’s service worker and have a functioning PWA. If you visit ColorMe then turn off your network connection, you should be able to refresh the page and use it as normal.
</p>
<p>
There is one issue I mentioned above. The filenames for the CSS, JS, and image are incorrect. In the earlier snippet, I cached files named;
</p>
<ul>
<li>
<code>"/static/css/main.css"</code>
</li>
<li>
<code>"/static/js/main.js"</code>
</li>
<li>
<code>"/static/media/bgTransparent.svg"</code>
</li>
</ul>
<p>
The CRA build process fingerprints those filenames to something like;
</p>
<ul>
<li>
<code>"/static/css/main.2ebebc14.css"</code>
</li>
<li>
<code>"/static/js/main.7e7a1a8f.js"</code>
</li>
<li>
<code>"/static/media/bgTransparent.e6317315.svg"</code>
</li>
</ul>
<p>
This is common for cachebusting and not only for CRAs. I’ve used and built tons of different asset pipelines over the years that use fingerprinting.
</p>
<p>
I got hung up on this big time. How am I going to tell the service worker to cache files when I have no control over their names? I didn’t find much info on the topic. <a href="https://github.com/w3c/ServiceWorker/issues/657">This issue</a> raises the question and the discussion helped me figure out an approach. I’d need to somehow generate the list of filenames during the build process. I had a few options, a couple of them not good ones;
</p>
<ol>
<li>Run the CRA eject script and change the default webpack build process to generate the service worker</li>
<li>Update to latest CRA and use their built-in PWA support</li>
<li>Walk away from computer, sleep on it, think about a better solution the next morning while walking my <a href="http://leela.dog/" target="_blank" rel="noopener">dog</a></li>
</ol>
<p>
I didn’t want to eject from CRA, because it didn’t seem worth it. I mentioned above, updating to the latest CRA would take away the opportunity to learn. Option #3 sounded best. After getting away from the computer, I realized I had more options than I realized and a solution that <em>should</em> work. It was’t going to be elegant or scalable, but it’d do the job.
</p>
<h3>Piggybacking on the CRA build script</h3>
<p>
The default CRA build script builds the project according to the baked in webpack config. I can’t change the config–without ejecting–but I can add to the build script in <code>package.json</code>. I’d already modified the script to include <code>NODE_PATH=src</code>. That makes it easier to import modules without referencing the full path. ColorMe’s starting build script looked like:
</p>
<pre><code class="language-javascript">NODE_PATH=src react-scripts build</code></pre>
<p>
I knew a couple things. I knew the build process creates a file named <code>asset-manifest.json</code>. The contents of that file include the full fingerprinted names of all static assets used in the site. Example contents of the manifest file:
</p>
<pre><code class="language-javascript">{
"main.css": "static/css/main.2ebebc14.css",
"main.css.map": "static/css/main.2ebebc14.css.map",
"main.js": "static/js/main.7e7a1a8f.js",
"main.js.map": "static/js/main.7e7a1a8f.js.map",
"static/media/bgTransparent.svg": "static/media/bgTransparent.e6317315.svg"
}</code></pre>
<p>
Those are the full filenames I need to cache with my service worker. I need to get those filenames into the service worker file.
</p>
<p>
The first thing I did was add to the build script. Back in <code>package.json</code> I updated the script to look like:
</p>
<pre><code class="language-javascript">NODE_PATH=src react-scripts build && npm run generate-sw</code></pre>
<p>
This says; “run the normal build process, when you’re done with that run this other npm script”. That script looks like:
</p>
<pre><code class="language-javascript">"generate-sw": "node scripts/generate-sw.js"</code></pre>
<p>
To make sure that worked, I created <code>/scripts/generate-sw.js</code> and added a single line; <code>console.log('hello')</code>. Then I ran the build script <code>npm run build</code> to make sure the project built and I saw “hello” in my terminal output. So far so good.
</p>
<h4>The generate script</h4>
<p>
I need to get the filenames out of <code>asset-manifest.json</code> and into the array of filenames to cache in <code>service-worker.js</code>. My plan was to not get fancy with this. I only need to take strings from one file and write them into another file. The fact that the target file is JavaScript is immaterial to this process.
</p>
<p>
The full file is available <a href="https://github.com/tylergaw/colorme/blob/03946e9540a031075f3f691356d7aa3f4e457a2d/scripts/generate-sw.js">on GitHub</a> and I’ll go through the code in detail here.
</p>
<pre><code class="language-javascript">const manifest = require("../build/asset-manifest.json");
const fs = require("fs");
const swPath = "build/service-worker.js";</code></pre>
<p>
First is setup. <code>asset-manifest</code> is JSON so I <code>require</code> it here for use as an object. I’ll use the <code>fs</code> package for reading and writing files. I store the path of the service worker for convenience.
</p>
<pre><code class="code-muted">const manifest = require("../build/asset-manifest.json");
const fs = require("fs");
const swPath = "build/service-worker.js";</code>
<code class="language-javascript">const urlsCSV = Object.keys(manifest)
.filter(k => !k.includes(".map"))
.map(k => manifest[k]);</code></pre>
<p>
Getting more interesting, but still not fancy. The goal of this chunk of code is to build an array of filenames. First, use <code>Object.keys</code> to get the keys from the manifest JSON to loop over an array.
</p>
<p>
Next, use <code>filter</code> to remove keys that include the string “.map.” If you look at <code>asset-manifest.json</code> you’ll see source maps. We don’t want to cache those. I’m not sure if there’s a best practice for or against that, but I decided it didn’t seem right for this project.
</p>
<p>
Now that we only have keys for the files we want to cache, use <code>map</code> to create the array of filenames stored as <code>urlsCSV</code>;
</p>
<pre><code class="language-javascript">[
"static/css/main.2ebebc14.css",
"static/js/main.7e7a1a8f.js",
"static/media/bgTransparent.e6317315.svg"
]</code></pre>
<p>
<strong>A short tangent.</strong> Given the code above, you might be asking; <em>“why didn’t you just use <code>Object.values</code> instead of <code>Object.keys</code> plus <code>map</code>?”</em> That’s a great question with a quick answer. As of this writing, I’m running Node.js version 6.9.1. <code>Object.values</code> is not supported without the <code>--harmony</code> flag until version <code>7.0.0</code>. I didn’t want to upgrade Node.js for this. I’ll do that another time. That’s all.
</p>
<p>
I need to get that array of filenames into the service worker file. Again, this isn’t meant to be fancy or scalable. It’s meant to do the work.
</p>
<pre><code class="code-muted">const manifest = require('../build/asset-manifest.json');
const fs = require('fs');
const swPath = 'build/service-worker.js';
const urlsCSV = Object.keys(manifest)
.filter(k => !k.includes('.map'))
.map(k => manifest[k]);</code>
<code class="language-javascript">fs.readFile(swPath, "utf8", (err, data) => {
if (err) { return console.log("Error trying to read SW file", err); }
const result = data.replace("%MANIFESTURLS%", JSON.stringify(urlsCSV));
fs.writeFile(swPath, result, "utf8", err => {
if (err) { return console.log("Error trying to write SW file", err); }
});
});</code></pre>
<p>
Let’s break this down. First, open the service worker file (<code>swPath</code>) for reading. The error condition isn’t important. I included it to be nice to myself in case something odd happens during a build.
</p>
<p>
The next line is the point of this script. It searches the contents of the service worker file (<code>data</code>) for the unique string <code>“%MANIFESTURLS%”</code>. When found, it’s replaced with a JSON stringified version of our filenames array, <code>urlsCSV</code>. Then, the updated contents are written back to the service worker file.
</p>
<h3>Updates to service-worker.js</h3>
<p>
As mentioned above the generate script needs to find <code>“%MANIFESTURLS%”</code> in <code>service-worker.js</code>. I went back and updated the script to account for that.
</p>
<pre><code class="language-javascript">const STATIC_CACHE_NAME = "colorme-v1";
const BASE_STATIC_URLS = [
"/",
"/index.html",
"/manifest.json",
"https://fonts.googleapis.com/css?family=Cousine:400|Karla:400,700"
];
const STATIC_URLS = BASE_STATIC_URLS.concat(JSON.parse('%MANIFESTURLS%'));
<p>// The install handler is the same as when we started.
self.addEventListener("install", event => {
event.waitUntil(
caches.open(STATIC_CACHE_NAME).then(cache => {
return cache.addAll(STATIC_URLS);
}).then(() => self.skipWaiting())
);
});</p></code></pre><p></p>
<p>
Here’s what I've done. I moved the filenames I know about to <code>BASE_STATIC_URLS</code>. I don’t fingerprint those file names, so they’re safe to hard-code. The important change is next. <code>STATIC_URLS</code> still ends up being an array of filenames, but now it’s a combination of two arrays. The filenames we know about and the generated array of filenames written to this file.
</p>
<p>
<code>STATIC_URLS</code> ends up looking something like this;
</p>
<pre><code class="language-javascript">[
"/",
"/index.html",
"/manifest.json",
"https://fonts.googleapis.com/css?family=Cousine:400|Karla:400,700",
"static/css/main.2ebebc14.css",
"static/js/main.7e7a1a8f.js",
"static/media/bgTransparent.e6317315.svg"
]</code></pre>
<p>
When the project builds the fingerprinted filenames and <code>asset-manifest</code> changes. Then the service worker gets updated with the new filenames.
</p>
<p>
That all adds up to a build process that handles caching of fingerprinted files and one offline-capable PWA.
</p>
<h2>A New Normal</h2>
<p>
I’ve seen folks talking at length about how transformative PWAs are, but I couldn’t grasp it until I went through this process. This felt like building a responsive design for the first time. It’s the realization that this isn’t going to be a gimmick or extra or nice-to-have. It’ll take time for habit to kick in and browsers to catch up, but this will become my default when building sites.
</p>
Starting Something New2017-09-25T00:00:00Zhttps://tylergaw.com/blog/something-new/<p class="entry-intro">
On December 15, 2016 I became unemployed. This was the first time in my career the unemployment was not my choice. The entirety of the company I worked for was RIF’d. If you’re unfamiliar, RIF stands for “reduction-in-force”. We were out of money to pay salaries and RIF was the best case scenario for us at the time.
</p>
<p>
We were lucky. The founder and team all wanted to keep doing the <a href="https://thegroundwork.com/">work</a> we all felt so close to. There’s a story behind that work that I plan to share in the future. Over the course of two months, we were able to rustle enough contract work to pay salaries for the team members that stuck around. We were hired back in late February 2017.
</p>
<p>
In that couple month span I was able to stop and breathe for the first time in two and half years. I got to work on <a href="https://tylergaw.com/articles/rewriting-day-player-for-sketch-40">fun</a> <a href="https://tylergaw.com/articles/sass-to-postcss">side</a> <a href="https://tylergaw.com/articles/introducing-colorme">projects</a>. And I was able to ruminate on things I wanted out of my work, career, and life.
</p>
<p>
Inspired by the loss of a job and the uncertainty and clarity that brings, my thoughts about want I wanted led towards one thing. I wanted to work on a <strong>business that I owned</strong> that I could help build from the ground up. I wasn’t sure what that business should be or how I’d go about it. Then I got an unexpected opportunity from a friend.
</p>
<figure>
<a href="https://www.limbo.io/">
<img src="https://tylergaw.com/articles/assets/post-image-limbo-1.png" alt="" />
</a>
<figcaption>
Limbo is an anonymous job platform
</figcaption></figure>
<p>
On Wednesday, September 13<sup>th</sup>, 2017 <a href="http://www.umbrae.net/">Chris</a> and I launched our new business, <a href="https://www.limbo.io/">Limbo</a>.
</p><p>
Chris visited New York from California during my two month “vacation” and invited me for a drink to catch up. We hadn’t seen each other since I’d last visted the Dary’s Berkeley home about a year before. At one point in our conversation about family and work and life he asked; “Can I pitch you an idea?” Of course. He then laid out the idea and business model for Limbo and asked, “are you interested in working on it with me?”
</p>
<p>
Chris and I met in 2008 when he interviewed me for a position at <a href="https://web.archive.org/web/20080828193240/https://arc90.com">Arc90 <sup>(RIP)</sup></a>. I ended up getting the job and staying there for <a href="https://tylergaw.com/articles/2000-days">five years</a>. In that time, I got to work alongside him on Readability and Readlists and occasional other projects. I saw him conceive of and build <a href="https://kindlingapp.com/">Kindling</a>–which stayed in business for close to a decade. Over the years I saw him do excellent work, make smart decisions, and in general be a great person to be around. Any time I had the chance to work with Chris or even just have a conversation over coffee, I jumped at it.
</p>
<p>
So when Chris comes to me and asks; “I have an idea, do you want to work on it with me?” I say “yes” because I know it’s going to be a great project.
</p>
<p>
As expected, Chris didn’t dissapoint this time around. The next day he emailed me what he had so far. It was a lot. He already had the project set up. He had wireframes and rough mocks. He even sent a spreadsheet full of projections and different business model scenarios. Real adult stuff.
</p>
<p>
So we got to work. Or more accurate, I got to work, he’d already been hard at it.
</p>
<p>
I was still working full-time at The Groundwork and wanted to keep a hard separation between it and Limbo. To do that, I worked on Limbo mornings, nights, and weekends. My schedule would usually go something like; get up, work on Limbo for 30 minutes or so. Switch to The Groundwork for the day. Stop in the early evening. Take a break. Eat food. Then hop back to Limbo and work until I couldn’t keep my eyes open. Repeat.
</p>
<p>
Weekdays were always more productive than weekends even with splitting my time. An occasional rainy Saturday or Sunday I’d get things done, but it’s tough for me to do more computering on the weekend. Too many other things I’d rather do including doing nothing.
</p>
<p>
And so it went like that for months. My schedule made it take longer to get to launch, but we were making progress every day and now it’s a real, live thing.
</p>
<h2>Unknown unknowns</h2>
<p>
The feedback we’ve received since launch has been overwhelmingly positive. So there’s <strong>something</strong> there but, this is a business so we can’t say for certain how things will shake out. The goal is for Limbo to be our full time–paying–jobs. We’re doing everything we can to get there.
</p>
<p>
If you’ve worked with me you know I take ownership of what I’m working on. Not only design and code aspects of what I’m doing. I like to get in there. Co-workers have described me as “vocal.” When I see things in projects or teams or organizations that I think could be better, I offer my thoughts, ideas, and help. This is the opportunity we’re making for ourselves. <strong>We get to ask all the questions.</strong> We get to make decisions about every aspect of the business in a way that reflects our values. This is a chance to make something positive and put it into the world.
</p>
<p>
One example of the decisions we’re making is around money. In our first conversation we discussed seeking funding versus bootstrapping. We’ve been around long enough and seen enough to know that seeking early outside investment often does not end well. We decided bootstrapping was the way to go. It’s not that we think VC is wrong, it just doesn’t feel right for us on this venture, at this time. We want to see if the business model is sustainable from the day one. Not work on it for years, then get to the common start-up point of “I guess it’s time to figure out how to make money?” We either offer enough value to make enough money or we don’t.
</p>
<p>
A huge thank you to everyone who’s helped us with Limbo so far. We’re fortunate to have friends who will take time out of their day to try things out, give us feedback, point us in new directions, and offer encouragement.
</p>
<p>
This feels like the cusp of something. This has the potential to be where we put our energy for years to come. Chris and I have helped design and build world class products for other smart people. Now we’ll see if we can build, nurture, and sustain a business for ourselves.
</p>
The Impact of Flash2017-07-29T00:00:00Zhttps://tylergaw.com/blog/the-impact-of-flash/<p class="entry-intro">
A short Twitter exchange with <a href="https://twitter.com/mciarlo/status/890931236054720513">Mike</a> made me pause and think about the positive impact <a href="https://blogs.adobe.com/conversations/2017/07/adobe-flash-update.html">Flash</a> has had on me. I felt the need to scribble a few loose thoughts and anecdotes about a technology near its end.
</p>
<p>
I’m thinking about the time between late 2004 to sometime in 2008. That's the start of my time working with the Web and computers in any real capacity. Flash was still Macromedia Flash. It was pervasive and incredible.
</p>
<p>
Building websites with HTML and CSS in 2004 to 2007-ish was interesting, but limited. We made 760 pixel fixed-width pages with tiled background images and bad image-based type. And that’s only if you’d made the switch from table-based layouts. Many folks had not. CSS transitions and animations weren’t even on the radar. jQuery wasn't a thing. JavaScript was still considered a toy language for “DHTML” gimmicks by most.
</p>
<p>
Flash filled a need and did it well. It provided a path to design and publish web sites with as far out graphics and layouts as we could conjure. Being able to work outside the capabilities of browsers was helpful for someone like me. I had a traditional art background and still felt stifled by computer-based design tools and the limits of browsers of the day. Flash was a looser way to learn and develop digital design skills. At the time, making Flash sites was flat out more fun and exciting.
</p>
<h2>Movement and sound</h2>
<p>
Flash helped many of us learn timing, pacing, and rhythm. It helped us learn how to design to music. Sometimes real music, sometimes the songs in our heads. These concepts aren’t limited to animation. Through the years, I’ve folded motion design concepts into tons of static design. It’s one way to arrive at richer, more interesting designs. I realize this sounds esoteric, it is. It’s squishy, designer-feel stuff, but it often results in design that stirs emotion.
</p>
<p>
Working in Flash opened doors to designing with other mediums like audio. How many of you also used <a href="http://www.audacityteam.org/">Audacity</a> to create sound effects for use in <code>.swf</code>s? Homemade foley was a constant in my early Flash projects. It was fun and silly and great.
</p>
<p>
Sites like <a href="http://www.2advanced.com/">2advanced.com</a> displayed a breadth and depth to designing with motion and sound on the Web. At the time, you could not find that with sites built with web standards alone. Seeing what was possible opened eyes and removed barriers. If 2Advanced and others could do work like that, so could we. And later, when technology caught up, we learned we could do it without Flash.
</p>
<p>
These concepts and approaches we learned weren’t lost when we stopped using Flash. We rolled them along to CSS and JavaScript and iOS and Android. The rich motion we now expect from digital products is no doubt inspired and learned by earlier work done with Flash.
</p>
<h2>Typography</h2>
<p>
We would not have the typographic capabilities on the Web we have today if not for Flash. <a href="https://en.wikipedia.org/wiki/Scalable_Inman_Flash_Replacement">sIFR</a> let us explore what was possible with custom type in web pages. There were no other options at the time. <code>@font-face</code> would not exist for a couple years. It would be more years still until it had wide enough browser support you could use it.
</p>
<p>
In this instance, Flash was a polyfill. Using Flash to embed custom fonts helped us learn what we wanted from typography on the Web. That knowledge informed specifications and browser vendors were then able to make the specs reality. This feels, to me, like the most day-to-day, “bread and butter” impact Flash had on the Web.
</p>
<h2>Learning to code</h2>
<p>
Everything in this post is from my perspective, but this section is 100% about my experience. Everyone who writes code learned in a different way. I learned to program using ActionScript 2.0 within Flash so I hold it in high regard. ActionScript was my introduction to basic programming concepts; variables, conditionals, loops, and functions. I took to it well I think because it was the first time I’d seen code tied to a fun end result. Write code, see something fun happen in a <code>.swf</code>. It was immediate and tangible so it stuck.
</p>
<p>
Writing ActionScript also taught me how to think in code and convince a computer to do what I wanted. Often times the convincing wasn’t done in an elegant way. I remember my final for an Advanced ActionScript course in college. I got half credit on one programming task because I made it work, but did so in some roundabout way. That’s representative of how I write code today. I may not always have the most elegant solution, but I'll get it done no matter what.
</p>
<h2>It’s really the humans</h2>
<p>
In Mike’s tweet he used “nostalgic.” I wasn’t sure at the time if I felt nostalgia about Flash or not.
</p>
<blockquote>
<p>
a sentimental longing or wistful affection for the past, typically for a period or place with happy personal associations.
</p>
</blockquote>
<p>
After thinking and writing about this, yes I do. I’m not nostalgic about the technology itself though. Flash, like all other technology, is a means to an end. Thinking about Flash reminds me of that time in my life. It reminds me of the work I was doing, the rush of new ideas, and the thrill of making new things. It reminds me of the people I was working with, most of whom I haven't spoken to in the years since. So yeah, I think I’ll always look back on that time with “wistful affection”.
</p>
<p>
We’re doing what we always do, moving on. We learn, we evolve, we evolve our tools. This is a good thing and a constant in our field. At the same time it’s important to witness and remember the steps in our evolution.
</p><p>
Even though it has a couple more years of life, Flash’s time has come and gone. Flash's positives far outweigh its negatives and its impact will persist for years after it’s gone.
</p>
New ColorMe Features2017-01-18T00:00:00Zhttps://tylergaw.com/blog/new-colorme-features/<p class="entry-intro">
Since releasing it two weeks ago, <a href="https://colorme.io/">ColorMe</a> has had time to simmer and I’ve gotten a bunch of great feedback. In that time I’ve made improvements and feature additions. New features include; support for <code>hsl(a)</code> and <code>rrggbbaa</code> colors. Red, green, and blue color adjusters. And the option to choose the format of the output color.
</p>
<p>
The first updates I made I snuck in the day after release. Those were improvements to the color adjuster sliders for touch devices. Or more accurate, devices without <code>hover</code> support. I knew the initial design had usability issues on mobile, but decided to ship it and iterate.
</p>
<p>
The main problem was the horizontal scrolling interfering with the range sliders. The thumb of the sliders was too hard to get a hold of with a finger. And it was tough to adjust the slider instead of scrolling horizontal.
For this first update I didn't change the UI completely for touch devices. For now I only increased the size of the thumb on the range sliders in <a href="https://github.com/tylergaw/colorme/pull/4">this pull request</a>.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-colorme-updates-touch-support.png" alt="A screenshot of touch device use of color adjusters on ColorMe" />
<figcaption>
The new size of an inactive and an active thumb on touch devices. It looks odd here, but easier to see and handle when you’re using a finger instead of a mouse pointer.
</figcaption></figure>
<p>
By default the CSS targets all devices. The range slider thumb is large. Almost comical in size and off center vertically when active. It looks strange in a screenshot, but the larger size and y-offset make it easier to control when using a finger.
</p>
<pre><code class="language-css">.adjusterValRange::-webkit-slider-thumb {
...
height: 1.6rem;
margin-top: -0.8rem;
width: 1.6rem;
}
<p>.adjusterValRange::-webkit-slider-thumb:hover,
.adjusterValRange::-webkit-slider-thumb:active {
margin-top: -1.8rem;
height: 2.5rem;
width: 2.5rem;
}</p></code></pre><p></p>
<p>
To decrease the thumb size on devices with <code>hover</code> support , I used the <code>hover</code> media query available in <a href="https://www.w3.org/TR/mediaqueries-4/#hover">Media Queries Level 4</a>.
</p>
<pre><code class="language-css">@media (hover) {
.adjusterValRange::-webkit-slider-thumb {
...
height: 0.75rem;
margin-top: -0.3rem;
width: 0.75rem;
}
<p>.adjusterValRange::-webkit-slider-thumb:hover,
.adjusterValRange::-webkit-slider-thumb:active {
margin-top: -0.5rem;
height: 1.1rem;
width: 1.1rem;
}
}</p></code></pre><p></p>
<p>
I’m a huge fan of using the <code>hover</code> media query instead of relying on screen size. The issue isn’t caused because the screen is too narrow. It’s caused by not having the fine control a mouse and cursor provide. The new media query isn’t available in all browsers yet, but support is good enough for these purposes.
</p>
<p>
You can see the full CSS for the adjuster range sliders in <code><a href="https://github.com/tylergaw/colorme/blob/2017-01-17T22.58.04/src/components/Adjuster/Adjuster.css#L102">Adjuster.css</a></code>.
</p>
<p>
Usability of the adjusters on touch devices still has room for improvement. There’s an <a href="https://github.com/tylergaw/colorme/issues/5">open issue</a> about it in the repo. Along with the ideas there, I have an alternate UI I’d like to try out. That will likely be the next round of changes I make to the project.
</p>
<h2>Red, green, and blue adjusters</h2>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-colorme-updates-rgb-adjusters.gif" alt="An animated gif showing red, green, and blue color adjusters ColorMe" />
<figcaption>
Adjust the red, green, and blue channels.
</figcaption></figure>
<p>
In the first release I also left out three of the available <a href="https://drafts.csswg.org/css-color/#modifying-colors">color adjusters</a>. Red, green, and blue. Those are in place now. They do what they say on package. Each one adjusts that single channel. Could be helpful for finding that perfect reddish-greenish-blue. Or for finding a good complementary color.
</p>
<h2>New supported formats</h2>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-colorme-updates-formats.gif" alt="An animated gif showing new format inputs in ColorMe" />
<figcaption>
<code>hsl(a)</code> and <code>rrggbbaa</code> formats now supported.
</figcaption></figure>
<p>
One of the first bits of feedback I got was from my coworker, <a href="https://twitter.com/thefivetoes">Justin</a>. He said ColorMe should support <code>rrggbbaa</code> colors for the base color. I’d never used them before so I took some time to educate myself. If they’re new to you too, read <a href="https://hashnode.com/post/understanding-rrggbbaa-color-notation-cisvdr52x088fwt53h1drf6m2">this post</a>. Then play with them in <a href="https://colorme.io/?color=ff0000af">ColorMe</a> to get a better feel how they work. I’ve been using the alpha adjuster to go through each value to see what the percentage is in hex. It’s still taking me some time to think about alpha values as hex, but I'm a fan. You should give it a try if you haven’t already.
</p>
<p>
I also added support for <code>hsl(a)</code> base colors. This was a no-brainer. It just didn't make the cut for first release.
</p>
<h2>Choosing output color format</h2>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-colorme-updates-format-select.gif" alt="An animated gif showing new format selection in ColorMe" />
<figcaption>
You can now choose the output color format.
</figcaption></figure>
<p>
OK, this is a fun one! Also the one that required the most effort. A request I got was the ability to see the output color in <code>hex</code> or <code>hsl(a)</code> format instead of only <code>rgb(a)</code>. This was also something I’d been wanting. So I took the idea and ran with it.
</p>
<p>
You now have the option to choose the format for the output color. There are up to seven different formats depending on the color. The full list of formats is; hex, hex shorthand, rrggbbaa, rrggbbaa shorthand, rgb(a), hsl(a), and keyword. Depending on the base color some of them are available and some aren’t.
</p>
<p>
When you choose a format, ColorMe will do its best to hold on to your selection. Sometimes though there's no way to display a color in a certain format. For example; say you enter the color <code>red</code>. The output format is "keyword". Now, say you adjust the <code>alpha</code> value to 90%. There is no hex or keyword format for the new color. In that case ColorMe switches to the <code>rgba</code> format. It also disables the hex, hex shorthand, rrggbbaa shorthand and keyword formats because those aren't available. In the UI, this just happen for you. React makes short work out of maintaining view layer updates. Behind the scenes there’s a lot of logic to determining what formats work for each color.
</p>
<p>
If you’re interested in that logic, have a look at the <code>getColorFormats</code> function in <code><a href="https://github.com/tylergaw/colorme/blob/2017-01-17T22.58.04/src/utils/color.js#L232">utils/color.js</a></code>. The code there isn’t clever. Most of it is <code>if</code> statements representing what I knew should be true about each format. Because this type of logic can be brittle when making changes, I also wrote a battery of <a href="https://github.com/tylergaw/colorme/blob/2017-01-17T22.58.04/src/utils/__tests__/color.test.js#L385">tests</a> to let me know when I break things.
</p>
<h2>A bug fix and open-source sleuthing</h2>
<p>
As I was tinkering with the UI, I hit one of those voodoo, ghost-looking bugs. After fiddling with the knobs a while longer, I saw the pattern and was able to reproduce the <a href="https://github.com/tylergaw/colorme/issues/3">issue</a>.
</p>
<p>
When using the <code>alpha</code> adjuster with the <code>tint</code>, <code>shade</code>, or <code>contrast</code> adjusters, the alpha value was wrong. For example, this code;
</p>
<pre><code class="language-css">color(red a(10%) tint(50%))</code></pre>
<p>
produced:
</p>
<pre><code class="language-css">rgba(255, 128, 128, 0.19999999999999996)</code></pre>
<p>
when I expected it to produce:
</p>
<pre><code class="language-css">rgba(255, 128, 128, 0.1)</code></pre>
<p>
First I made sure my code wasn't up to anything fishy. Then started looking at my dependencies. The unexpected results were coming from the the <a href="https://github.com/ianstormtaylor/css-color-function">css-color-function</a> package. To complicate things more, the problematic code didn't end in css-color-function. It was its use of another <a href="https://github.com/Qix-/color">color package</a>.
</p>
<p>
css-color-function uses the <code>mix</code> function of that color package for <code>tint</code>, <code>shade</code>, and <code>contrast</code> adjusters. The <code>Color.mix</code> function was ported from Sass. In Sass, the <code>mix</code> function not only mixes color channels, it also mixes the alpha channel.
</p>
<blockquote>
<p>
The opacity of the colors is also considered when weighting the components.
</p>
<cite>
<a href="http://sass-lang.com/documentation/Sass/Script/Functions.html#mix-instance_method">
From the Sass docs on the <code>mix</code> function
</a>
</cite>
</blockquote>
<p>
As far as I can tell, this isn't the intended behavior of the color function adjusters. For ColorMe, I forked css-color-function and modified its <code>blend</code> method. The problematic adjusters use that method. What I did was take the alpha value out of play before mixing colors. Then put it back when finished. Here's my updated version of the <code>blend</code> method:
</p>
<pre><code class="language-javascript">exports.blend = function (color, args) {
var targetAlpha = color.alpha();
<p>// Reset the alpha value to one. This is required because color.mix mixes
// the alpha value as well as rgb values. For blend() purposes, that's not
// what we want.
color.alpha(1);</p>
<p>var other = new Color(args[0].value);
var percentage = 1 - parseInt(args[1].value, 10) / 100;</p>
<p>// Finally set the alpha value of the mixed color to the target value.
color.mix(other, percentage).alpha(targetAlpha);
};</p></code></pre><p></p>
<p>
ColorMe is now using <a href="https://github.com/tylergaw/css-color-function/tree/tg-ignore-alpha-on-mix">my fork</a> of css-color-function. I opened a <a href="https://github.com/ianstormtaylor/css-color-function/pull/26">pull request</a> to get the changes the original fork. I also added more detail about the cause and solution in the description of that PR.
</p>
<h3>Pull requests beget pull requests</h3>
<p>
When I started pulling at this thread I found two other projects affected by it. I'm sure there are others. I opened a <a href="https://github.com/postcss/postcss-color-function/pull/33">pull request</a> in postcss-color-function and <a href="https://github.com/MoOx/postcss-cssnext/issues/337">reported the problem</a> in postcss-cssnext.
</p>
<h2>More to come</h2>
<p>
I had a lot fun with these updates, hope folks find them useful. As said I have more ideas for future ColorMe updates. And as always if you see issues or have ideas, <a href="https://github.com/tylergaw/colorme/issues">open an issue</a>.
</p>
Introducing: ColorMe2017-01-05T00:00:00Zhttps://tylergaw.com/blog/introducing-colorme/<p class="entry-intro">
<a href="https://colorme.io/">ColorMe</a> is a site I built to make working with the <a href="https://drafts.csswg.org/css-color/#modifying-colors">CSS Color Function</a> easier. It provides a UI for applying adjusters to a base color and gives a visual of the adjustments. The excellent <a href="http://jim-nielsen.com/sassme/" target="_blank">SassMe</a> was my inspiration.
</p>
<p>
The Color Function is a CSS feature in editor's draft stage of specification. This means it will be a while before it lands in any browser. That doesn't mean you can't use the proposed syntax today though. You can use the <a href="https://github.com/postcss/postcss-color-function">color function</a> PostCSS plugin. That plugin is also included in <a href="http://cssnext.io/features/#color-function">cssnext</a> if you want access to more future-facing CSS. The plugin uses the <a href="https://github.com/ianstormtaylor/css-color-function">css-color-function</a> package for its parsing and converting.
</p>
<p>
I <a href="https://tylergaw.com/articles/sass-to-postcss">wrote</a> about switching this site to PostCSS. That was my first encounter with the color function. After years of using color transform functions in Sass it's easy to see how powerful a native CSS color function will be.
</p>
<p>
There are more detailed <a href="https://topaxi.codes/modifying-css-colors-with-the-color-function/"> articles</a> about the color function. I'll give a brief overview of it here. On its own, <code>color</code> doesn't do anything with the given base color:
</p>
<pre><code class="language-css">color(#ea3333) /* output is the same as input */</code></pre>
<p>
The power of the function is in adjusters. These are transformations applied to the base color. The current draft spec lists 15 available adjusters; <code>alpha</code>, <code>rgb</code>, <code>red</code>, <code>green</code>, <code>blue</code>, <code>hue</code>, <code>saturation</code>, <code>lightness</code>, <code>whiteness</code>, <code>blackness</code>, <code>tint</code>, <code>shade</code>, <code>blend</code>, <code>blenda</code>, and <code>contrast</code>.
</p>
<p>
The <code>color</code> function allows you to apply one or more adjusters to the given base color:
</p>
<pre><code class="language-css">color(#ea3333 alpha(90%) saturation(75%) shade(20%)) /* rgba(182, 47, 47, 0.9) */</code></pre>
<p>
Many of the adjusters also have short names:
</p>
<pre><code class="language-css">color(#ea3333 a(90%) s(75%) shade(20%)) /* rgba(182, 47, 47, 0.9) */</code></pre>
<p>
Like Sass, using the CSS Color Function in code can be difficult visualize. Colors are easier to work with when you can see them. That's why SassMe is so darn useful. And that's why I thought we needed the same for CSS.
</p>
<figure>
<a href="https://colorme.io/">
<img src="https://tylergaw.com/articles/assets/post-image-colorme-screenshot-1.png" alt="A screenshot of colorme.io" />
</a>
<figcaption>
This is <a href="https://colorme.io/">ColorMe.io</a>
</figcaption></figure>
<h2>Building It</h2>
<p>
ColorMe is a React app. I used the wonderful <a href="https://github.com/facebookincubator/create-react-app">create-react-app</a> to get started.
</p>
<p>
Before getting any UI in place I needed to see a first thing work. I needed to convert color function strings to rgb(a) strings. For that I used the <code>css-color-function</code> package. I started by importing the package and logging results of its use:
</p>
<pre><code class="language-js">import colorFn from 'css-color-function';
console.log(colorFn.convert('color(red alpha(50%))')); // rgba(255, 0, 0, 0.5)</code></pre>
<p>
No major breakthrough, but that's how I get myself going. Just get one win no matter how small. From there it was a matter of building up functionality piece by piece.
</p>
<p>
The workhorse of the project is the <a href="https://github.com/tylergaw/colorme/blob/2017-01-05T17.19.10/src/utils/color.js">color utils</a>. All color calculations happen there. When you enter a base color, the app <a href="https://github.com/tylergaw/colorme/blob/2017-01-05T17.19.10/src/utils/color.js#L10">breaks it apart</a> into individual color properties. Those properties are initial values for the adjusters. I used <a href="https://github.com/Qix-/color">this color package</a> to parse the base color string.
</p>
<p>
Because the color utils do so much I took the time to write <a href="https://github.com/tylergaw/colorme/blob/develop/src/utils/__tests__/color.test.js">decent test coverage</a>. Writing tests isn't something I jump at doing (<i>read as: I get distracted, just wanna draw pictures, or go outside, or anything else. Please don't make me write tests!</i>). But it felt necessary for this and did save me a few headaches during refactoring.
</p>
<h2>Deploying It</h2>
<p>
When I'm ready to release updates to ColorMe I run <code>npm run release</code>. That command creates a new Git tag and pushes it to GitHub. Travis then runs the tests. When Travis sees the build is for a tag it executes <a href="https://github.com/tylergaw/colorme/blob/2017-01-03T23.50.13/scripts/deploy.sh">deploy.sh</a>. That script does a production-ready build with <code>npm run build</code> and then syncs the artifacts to AWS S3. This is a similar build process that I wrote about in more detail in <a href="https://tylergaw.com/articles/rewriting-day-player-for-sketch-40">“Rewriting Day Player for Sketch 40+”</a>.
</p>
<h2>Try it out</h2>
<p>
It's still early days for the CSS Color Function. It's a promising feature with momentum behind it. My hope is that more folks will start using it and giving feedback. We'll see the spec continue to evolve and sooner or later get implemented by browsers.
</p>
<p>
Take <a href="https://colorme.io/">ColorMe</a> for a spin. If you find bugs or have ideas, feel free to add <a href="https://github.com/tylergaw/colorme/issues">issues</a>.
</p>
From Sass to PostCSS2016-12-28T00:00:00Zhttps://tylergaw.com/blog/sass-to-postcss/<p class="entry-intro">
Sass has been my go-to for years. But for a while now, I've wanted to try a new styling setup with PostCSS and the cssnext plugin. I love the idea of writing future CSS syntax today and using tooling more aligned with other tools I'm used to. This personal site is a perfect test bed to try this new setup.
</p>
<p>
The first step I took was an inventory of my Sass usage. I needed to know what features I was using to make sure I found a replacement in the new setup. Here's a list of what I was using on this project:
</p>
<ul>
<li>partial imports</li>
<li>variables</li>
<li>nesting</li>
<li>mixins</li>
<li>extend</li>
<li>placeholder classes</li>
<li><code>darken</code> and <code>rgba</code> color functions</li>
<li>compression</li>
</ul>
<h2>Preparing</h2>
<p>
Before getting to new syntax or other fun I needed to do some yak shaving. The project was using a file structure typical for Sass usage. I used the leading underscore naming convention for partials. And included the required <code>scss</code> extension. I used two directories to loosely organize Sass files. <code>modules</code> housed Sass that didn't produce CSS. Things like variables, placeholder classes, and mixins. <code>partials</code> housed all CSS-producing Sass.
</p>
<p>
This was the beginning file structure:
</p>
<pre><code class="language-none">css/
scss/
modules/
_module.scss
...
partials/
_partial.scss
...
tylergaw.scss
</code></pre>
<p>
Each Sass partial gets imported in <code>tylergaw.scss</code>.
</p>
<pre><code class="language-scss">@import "modules/setup";
@import "modules/reset";
@import "modules/fonts";
</code></pre>
<p>
I reorganized and renamed the files. I first changed the extension from <code>scss</code> to <code>css</code>. Instead of doing it a file at a time, I used a Bash script:
</p>
<pre><code class="language-bash">for f in *.scss; do git mv -- "$f" "${f%.scss}.css"; done;
</code></pre>
<p>
Because the leading underscore is from the Sass world I also removed that. I couldn't figure a way to do it with Bash so I removed it from each file by hand. <i>(note to self; learn how to Bash better)</i>
</p>
<p>
The last step was to move all the CSS files to the <code>modules</code> directory and remove the <code>partials</code> directory. I decided referring to all CSS as modules made more sense than trying to split them along the modules/partials line.
</p>
<h2>Build setup</h2>
<p>
I started with the <a href="https://github.com/postcss/postcss-cli">PostCSS CLI</a>. I added a temporary build script to <code>package.json</code>:
</p>
<pre><code class="language-json">"scripts": {
"postcss": "postcss -o public/css/tylergaw.css src/css/tylergaw.css"
}</code></pre>
<p>
Without changing any styles I compiled the CSS:
</p>
<pre><code class="language-json">npm run postcss</code></pre>
<p>
It worked! Sorta. I didn't get errors in the console, but it left me with a naked page on the <a href="http://css-naked-day.github.io/">wrong day</a>.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-postcss-1.png" alt="A screenshot of tylergaw.com missing all styles" />
<figcaption>
Results of the first PostCSS build
</figcaption></figure>
<p>
With the build process functional I could now work to get the styles back in order.
</p>
<p>
Looking at the console in Chrome I saw a stack of 404s. This revealed the first missing feature; <code>@import</code> inlining. <code>tylergaw.css</code> contains only <code>@import</code>s for each CSS module. The browser saw those and did what it knows to do. It attempted to load each module via an HTTP request. My build process only copies the single CSS file, not each module. Because of that, the browser couldn't find them.
</p>
<p>
I could change the build process to make default <code>@import</code>s work, but that would be inefficient. I needed a replacement for Sass style <code>@import</code> inlining.
</p>
<h2>The first plugin</h2>
<p>
To get Sass-style <code>@import</code>s I used the <code>postcss-import</code> plugin. After installing the module via npm I updated the build script to use it:
</p>
<pre><code class="language-json">"scripts": {
"postcss": "postcss -u postcss-import -o public/css/tylergaw.css src/css/tylergaw.css"
}</code></pre>
<p>
And ran the script again with <code>npm run postcss</code>. The single CSS file contains all the modules and the site now has partial styling.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-postcss-2.png" alt="A screenshot of tylergaw.com with partial styles" />
<figcaption>
Results of the PostCSS build using postcss-import plugin
</figcaption></figure>
<h3>Will this be in a future CSS?</h3>
<p>
The inlining of <code>@import</code>s was huge when it showed up in Sass. It's changed how we're able to organize styles for the better. I'm not sure this functionality will ever be native in though. It seems like we'll always need a build step for this type of functionality. Which doesn't seem all that bad.
</p>
<p>
I imagine the <code>postcss-import</code> plugin will be a staple for all my future PostCSS setups. My guess is this will be true for other folks too. This quote from the plugin author sounds right on:
</p>
<blockquote>
<p>
This plugin should probably be used as the first plugin of your list. This way, other plugins will work on the AST as if there were only a single file to process, and will probably work as you can expect.
</p>
<cite>
<a href="https://github.com/postcss/postcss-import#postcss-import">
postcss-import
</a>
</cite>
</blockquote>
<h2>cssnext</h2>
<p>
<a href="https://cssnext.github.io/">cssnext</a> is a PostCSS plugin for compiling future CSS syntax to syntax that works today. It's important to note that it's not a different language like Sass or Less. The features it offers are in-progress CSS specs. Some for features already showing up in browsers today. Others in beginning stages of the specification process.
</p>
<p>
I used cssnext to fill in the rest of the gaps left by missing Sass features.
</p>
<h3>Vendor prefixes</h3>
<p>
I built this site before I knew about <a href="https://github.com/postcss/autoprefixer">Autoprefixer</a>. I used <a href="https://github.com/tylergaw/tylergaw.com/blob/pre-postcss/src/css/scss/modules/_prefixed.scss">custom Sass mixins</a> to handle adding the needed prefixes. cssnext includes Autoprefixer, so I was able to remove that entire mixins module.
</p>
<h3>Variables</h3>
<p>
Next I changed the Sass variables to CSS custom properties. In <a href="https://github.com/tylergaw/tylergaw.com/blob/pre-postcss/src/css/scss/modules/_setup.scss">_setup.scss</a> I had:
</p>
<pre><code class="language-scss">$grey: #1e1e1d;
$yellow: #ffad15;
$offwhite: #f8f8f8;
$darkerwhite: darken($offwhite, 15);</code></pre>
<p>
This isn't all the Sass vars I was using, but it's the main ones. The rest are in individual modules.
</p>
<p class="note-special">
<b>Note:</b> The “custom properties” vs. “variables” distinction. CSS custom properties are only valid for property values. They can't be used in selectors, property names, or media query values.
</p>
<p>
The updated <code>setup.css</code>:
</p>
<pre><code class="language-css">:root {
--white: #fff;
--grey: #1e1e1d;
--yellow: #ffad15;
--offwhite: #f8f8f8;
...
}</code></pre>
<p>
and an example of updated usage:
</p>
<pre><code class="language-css">a {
color: var(--yellow);
}</code></pre>
<p>
Apart from syntax, CSS custom properties work the same as Sass variables. Because of limited browser support, properties are still compiled out. In the above example, the compiled value is <code>color: #ffad15</code>.
</p>
<h3>Color functions</h3>
<p>
In the previous example, I left out one variable; <code>$darkerwhite: darken($offwhite, 15);</code>. This is another Sass feature I needed a replacement for. There's a <a href="https://drafts.csswg.org/css-color/#modifying-colors">draft spec</a> for a CSS <code>color</code> function. cssnext includes this function today and it's super cool. Here's <code>setup.css</code> with a <code>darkerwhite</code> custom property created with the <code>color</code> function and <code>shade</code> adjuster:
</p>
<pre><code class="language-css">:root {
...
--offwhite: #f8f8f8;
--darkerwhite: color(var(--offwhite) shade(20%));
...
}</code></pre>
<p>
The <code>color</code> function provides a bunch of <a href="https://github.com/postcss/postcss-color-function#list-of-color-adjuster">adjusters</a>. You can use multiple adjusters in a single usage:
</p>
<pre><code class="language-css">background-color: color(#d32c3f shade(40%) alpha(40%));</code></pre>
<p>
compiles to:
</p>
<pre><code class="language-css">background-color: rgba(127, 26, 38, 0.4);</code></pre>
<p>
To reiterate. Right now cssnext compiles the result of <code>color()</code> to hex or rgba values. When the <code>color</code> function arrives in browsers, the compilation won't be necessary. The color manipulation can happen at runtime.
</p>
<h3>Nesting</h3>
<p>
Nesting is an indispensable feature introduced by CSS preprocessors. A must for any comfortable styling setup. Tab Atkins has a <a href="http://tabatkins.github.io/specs/css-nesting/">spec</a> in-progress for CSS nesting and cssnext makes it available today.
</p>
<p>
This was mostly legwork. The CSS syntax for nesting includes a leading <code>&</code> before nested blocks. For example, the following is a Sass snippet from my Projects page:
</p>
<pre><code class="language-scss">.projects-list {
...
<p>li {
& > div {...}
}</p>
<p>a {
...</p>
<pre><code>&:hover,
&:focus {...}
&::after {...}
</code></pre>
<p>}</p>
<p>@media (min-width: 640px) {...}
}</p></code></pre><p></p>
<p>
For CSS nesting, I changed that to:
</p>
<pre><code class="language-css">.projects-list {
...
<p>& li {
& > div {...}
}</p>
<p>& a {
...</p>
<pre><code>&:hover,
&:focus {...}
&::after {...}
</code></pre>
<p>}</p>
<p>@media (min-width: 640px) {...}
}</p></code></pre><p></p>
<p>
Basic nesting requires the leading <code>&</code>. Pseudo classes and selectors are the same in Sass and CSS. Media queries don't need a leading <code>&</code>.
</p>
<p>
Also worth noting is <code>@nest</code>. As mentioned in the <a href="https://cssnext.github.io/features/#nesting">docs</a>, complex nesting requires <code>@nest</code> instead of <code>&</code>. I didn't have any use cases for it on this project, but may in the future.
</p>
<h3>Extend and Placeholder classes</h3>
<p>
I was using Sass <code>@extend</code> and placeholder classes for common styles. Here's an example usage responsible for styling Futura headings:
</p>
<pre><code class="language-scss">%futura {
font-family: 'futura-pt', helvetica, sans-serif;
}
<p>%futura-heading {
@extend %futura;
font-weight: 700;
line-height: 1.1;
text-transform: uppercase;
}</p></code></pre><p></p>
<p>
and an example usage:
</p>
<pre><code class="language-scss">.my-heading {
@extend %futura-heading;
}</code></pre>
<p>
We looked at CSS custom properties usage earlier. There's a related in-progress <a href="http://tabatkins.github.io/specs/css-apply-rule/">spec</a> for the <code>@apply</code> rule. <code>@apply</code> allows you to store a set of properties and reference them in selectors. I used <code>@apply</code> in place of Sass's <code>extend</code>.
</p>
<p>
Back in <code>setup.css</code> I added the updated Futura heading properties:
</p>
<pre><code class="language-css">:root {
...
--franklin: {
font-family: 'futura-pt', helvetica, sans-serif;
};
--franklin-heading: {
@apply --franklin;
font-weight: 700;
line-height: 1.1;
text-transform: uppercase;
};
}</code></pre>
<p>
and an example usage:
</p>
<pre><code class="language-scss">.my-heading {
@apply --franklin-heading;
}</code></pre>
<p>
<code>@apply</code> is not <code>extend</code>. In the current form in cssnext, <code>@apply</code> copies the properties and values to each rule. This is a small project so that's OK. On larger projects the extra properties may cause too much bloat. At that time it would probably be best to use a common class name to get similar results.
</p>
<p>
At this point I had the site looking as it did before the changes. The Projects page was an exception. On it I used a different color for each project tile. Next I'll describe how styling that correctly without Sass required more work and more typing.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-postcss-3.png" alt="A screenshot of tylergaw.com/projects" />
<figcaption>
The colorful tiles of the Projects page
</figcaption></figure>
<h2>Mixins with arguments</h2>
<p>
To make writing the projects styles easier I used a Sass mixin. The mixin took a single argument, the color for the tile. Here's the <code>project-block</code> mixin:
</p>
<pre><code class="language-scss">@mixin project-block ($c) {
background-color: $c;
a {
color: $c;
&:hover {
background-color: $c;
color: $offwhite);
}
}
}</code></pre>
<p>
and example usage:
</p>
<pre><code class="language-scss">.p-jribbble {
@include project-block(#ff0066);
}</code></pre>
<p>
At the time of this writing, I couldn't find a way to mimic this functionality in CSS. Custom property sets with <code>@apply</code> aren't functions, so you can't pass them arguments. In the future, it might be possible to use custom selectors for argument magic. The <a href="https://drafts.csswg.org/css-extensions/#declarative-custom-selector">draft spec</a> has a complex example that looks promising. Right now, I'll admit, I don't fully understand how it works.
</p>
<p>
That didn't mean I was out of luck. The CSS I write is longer than the Sass, but not by much. I also made use of another in-progress CSS feature; the <a href="https://drafts.csswg.org/selectors-4/#matches">matches</a> selector.
</p>
<p>
Here's an example of the CSS replacement for the <code>project-block</code> mixin:
</p>
<pre><code class="language-css">.p-jribbble,
.p-jribbble a:matches(:hover, :focus) {
background-color: var(--color-jrb);
<p>& a {
color: var(--color-jrb);
}
}</p></code></pre><p></p>
<p>
The color variables are in a <code>:root</code> scope earlier in the file. cssnext compiles the above CSS to:
</p>
<pre><code class="language-css">.p-jribbble,
.p-jribbble a:hover,
.p-jribbble a:focus {
background-color: #ff0066
}
<p>.p-jribbble a,
.p-jribbble a:hover a,
.p-jribbble a:focus a {
color: #ff0066;
}</p></code></pre><p></p>
<p>
The last two selectors <code>...a a:hover</code> and <code>...a a:focus</code> won't match any elements. They're unecessary, but aside from a few more bytes they don't hurt anything. I preferred nesting the <code>a</code> selector for code readability.
</p>
<h2>More PostCSS</h2>
<p>
With the styles back in proper order, I decided to take advantage of more PostCSS plugins. I used <a href="https://github.com/hail2u/node-css-mqpacker">css mqpacker</a> to combine media queries that share the same query. I also used <a href="http://cssnano.co/">cssnano</a> for code optimization.
</p>
<p>
This is where I'm looking forward to using a PostCSS setup. With Sass I felt locked in to the features in the current version. Because PostCSS works as a collection of plugins, it's extendible. If I have a specific need, I can write a plugin for it. The potential there is exciting.
</p>
<h2>I'm sold</h2>
<p>
After working with this setup for a few days, I'm all in. Making the switch from Sass syntax to new CSS syntax has been easy. And that's after five or six years of using Sass on every project I worked on.
</p>
<p>
I enjoy the shift in thinking this brings. cssnext has a similar approach to CSS as <a href="https://babeljs.io/">Babel</a> has to JavaScript. They both allow you to write the language as it is and as it will be in the future.
</p>
Rewriting Day Player for Sketch 40+2016-12-21T00:00:00Zhttps://tylergaw.com/blog/rewriting-day-player-for-sketch-40/<p class="entry-intro">
At 2 ½ my placeholder image plugin was showing signs of age and side-project neglect. I decided to make it a priority to get <a href="https://github.com/tylergaw/day-player" title="View Day Player on GitHub">Day Player</a> back in working order. In doing so, I would also need to educate myself on the latest in Sketch Plugin development. I didn't know it when I started, but I would also put together a fun build and release setup for Sketch plugins.
</p>
<p>
Day Player has a limited scope. It requests images from placeholder image services and places them in Sketch documents. This <code>3.0.0</code> rewrite and release hasn't changed the scope. I did end up cutting two features and adding a new one.
</p>
<h2>What got removed?</h2>
<p>
The first feature cut was the placeholder service <a href="http://lorempixel.com/">Lorempixel.com</a>. I removed it because it doesn't support <code>https</code>. Day Player doesn't transport sensitive data. Still, I decided not to use services without <code>https</code> support.
</p>
<p>
The second feature cut was <a href="https://github.com/tylergaw/day-player/pull/9">remembering user values for each service</a>. I cut this for time. I didn't want to hold up the release for a non-essential feature. There's a good chance I'll circle back and add this in the future. Or, if you're feeling up to it, <a href="https://github.com/tylergaw/day-player#contributing-to-this-project">open a pull request</a>.
</p>
<h2>What’s new?</h2>
<p>
The one new feature is the ability to add an image with the same dimensions and position <a href="https://github.com/tylergaw/day-player#creating-images-with-dimensions-and-position-of-existing-layers">as an existing layer</a>.
</p>
<figure>
<a href="https://github.com/tylergaw/day-player">
<img src="https://d3vv6lp55qjaqc.cloudfront.net/items/0o1M3n07223o223D2C3R/Screen%20Recording%202016-11-13%20at%2003.23%20PM.gif" alt="Animated gif showing new Day Player functionality" />
</a>
<figcaption>
Placing an image with the same dimensions and position as an existing layer
</figcaption></figure>
<p>
I added this because of a <a href="https://github.com/tylergaw/day-player/issues/27">user request</a>. While it's not exactly what the user was asking for, it feels close enough to get the job done. And, as a user myself, it's a welcome addition.
</p>
<h2>What’s next?</h2>
<p>
As expected, within a day or two of release, I got the first “this doesn't work” <a href="https://github.com/tylergaw/day-player/issues/37">issue</a>. After a quick back and forth, it seems we've isolated the problem to macOS Sierra. I'm a slow new adopter, so I still haven't reproduced the issue. Sooner than later I'll update a machine and find and fix what's broken. If you have any tips for what may have changed, let me know here or on that issue.
</p>
<h2>How do you build and release this?</h2>
<p>
So glad you asked. This is a topic I want to write in more detail about in the future because it's fun, but I'll give a few highlights now.
</p>
<h3>Building</h3>
<p>
I'm not using Babel or any other code transmogrifying. I write in the JavaScript available in JavaScriptCore. In my day-to-day JS I use Babel and enjoy using ES2015+ features. With the current JSCore we have access to a limited set of new features. We can use <code>const</code> (but not <code>let</code>). Template literals are available to make short work of string munging. And that's about it. No arrow functions or other niceties yet. They'll get there. As I do more plugin development, I'll likely update my build tooling to use Babel.
</p>
<p>
Sketch plugins give you access to the Cocoa Framework. This allows you to write JS to create and interact with Cocoa elements. If you've ever written code to do this, you know that it doesn't have the smoothest API. A fun aspect of Day Player was writing components to act as <a href="https://github.com/tylergaw/day-player/tree/3.0.0/src/components">facades</a> for each Cocoa element needed. These components allow me to write more idiomatic code. They also allow for writing less code and provide a chainable interface.
</p>
<p>
For example, here's the code required to create a text label using regular Cocoa:
</p>
<pre><code class="language-javascript">const textField = NSTextField.alloc().initWithFrame(NSMakeRect(0, 0, 350, 16));
textField.setDrawsBackground(false);
textField.setEditable(false);
textField.setBezeled(false);
textField.setSelectable(true);
textField.setStringValue('My Label:');
</code></pre>
<p>
and the code required using the <code>Label</code> <a href="https://github.com/tylergaw/day-player/blob/3.0.0/src/components/Label.js">component</a>:
</p>
<pre><code class="language-javascript">new Label({
value: 'My Label:'
});
</code></pre>
<p>
Each image service has similar options. Because of this, I was able to abstract common setup code to a few functions. Those functions live in <a href="https://github.com/tylergaw/day-player/blob/3.0.0/src/utils.js">utils.js</a>. Utils.js also includes comments describing my learning process with the excellent and in-progress <a href="http://developer.sketchapp.com/reference/api/">Sketch JS API</a>.
</p>
<h3>Releasing</h3>
<p>
<a href="https://github.com/tylergaw/day-player#installation">Installing</a> Day Player takes 3 steps; download, unzip, double-click. It was important to make that process as easy as possible. I want the user to use the plugin, not spend time figuring out how to get it.
</p>
<p>
As as plugin author, it's also important that the release process is as painless as possible. The release process works by chaining 5 primary tools together. Make, Node.js, Git, Travis CI, and AWS S3.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-dayplayer-process.png" alt="An illustration showing the steps of the Day Player release process" />
<figcaption>
A diagram showing the steps of the release process
</figcaption></figure>
<p>
Here's how it works. When I'm ready to ship a new release I update the <code>version</code> in <code>package.json</code>. I then run <code>make release</code>. That in turn uses Node.js to execute the <a href="https://github.com/tylergaw/day-player/blob/3.0.0/scripts/release.js">release.js</a> script. I could run <code>node scripts/release.js</code> directly, but I prefer to put it in a Make target so all build commands are in one place.
</p>
<p>
<code>Release.js</code> first confirms I want to release. I put this in as check to keep myself from releasing an incorrect version. The script then creates a new Git tag and pushes it to the origin at GitHub. At this point, the process is on autopilot.
</p>
<figure>
<a href="https://github.com/tylergaw/day-player/blob/3.0.0/scripts/release.js">
<img src="https://cl.ly/2p0j2O0Q1a3p/Screen%20Shot%202016-12-18%20at%208.18.39%20PM.png" alt="A screenshot of a command prompt confirming the release of Day Player" />
</a>
<figcaption>
The friendly prompt I see when releasing Day Player
</figcaption></figure>
<p>
Travis CI now takes over. In <a href="https://github.com/tylergaw/day-player/blob/3.0.0/.travis.yml">travis.yml</a> we have the following:
</p>
<pre><code class="language-javascript">deploy:
- provider: script
script: make publish
on:
tags: true
</code></pre>
<p>
This tells Travis to watch for builds that create new Git tags. When it sees a tag it should run <code>make publish</code> after all other tasks are complete.
</p>
<p>
The <a href="https://github.com/tylergaw/day-player/blob/3.0.0/Makefile#L53">publish</a> Make target handles packaging the plugin and uploading it to S3. It first creates a zip file of the Sketch plugin <a href="http://developer.sketchapp.com/introduction/plugin-bundles/">bundle</a>. Then using the AWS CLI, copies the zip file to the Day Player bucket; <code>s3://day-player</code>.
</p>
<p>
Interacting with AWS CLI in this way requires extra setup. First, we install the tool during the Travis <a href="https://github.com/tylergaw/day-player/blob/3.0.0/.travis.yml#L13">install step</a>.
</p>
<pre><code class="language-javascript">install:
- pip install --user -U awscli
</code></pre>
<p>
The next steps ensure Travis can make safe requests to AWS on my behalf. I set two environment variables in the <a href="https://github.com/tylergaw/day-player/blob/3.0.0/.travis.yml#L24">travis conf</a>. <code>AWS_ACCESS_KEY_ID</code> and an encrypted value for the <code>secure</code> key. The encrypted key:value is for the <code>AWS_SECRET_ACCESS_KEY</code>. The access key is not sensitive, so it's OK to be out in the open. The secret is for my eyes only. I used the Travis CLI tool to encrypt that value. You can read about that process in the <a href="https://docs.travis-ci.com/user/environment-variables/#Encrypting-environment-variables">Travis docs</a>.
</p>
<p>
By default, the AWS CLI tool looks for the environment variables set above. Requests use them for authentication if found.
</p>
<p>
You may be wondering where the AWS access key and secret come from. This is an area where I use a best practice for handling AWS credentials. I've set up an AWS IAM Role that only has permission to upload objects to the Day Player S3 bucket. Setting up a restricted IAM Role is outside the scope of this post. A Google search for “IAM role for single bucket” returns up-to-date articles on the topic. That's how I figured it out for this process.
</p>
<h2>All this for placeholder images?</h2>
<p>
Yeah! This is a side project and my side projects are for learning, tinkering, and fun. If I also build things that are useful to other folks at the same time, that makes it even better.
</p>
<p>
Download <a href="https://github.com/tylergaw/day-player/releases/tag/3.0.0">Day Player 3.0</a>.
</p>
Overawe2015-06-25T00:00:00Zhttps://tylergaw.com/blog/overawe/<p class="entry-intro">
Using Web technolgies as a medium for motion graphics was maybe not on the
mind of Tim Berners-Lee when he came up with the WWW. For the past few months
I’ve been working on a <a href="http://lab.tylergaw.com/overawe">project</a>
that does just that.
</p>
<p>
Web folks–especially those of us working on the front-end of things–tend to be
a bit consumed by tools and process. I sometimes get fatigued by the
near constant chatter of new frameworks, build tools, package managers,
plugins and on and on. I enjoy those tools and their benefits, but when
I get worn down by them I need something to help pull me up and remind me why
I love working on the Web. Our tools and processes are just means. I needed
to work on something that would allow me to focus 100% on the end. Where the code creating
it was unimportant. Where I could do things the long, hard, stupid way. And where I could bend and
break best practices. That’s what I set out to do with what became
<a href="http://lab.tylergaw.com/overawe">Overawe</a>.
</p>
<h2>What is this?</h2>
<p>
I’m no stranger to using HTML/CSS to build projects that <a href="http://lab.tylergaw.com/themanfromhollywood/">exist</a>
<a href="http://lab.tylergaw.com/css-shaky-animation/">a bit</a>
<a href="http://lab.tylergaw.com/css-true-titles/">outside the norm</a>. Until now
I’ve considered these projects “experiments”. They seemed like a way to try
out new–at the time–CSS features. I think I’m beyond that point. Things like
CSS animations are here to stay. With Overawe–and other projects like
it I’ll do–I’m not experimenting with CSS anymore. I’m choosing the web as my
preferred medium for creating art.
HTML, CSS, and JavaScript are my paintbrushes and web browsers are my canvases.
And how cool is that? There's not a single, fixed size, always-the-same canvas.
Each browser and each operating system and each computer that runs Overawe will
run it a bit different. This is due to feature implementation, processor
strength, etc. It’s also different based on each viewers' preferences. What size
is their screen? Are they running their browser in full screen or presentation mode?
Do they have a slow network connection?
Do they have a bunch of other programs running that will cause Overawe to stutter?
</p>
<p>
For each one of the questions and many others, there is an effect on the project
and how each viewer experiences it. I think that's incredible. Some people will
hate it because it doesn't appear to do anything. For some it will run
the same as it does for me on my local development server. I have no control
over it and that makes it fun.
</p>
<h2>Where did this come from?</h2>
<p>
The idea for the format came from those cool MTV identities. You know, the short clips
they play between segments. If you're not familiar,
<a href="https://www.youtube.com/watch?v=3u_UQoMViEM">here’s an hour of them </a>.
I wanted to make something similar by pairing graphics with short audio
clips. Each section is independent of the others. With some of them I have an
idea about what it means and what I’m saying with it. Others, I made just because they
give me chuckle.
</p>
<p>
Coming up with each section would be sporadic. Sometimes I would sit down and
just start writing words and phrases that popped into my head. Other times I would
be walking or taking shower or just doing other things and an idea for a section
would come into my head. The process was loose. Once I had a phrase or an idea
I'd hop into Photoshop and just play. I tried not to spend too much time on any
one section. I would just throw together text and images and apply whichever
styles and filters felt right at the time. I didn't worry about “mistakes”. Because
this project was about creating a little world of it's own, there are no mistakes.
Whatever ended up on the screen was supposed to be there in that form.
</p>
<h2>How did you do this?</h2>
<p>
While I don’t want this article or poject to be defined by the means of creation, I do want
them to be available to anyone curious. As with most of my projects, you can
access the <a href="https://github.com/tylergaw/overawe">source code on Github</a>.
All the animations are done using CSS. I did make use of the Web Audio API to
fiddle with the audio clips. Each one is different, but I do things like
speed up or slow down playback, add distortion, and apply high and low band filters.
This is another area that gets interesting in different browsers. The Web Audio API
is implemented different across browsers so the audio sounds different depending
on which one you're using.
</p>
<p>
This was a lot of fun. Any time I get to sit down and create my own little
world is good time. If no other person saw this but me, it’s still served it’s
purpose. It allowed me to just make a thifng without the normal internal
debates about if I was doing every little thing the "right" way. I encourage everyone to
do the same regardless of the medium.
</p>
<p>
<a href="http://lab.tylergaw.com/overawe">http://lab.tylergaw.com/overawe</a>
<br />
<a href="https://github.com/tylergaw/overawe">https://github.com/tylergaw/overawe</a>
</p>
<p>
<i>Thanks for reading</i>
</p>
Building OS X Apps with JavaScript2014-09-27T00:00:00Zhttps://tylergaw.com/blog/building-osx-apps-with-js/<p class="entry-intro">
OS X Yosemite introduced JavaScript for Automation. This makes it possible to
access native OS X frameworks with JavaScript. I’ve been
digging in to this new world and putting together <a href="https://github.com/tylergaw/js-osx-app-examples">examples</a>
along the way. In this post I’ll explain the basics and step through building
a small <a href="https://github.com/tylergaw/js-osx-app-examples/tree/master/ChooseAndDisplayImage.app">example app</a>.
</p>
<p>
WWDC 2014 included a session on
<a href="https://developer.apple.com/librarY/prerelease/mac/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/index.html">JavaScript for Automation</a>.
The session explained that you would be able to use JavaScript in place of
AppleScript to automate applications. That alone is exciting news. Being able to automate
repetitive tasks using AppleScript has been around a long time. AppleScript is
not the most fun thing to write so using a familiar syntax in its place is welcome.
</p>
<p>
During the session the presenter explains the Objective-C bridge. This is where
things get super cool. The bridge allows you to import any Objective-C framework
into JS applications. For example, if you want to build a GUI using standard OS X
controls you would import Cocoa:
</p>
<pre><code class="language-javascript">ObjC.import("Cocoa");
</code></pre>
<p>
The <a href="https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/ObjC_classic/_index.html">Foundation</a>
framework does what its name suggests. It provides the building blocks for
OS X apps. The framework includes a large amount of classes and protocols.
<code>NSArray</code>, <code>NSURL</code>, <code>NSUserNotification</code>, and so on. You may not be familiar with
those classes, but their names hint at what they do. Because of its importance,
you can use its classes without needing to import it in new apps. They are
available by default.
</p>
<p>
As far as I can tell, you can use JavaScript to build anything that you can
with Objective-C or Swift.
</p>
<h2>Building an example app</h2>
<p class="note-special">
<b>Note:</b> You need Yosemite Developer
Preview 7+ for the examples to work.
</p>
<p>
The best way to get going is to jump in and build something. I’m
going to walk through building a small app that lets you display an image
from your computer.
</p>
<p>
You can download the completed example from my <a href="https://github.com/tylergaw/js-osx-app-examples">example apps repo</a>.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-jsosx-example-01.jpg" alt="" />
<figcaption>
A screenshot of the app we’ll build.
</figcaption></figure>
<p>
Making up the app is; a window, text label, text input, and button. Or by their
class names; <code>NSWindow</code>, <code>NSTextField</code>, <code>NSTextField</code>,
and <code>NSButton</code>.
</p>
<p>
Clicking the Choose an Image button will show an <code>NSOpenPanel</code> that allows you
to select a file. We’ll configure the panel to restrict the file selection to .jpg,
.png, or .gif.
</p>
<p>
After selecting an image, we'll display it in the window. The window will
resize to match the width and height of the image plus the height of the controls.
We’ll also set a minimum width and height for the window to make sure the controls
don’t get cut off.
</p>
<h3>Setting up the project</h3>
<p>
Open the Apple Script Editor application located in <code>Applications > Utilities</code>.
Script Editor ain’t the best editor I’ve ever used, but it’s necessary for now.
It has a bunch of features that we need for building JS OS X apps. I’m not sure
what goes on beyond the scenes, but it can compile and run your scripts as apps.
It also creates extra stuff that we need like an Info.plist file. My guess is there
are ways to make other editors do the same, but I haven’t looked into it yet.
</p>
<p>
Create a new document with <code>File > New</code> or <code>cmd + n</code>. The
first thing we need to do is save the document as an application.
Save with <code>File > Save</code> or <code>cmd + s</code>.
Don’t confirm the save right away. There are two options that are necessary to make
this run as an application.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-jsosx-save.jpg" alt="" />
<figcaption>
The Script Editor Save dialog with important fields highlighted
</figcaption></figure>
<p>
Change the File Format to “Application”. Check the “Stay open after run handler”
option. <s>It’s important to set both of these options the first time you save the
file because–as far as I can tell–there is no way to set them later. If you
miss one of these you’ll have to create a new document and copy your code over.</s>
</p>
<p>
<s>If anyone knows a way to change the settings after creation, let me know.</s>
</p>
<p class="note-update">
<b>Update:</b> A nice <a href="https://tylergaw.com/articles/building-osx-apps-with-js#comment-1609250306">person</a>
explained how to update the settings. Open the File menu and then hold down
the option key. That will give you the “Save as...” option. The save dialog
allows you to make changes to the settings. It’s still best to set these on first save to avoid the hassle.
</p>
<p>
If you don’t select “Stay open after run handler” your app will open, flash for
a split second, and then close. There is not much in the way of documentation for
this stuff online. I only learned this detail after hours of forehead/keyboard merging.
</p>
<h3>Let’s make something do something!</h3>
<p>
Add the following two lines to your script and then run it with <code>Script > Run Application</code>
or <code>opt + cmd + r</code>.
</p>
<pre><code class="language-javascript">ObjC.import("Cocoa");
$.NSLog("Hi everybody!");
</code></pre>
<p>
Not much happened. The only visible changes are in the menu bar and dock. The app
name along with <code>File</code> and <code>Edit</code> menus are in the menu bar.
You can see the app is running because its icon is in your dock.
</p>
<p>
Where is the “Hi everybody!”? What is with the dollar sign, what is this, jQuery?
Quit the app with <code>File > Quit</code> or <code>cmd + q</code> and
we’ll find out where that <code>NSLog</code> went.
</p>
<p>
Open the Console app: <code>Applications > Utilities > Console</code>. Every
application can log messages to the console. This console is not much different
than Developer Tools in Chrome, Safari, or Firefox. The main difference is you
use it for debugging applications instead of websites.
</p>
<p>
There’s a lot of messages in the console. You can filter it down by
typing “applet” into the search field in the top right corner. Leave “applet” in
the search field and go back to Script Editor. Run the application again
with <code>opt + cmd + r</code>.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-jsosx-console.jpg" alt="A screenshot showing a log message in Console.app" />
</figure>
<p>
Did you see it!? The message “Hi everybody!” should have logged to the console.
If you didn’t see it, quit your application and run it again. A lot of times
I forget to quit and the code doesn’t run again.
</p>
<h3>What about the dollar sign?</h3>
<p>
The dollar sign is your access to the Objective-C bridge. Any time you need
to access an Objective-c class or constant, you use <code>$.foo</code> or
<code>ObjC.foo</code>. There are a couple of other ways
to use <code>$</code> that I’ll cover later.
</p>
<p>
Console App and <code>NSLog</code> are indispensable tools, you’ll use them
non-stop for debugging.
For examples of logging things other than strings, have a look at my
<a href="https://github.com/tylergaw/js-osx-app-examples/tree/master/NSLog.app">NSLog example</a>.
</p>
<h3>Creating the window</h3>
<p>
Let’s make something we can see and interact with. Update your script to look
like the following code:
</p>
<pre><code class="language-javascript">ObjC.import("Cocoa");
<p>var styleMask = $.NSTitledWindowMask | $.NSClosableWindowMask | $.NSMiniaturizableWindowMask;
var windowHeight = 85;
var windowWidth = 600;
var ctrlsHeight = 80;
var minWidth = 400;
var minHeight = 340;
var window = $.NSWindow.alloc.initWithContentRectStyleMaskBackingDefer(
$.NSMakeRect(0, 0, windowWidth, windowHeight),
styleMask,
$.NSBackingStoreBuffered,
false
);</p>
<p>window.center;
window.title = "Choose and Display Image";
window.makeKeyAndOrderFront(window);
</p></code></pre><p></p>
<p>
When that’s in place, run the app. <code>opt + cmd + r</code>. Now we’re
talking! With a small amount of code we’ve built an app that launches a window
with a title that we can move, minimize, and close.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-jsosx-basic-window.jpg" alt="" />
<figcaption>A basic NSWindow created with JS</figcaption>
</figure>
<p>
If you’ve never built an app with Objective-C or Cocoa–like me–parts of this might
look like a lot of gibberish. For me, that was due to the length of the
method names. I enjoy descriptive names, but Cocoa takes it to the extreme.
</p>
<p>
Looking past that though, this is JavaScript. This code looks just like code
you’d write when building a website.
</p>
<p>
What’s going on with that first new line where we set <code>styleMask</code>?
You use style masks to configure windows. Each style option says what it adds;
a title, a close button, a minimize button. These options are constants. Use a pipe “|”
to separate options. That pipe is a C bitwise <code>OR</code> operator.
I’m not gonna pretend to know what that means. I just know it’s
needed to combine style options for a mask and that’s good enough for me.
</p>
<p>
There are more style options. You can read about them in <a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowAppearance.html">the docs</a>.
<code>NSResizableWindowMask</code> is one you’ll use. Try adding it to the
style mask to see what it does.
</p>
<p>
There are a couple interesting things about syntax that you’ll need to remember.
<code>$.NSWindow.alloc</code> calls the <code>alloc</code> method of <code>NSWindow</code>.
Notice there are no parenthesis “()” after alloc. In JavaScript that’s how we
access properties, not how we call a methods. What’s with that? In JS for OS X parenthesis are only
allowed when calling a method if you pass arguments to it. If you use parenthesis
with no arguments, you will get a runtime error. Check Console for errors when
things don’t do what you think they should.
</p>
<p>
The next thing is about that super long method name:
</p>
<pre><code class="language-javascript">initWithContentRectStyleMaskBackingDefer</code></pre>
<p>
Take a look at the <code>NSWindow</code> <a href="https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSWindow_Class/Reference/Reference.html#jumpTo_105">docs</a>
for that method and you’ll notice it looks a bit different.
</p>
<pre><code class="language-javascript">initWithContentRect:styleMask:backing:defer:</code></pre>
<p>
In Objective-C you would create the same window we did like this:
</p>
<pre><code class="language-clike">NSWindow* window [[NSWindow alloc]
initWithContentRect: NSMakeRect(0, 0, windowWidth, windowHeight)
styleMask: styleMask,
backing: NSBackingStoreBuffered
defer: NO];
</code></pre>
<p>
The thing to note are the colons “:” in the original method signature. When you convert
and Objective-C method to JS you remove the colons and capitalize the first letter
following it. When you see two items in brackets “[]”, that is calling a method
of a class or object. <code>[NSWindow alloc]</code> calls the <code>alloc</code> method
of <code>NSWindow</code>. For JS, convert those to dot-notation and parenthesis
if necessary; <code>NSWindow.alloc</code>.
</p>
<p>
I think the rest of the code to create and display the window is straightforward-enough.
I’ll skip any more detailed description of it.
It takes time and a lot of reading the docs to understand what’s happening
for each step along the way, but you’ll get there. As long as the window is
showing up you’re doing great. Let’s do more.
</p>
<h3>Adding the controls</h3>
<p>
We need a label, text field, and button in this window. We’ll use <code>NSTextField</code>
and <code>NSButton</code> to make that happen. Update your script with the
following code and then run the app.
</p>
<pre><code class="language-javascript">ObjC.import("Cocoa");
<p>var styleMask = $.NSTitledWindowMask | $.NSClosableWindowMask | $.NSMiniaturizableWindowMask;
var windowHeight = 85;
var windowWidth = 600;
var ctrlsHeight = 80;
var minWidth = 400;
var minHeight = 340;
var window = $.NSWindow.alloc.initWithContentRectStyleMaskBackingDefer(
$.NSMakeRect(0, 0, windowWidth, windowHeight),
styleMask,
$.NSBackingStoreBuffered,
false
);</p>
<p>var textFieldLabel = $.NSTextField.alloc.initWithFrame($.NSMakeRect(25, (windowHeight - 40), 200, 24));
textFieldLabel.stringValue = "Image: (jpg, png, or gif)";
textFieldLabel.drawsBackground = false;
textFieldLabel.editable = false;
textFieldLabel.bezeled = false;
textFieldLabel.selectable = true;</p>
<p>var textField = $.NSTextField.alloc.initWithFrame($.NSMakeRect(25, (windowHeight - 60), 205, 24));
textField.editable = false;</p>
<p>var btn = $.NSButton.alloc.initWithFrame($.NSMakeRect(230, (windowHeight - 62), 150, 25));
btn.title = "Choose an Image...";
btn.bezelStyle = $.NSRoundedBezelStyle;
btn.buttonType = $.NSMomentaryLightButton;</p>
<p>window.contentView.addSubview(textFieldLabel);
window.contentView.addSubview(textField);
window.contentView.addSubview(btn);</p>
<p>window.center;
window.title = "Choose and Display Image";
window.makeKeyAndOrderFront(window);
</p></code></pre><p></p>
<p>
If things worked you’ll now have a window with controls.
You can’t type in the text field and the button doesn’t do anything, but hey,
we’re getting somewhere.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-jsosx-controls.jpg" alt="" />
<figcaption>Label, field, and button elements in the window</figcaption>
</figure>
<p>
What did we do in that addition? <code>textFieldLabel</code> and <code>textField</code>
are similar. They’re both instances of <code>NSTextField</code>. We make them
in a similar way as we made the window. When you see <code>initWithFrame</code>
and <code>NSMakeRect</code>, it’s a good sign that the script is creating
a UI element. <code>NSMakeRect</code> does what it says. It makes a rectangle
with the given position and dimensions; <code>(x, y, width, height)</code>. That
creates what’s called a struct in Objective-C. In JavaScript we’d refer to it as an
object or hash or maybe dict. It’s a thing with keys and values.
</p>
<p>
After creating the text fields we set a handful of properties on each to get the
results we’re looking for. Cocoa doesn’t have anything like the html <code>label</code>
element. For that we make our own by disabling editing and background styling.
</p>
<p>
We’ll populate the text field programmatically, so we disable editing on it as well.
If we didn’t need that, creating a standard text field is a one-liner.
</p>
<p>
For the button we use <code>NSButton</code>. Like the text fields, creating
it requires drawing a rectangle. There are two properties that stand out;
<code>bezelStyle</code> and <code>buttonType</code>. The values for both are
constants. These properties control how the button will render and the style
it will have. Check out the <code>NSButton</code>
<a href="https://developer.apple.com/library/mac/documentation/cocoa/reference/applicationkit/classes/NSButton_Class/Reference/Reference.html">docs</a>
to see everything you can do with it. I also have
an <a href="https://github.com/tylergaw/js-osx-app-examples/tree/master/NSButton.app">example app</a>
that shows the different button types and styles in action.
</p>
<p>
The last new thing we do is add the elements to the window with <code>addSubview</code>.
When I first tried this I did <code>window.addSubview(theView)</code>. That works
for other standard views that you create with <code>NSView</code>, but not for
instances of <code>NSWindow</code>. I’m not sure why that is, but for windows you
need to add sub views to the <code>contentView</code>. The docs describe it as;
“The highest accessible NSView object in the window’s view hierarchy”. Works for
me.
</p>
<h3>Making the button do something</h3>
<p>
When clicking the Choose an Image button, we want a panel to open that shows
files on our computer. Before we do that, let’s warm up by logging a message
to the console when clicking the button.
</p>
<p>
In JavaScript you bind event listeners to elements to handle clicks.
Objective-C doesn’t use quite that same concept. It has what’s called message passing.
With it, you send a message containing a method name to an object. That object needs
to have information about what to do when it receives a message containing
the method name. That may not be a spot-on description, but it’s
how I understand it.
</p>
<p>
The first thing we need to do is set the button <code>target</code> and <code>action</code>.
The <code>target</code> is the object we want to send the <code>action</code>
to. If this doesn’t make sense now, just keep going and it’ll get more clear
when you see the code. Update the button setup part of the script with the
following properties.
</p>
<pre><code class="language-javascript">...
btn.target = appDelegate;
btn.action = "btnClickHandler";
...
</code></pre>
<p>
<code>appDelegate</code> and <code>btnClickHandler</code> don’t exist yet. We need to
make them. In the next addition to the script, order matters. I put
comments in the following code to show where you should add the new stuff.
</p>
<pre><code class="language-javascript">ObjC.import("Cocoa");
<p>// New stuff
ObjC.registerSubclass({
name: "AppDelegate",
methods: {
"btnClickHandler": {
types: ["void", ["id"]],
implementation: function (sender) {
$.NSLog("Clicked!");
}
}
}
});</p>
<p>var appDelegate = $.AppDelegate.alloc.init;
// end of new stuff</p>
<p>// Below here is in place already
var textFieldLabel = $.NSTextField.alloc.initWithFrame($.NSMakeRect(25, (windowHeight - 40), 200, 24));
textFieldLabel.stringValue = "Image: (jpg, png, or gif)";
...
</p></code></pre><p></p>
<p>
Run the app, click the Choose Image button, and watch Console. Do you see “Clicked!”
when you click the button? If so, that’s pretty darn cool right? If not, double
check your script matches the code here and look for any errors in Console.
</p>
<h3>Subclassing</h3>
<p>
What’s this <code>ObjC.registerSubclass</code> all about? Subclassing is a way
to create a new class that inherits from another Objective-C class. Side note; the likelihood
I’m using incorrect terminology here is through the roof. Bear with me.
<code>registerSubclass</code> takes a single argument;
a JS object with the members of the new object. Members can be; <code>name</code>,
<code>superclass</code>, <code>protocols</code>, <code>properties</code>, and
<code>methods</code>. I’m not 100% sure that’s an exhaustive list, but that’s what’s
in the <a href="https://developer.apple.com/librarY/prerelease/mac/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/index.html#//apple_ref/doc/uid/TP40014508-CH109-SW30">release notes</a>.
</p>
<p>
That’s all well and good, but what did we do there? Since we didn’t provide a
<code>superclass</code> we inherited from <code>NSObject</code>. That’s the root
class of most Objective-C classes. Setting a <code>name</code> let’s us reference
the object later through the bridge with <code>$</code> or <code>ObjC</code>.
</p>
<p>
<code>$.AppDelegate.alloc.init;</code> creates an
instance of our <code>AppDelegate</code> class. Again, notice we don’t
use parenthesis with the <code>alloc</code> or <code>init</code> methods since
we’re not passing arguments.
</p>
<h3>Subclass methods</h3>
<p>
You create a method by giving it any string name. In this case “btnClickHandler”.
Give it an object with <code>types</code> and <code>implementation</code> members.
I haven’t found official docs for what the <code>types</code> array should
contain. Through trial and error I found it goes like this:
</p>
<pre><code class="language-javascript">["return type", ["arg 1 type", "arg 2 type",...]]</code></pre>
<p>
<code>btnClickHandler</code> won’t return anything so we set the return type to <code>void</code>.
It takes one parameter, the sender object. In this case it will be the <code>NSButton</code>
we named <code>btn</code>. We use the “id” type because represents any object.
</p>
<p>
The full list of types is in the
<a href="https://developer.apple.com/librarY/prerelease/mac/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/index.html#//apple_ref/doc/uid/TP40014508-CH109-SW32">release notes</a>.
</p>
<p>
<code>implementation</code> is a normal function. Inside of it you write
JavaScript. You have the same access to the <code>$</code> bridge as you do
outside of an object. You also have access to variables you create outside
the function.
</p>
<h3>A quick note on using protocols</h3>
<p>
You can implement existing Cocoa protocols with sublcasses,
but there’s a “gotcha”.
I found out that if you use the <code>protocols</code> array your script
will just stop with no errors. I wrote an <a href="https://github.com/tylergaw/js-osx-app-examples/blob/master/docs/subclass-using-NSUserNotificationCenterDelegate.md">example and explanation</a>
that you’ll want to have a look at if you’re doing that type of thing.
</p>
<h3>Choosing and displaying images</h3>
<p>
We’re ready to open the panel, choose an image, and display it. Update the
<code>btnClickHandler</code> implementation function with following code:
</p>
<pre><code class="language-javascript">...
implementation: function (sender) {
var panel = $.NSOpenPanel.openPanel;
panel.title = "Choose an Image";
<p>var allowedTypes = ["jpg", "png", "gif"];
// NOTE: We bridge the JS array to an NSArray here.
panel.allowedFileTypes = $(allowedTypes);</p>
<p>if (panel.runModal == $.NSOKButton) {
// NOTE: panel.URLs is an NSArray not a JS array
var imagePath = panel.URLs.objectAtIndex(0).path;
textField.stringValue = imagePath;</p>
<pre><code>var img = $.NSImage.alloc.initByReferencingFile(imagePath);
var imgView = $.NSImageView.alloc.initWithFrame(
$.NSMakeRect(0, windowHeight, img.size.width, img.size.height));
window.setFrameDisplay(
$.NSMakeRect(
0, 0,
(img.size.width > minWidth) ? img.size.width : minWidth,
((img.size.height > minHeight) ? img.size.height : minHeight) + ctrlsHeight
),
true
);
imgView.setImage(img);
window.contentView.addSubview(imgView);
window.center;
</code></pre>
<p>}
}
</p></code></pre><p></p>
<p>
The first thing we do is create an instance of an <code>NSOpenPanel</code>. If
you’ve ever selected a file to open, or a location to save a file you’ve seen
panels in action.
</p>
<p>
We only want the app to open images. Setting <code>allowedFileTypes</code> lets
us specify the allowed types for the panel. It expects an <code>NSArray</code>. We create
a JS array with <code>allowedTypes</code>, but we need to convert that to an
<code>NSArray</code>. We make that conversion with <code>$(allowedTypes)</code>.
This is another use of the bridge. You use it in this way to bridge
a JS value to Objective-C. To bridge an Objective-C value to JavaScript you’d
use <code>$(ObjCThing).js</code>.
</p>
<p>
We open the panel with <code>panel.runModal</code>. That pauses code execution.
When you click Cancel or Open, the panel will return a value.
If you clicked Open, the contstant value of <code>$.NSOKButton</code> is returned.
</p>
<p>
The other note about <code>panel.URLs</code> is important. In JS, we access
the first value of an array with <code>array[0]</code>. Because <code>URLs</code>
is an <code>NSArray</code> we can’t use bracket notation. Instead we use the <code>objectAtIndex</code>
method. They both give the same results.
</p>
<p>
Once we have the URL of the image we can create a new <code>NSImage</code>.
Since it’s common to create an image from a file URL, there’s a handy method
doing it:
</p>
<pre><code class="language-javascript">initByReferencingFile</code></pre>
<p>
We create an <code>NSImageView</code> using the same process we did for creating
other UI elements. <code>imgView</code> handles displaying the image.
</p>
<p>
We want to change the width and height of our window to match the width and height of
the image. We also want to make sure we don’t go below a minimum width or height.
We use <code>setFrameDisplay</code> to change the size of the window.
</p>
<p>
We wrap things up by setting the image view image and
adding it to the window. Since its width and
height have updated we re-center the window.
</p>
<p>
And that’s our little app. Kick it around, open a bunch of images. And yes,
animated gifs will display and play so be sure to try some.
</p>
<h3>Tidbits</h3>
<p>
So far, we’ve been running the app from Script Editor
with <code>opt + cmd + d</code>. You run the actual app by double clicking
its icon like any other application.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-jsosx-appicon.jpg" alt="" />
<figcaption>Double click the icon to run the app</figcaption>
</figure>
<p>
You can update the app icon by replacing <code>/Contents/Resources/applet.icns</code>.
To access the app resources, right click on the app icon and choose “Show Package Contents”.
</p>
<h2>Why I’m excited</h2>
<p>
I’m excited about this because I think it has a lot of potential. Here’s
how I’ve been thinking about it. When Yosemite is available, anyone that installs
the OS will be able to sit down and write a native application. They will be
able to do so using one of the most ubiquitous programming languages that exists. They
won’t need to download or install anything extra. You don’t even have to install
or open Xcode if you don’t want to. That removes a massive barrier to entry.
It’s incredible.
</p>
<p>
I know there’s a whole lot more to building OS X apps than just being able
whip up a quick script. I don’t have any illusions that JavaScript will become
the defacto way to build for the Mac. I do think this will allow developers
to build small apps that make development easier for themselves and for other
people. Have a team member that’s not so comfortable using the command line?
Build a quick GUI for them. Need a quick, visual way to create and update
a large configuration file? Make a little app for it.
</p>
<p>
Other languages have these capabilities. Python and Ruby have access to the same
native APIs and people make apps using those languages. Something just feels
different about being able to use JavaScript. It feels subversive. It feels
like some of the DIY of the Web knocking on the door of the Desktop.
Apple left the door unlocked, I’m inviting myself in.
</p>
<p>
<i>Thanks for reading</i>
</p>
Media Seek from URL2014-09-20T00:00:00Zhttps://tylergaw.com/blog/media-seek-from-url/<p class="entry-intro">
I recently had a need to be able to control the time of <code>audio</code>
elements via a URL query string. I whipped up a bit of JavaScript to handle it.
There’s a demo <a href="http://lab.tylergaw.com/media-seek-from-url/">here</a> and
the source is <a href="https://github.com/tylergaw/media-seek-from-url">on GitHub</a>.
I had fun figuring it out and thought maybe you’d enjoy reading about it.
The following are details about why I needed it and the code I wrote.
</p>
<p>
As with most projects, this one started with a problem that needed fixin’ The
site this was for had a listing of audio tracks. Each list item included; the <code>audio</code>
element, a title/link to the page for that track, and a short description.
Some of list items had extra links to the track’s page in the description.
</p>
<p>
With this setup, the links to the track pages were causing the problem. Here’s
what was happening; Users were pressing play on one of the tracks in the list.
They would then click on one of the track’s links. That would take them away
from the listing to that track’s page. The navigation would cause them to
lose their place in the track they were listening to. The tracks were podcasts that
ran from 45 to 90 minutes in length so losing your spot at 33 minutes and 45 seconds was annoying.
</p>
<p>
This is where controlling the time of the <code>audio</code> element came into play.
Clicking on a link should take the user to the track’s page and
the track should resume playing where they left off.
</p>
<h2>Writing The Thing</h2>
<p>
YouTube does this style of jumping around in videos via the URL. I think
the query string style they use works well, so I took my cues from them. I decided
I’d use URLs like:<br /> <code>http://site.com/tracks/track-name?t=1h22m35s</code>.
</p>
<p>
Two quick things about the code examples and descriptions:
</p>
<ol>
<li>
I wrote the original code for a specific project. The code I’ll be showing
here is a modified version of that project’s code. The main difference
is that there are no links to separate pages. I left that out because I
felt like the interesting thing is not the navigation between pages.
</li>
<li>
The examples only show <code>audio</code> elements, but you can use the same code
with <code>video</code> elements.
</li>
</ol>
<p>
On page load, we run a function called <code>seekIfNeeded</code>. This
function checks the <code class="language-javascript">window.location.search</code> for the presence of
the string “t=”. This determines if we need to bother trying to parse
a time from the URL. This check is by no means fool-proof, but it gets the job done.
</p>
<pre><code class="language-javascript">function seekIfNeeded () {
var q = window.location.search;
<p>if (q.indexOf('t=') > -1) {
// Do parsing stuff
}
}
</p></code></pre><p></p>
<p>
Once it’s determined there’s a time to parse the fun code starts.
We declare a couple convenience variables for later use.
We bind a <code>canplay</code> event to the <code>audio</code>.
We convert the query string to seconds and then update <code>currentTime</code>
property of the <code>audio</code> element.
</p>
<pre><code class="language-javascript">function seekIfNeeded () {
var q = window.location.search;
<p>if (q.indexOf('t=') > -1) {
// Store the "1h34m27s" part of the query string
var timeString = q.split('=')[1],</p>
<pre><code> // Store a reference to the audio element with an id of "media"
media = document.getElementById('media'),
// Have we updated the time of the media element from the URL before?
seekedFromURL = false;
// We can only interact with audio elements when they are ready.
// Listen for the "canplay" event to know when that is.
media.addEventListener('canplay', function () {
// The "canplay" event is triggered every time the audio element
// is able to play. We only want to change the currentTime of
// the audio the first time this event fires.
if (!seekedFromURL) {
// The currentTime property seeks to a value of seconds in
// the media element.
media.currentTime = secondsFromTimeParts(partsFromTimeString(timeString));
// We've done the seeking, don't do this again.
seekedFromURL = true;
}
});
media.play();
</code></pre>
<p>}
}
</p></code></pre><p></p>
<h2>Converting a String to Time</h2>
<p>
Things get interesting with the line that sets
<code>media.currentTime</code>. It’s being set to the return value of the
<code>secondsFromTimeParts</code> function. That function is given the
return value of another function, <code>partsFromTimeString</code>.
</p>
<p>
I’ll break down that line of code from the inside out. <code>timeString</code>
is a string like; “1h32m23s” or “15m10s” or “12s”. Any combination of hours,
minutes, and seconds. Even though humans can figure out the string
represents time, the <code>audio</code>
element isn’t going to understand it. We pass the string to <code>partsFromTimeString</code>.
That function converts the string into an object of key/value pairs. In the object, the
keys represent a part of the time and the values the amount of time for each part.
</p>
<pre><code class="language-javascript">function partsFromTimeString (str) {
var parts = {h: 0, m: 0, s: 0};
<p>// Wrapping in a try to avoid an error in case someone gives the 't='
// query string with no time. It'll just default to zero without it.
try {</p>
<pre><code>// The regex match breaks the string into an array
// like ["1h", "32m", "6s"]
str.match(/[0-9]+[hms]+/g).forEach(function (val) {
// Creates an array with two elements, the time and part
// key like ["32", "m"]
var part = val.match(/[hms]+|[0-9]+/g);
parts[part[1]] = parseInt(part[0], 10);
});
</code></pre>
<p>}
catch (e) {}</p>
<p>return parts;
}</p>
<p></p></code></pre><p></p>
<p>
My first idea wasn’t to use a regular expression. I tend to avoid them because
I only have a cursory knowledge of them. But the query string posed a problem
I couldn’t solve without a regex. When trying
to break up a string into specific parts, I look for patterns. A common letter or
symbol or certain number or anything that repeats. A string like “1h32m23s”
doesn’t have that, so I needed a more complex pattern.
</p>
<p>
The pattern that sticks out is; a number, followed by one of three letters.
That letter could be “h”, “m”, or “s”. There’s no certainty
that all the letters will be there. And they may not appear in the same order. Ambiguity lead
me down the regular expression path. I suppose that’s what they’re for?
</p>
<p>
The <code>string.match</code> method returns an array of all matches found
by a given regex. The regex <code class="language-javascript">/[0-9]+[hms]+/g</code> reads the string from left to right
and says, “I want an integer with a value of 0 through 9, followed by the letter ‘h’, ‘m’, or ‘s’.”
If the regex finds that alphanumeric combination, it puts it in the array.
</p>
<p>
We’re getting closing to being able to separate the letters from the numbers.
That’s what we’re after. Even though we want to separate them, we don’t want
to disassociate them. Each letter gives us valuable information about the
number. It tells us what part of the time it represents.
</p>
<p>
The next step is to iterate over the array of number/letter pairs one by one.
</p>
<p>
Again, we use the <code>match</code> method with a regex. The <code class="language-javascript">/[hms]+|[0-9]+/g</code>
regex says, “I want either the letter ‘h’, ‘m’, ‘s’ or a number, 0 through 9.”
When either of those are found they’re placed in the array we named “part”.
</p>
<p>
The last bit of work is to add each of the time parts to the parts object we
created at the start of the function. Since we get the time value from a
string we need to use <code>parseInt</code> to convert it to a integer.
</p>
<p>
Now we have an object of time parts. The object looks like <code class="language-javascript">{h: 1, m: 22, s: 37}</code>.
The next step in <code>seekIfNeeded</code> is to get seconds from those parts.
For that, we use the <code>secondsFromTimeParts</code> function.
</p>
<pre><code class="language-javascript">function secondsFromTimeParts (parts) {
var seconds = 0;
<p>seconds += parts.s;
seconds += parts.m _ 60;
seconds += parts.h _ 3600;</p>
<p>return seconds;
}</p>
<p></p></code></pre><p></p>
<p>
<code>secondsFromTimeParts</code> adds up the values of the time parts object to
get a total number of seconds. To get the number of seconds from
minutes we multiply by 60. To get the number of seconds from
hours we multiply by 3600. 3600 equals 60 minutes per hour times 60 seconds per minute.
</p>
<p>
At this point, we’ve converted a string that represents a time to an actual
time value the media element can understand. We use that value–the number
of seconds into the media we want to go–to set the <code>currentTime</code>
property and then tell it to play.
</p>
<h2>Converting a Time to String</h2>
<p>
So now we can update the time of a media element with a query string, but what
if we want to go the other way? What if we have the seconds and we want
to convert that to our time string?
</p>
<p>
For that we’ll follow the previous process in reverse. We’ll get an object of
time parts from the seconds and then a time string from the parts.
</p>
<pre><code class="language-javascript">var timeStr = timeStringFromParts(timePartsFromSeconds(media.currentTime));
</code></pre>
<p>
Again, from the inside out, let’s look at the <code>timePartsFromSeconds</code>
function.
</p>
<pre><code class="language-javascript">function timePartsFromSeconds (seconds) {
var parts = {},
secondsInt = Math.floor(seconds);
<p>parts.h = Math.floor((secondsInt / 3600) % 24);
parts.m = Math.floor(secondsInt / 60);
parts.s = secondsInt % 60;</p>
<p>return parts;
}</p>
<p></p></code></pre><p></p>
<p>
We start by creating the empty parts object where we’ll store the key/value pairs.
<code>media.currentTime</code> gives the number of seconds and milliseconds
as a float. In this script I decided that I didn’t need milliseconds.
<code>Math.floor(seconds)</code> removes the decimal point and everything after it.
</p>
<p>
The next few lines use the total number of seconds to determine the number of
hours and minutes. Those values are set to the appropriate members of the parts
object. These calculations are the reverse of what we used in <code>secondsFromTimeParts</code>.
We know there are 3600 seconds in an hour so we divide the total seconds by it.
We use the modulus–<code>% 24</code>–because anything over 24 hours would be a new
time part: days. This script doesn’t handle days, but with a handful of additions
it would be able to. We determine the number of minutes by dividing the total
seconds by the number of seconds in a minute: 60.
</p>
<p>
That gives us an object of parts–<code class="language-javascript">{h: 1, m: 32, s: 27}</code>–that we need
to convert into a time string. We’ll do that with the <code>timeStringFromParts</code>
function.
</p>
<pre><code class="language-javascript">function timeStringFromParts (parts) {
var str = '';
<p>for (key in parts) {
if (parts[key] > 0) {
str += parts[key] + key;
}
}</p>
<p>return str;
}</p>
<p></p></code></pre><p></p>
<p>
This small function iterates over the keys in the parts object. For each key,
if the value is greater than zero we add the value followed by the key to the
<code>str</code> variable that we’ll return. The string returned will look something
like “1h32m28s”.
</p>
<p>
<code>secondsFromTimeParts</code> and <code>timeStringFromParts</code> could have
been combine into a single function like <code>timeStringFromSeconds</code>. I
chose to separate them because I like to make functions that are as small and
as specific as possible. Doing so provides clarity and allows for reuse. This
approach is a tenet of functional programming.
</p>
<p>
And with that, we have our script soup to nuts. If you take a look at the source
code you’ll see a function named <code>displayTimeURL</code>. That function
converts a time to a time string for display. It also contains a couple other
tidbits that may be useful if you need to build something like this yourself.
</p>
<h2>Bonus: Soundcloud</h2>
<p>
Soundcloud allows for embedding of most of their tracks. The embedded player
also has a <a href="https://developers.soundcloud.com/docs/api/html5-widget">pretty cool API</a>.
I whipped up a version of this script to work with Soundcloud players. I’d say
it’s about 98% the same. All the little functions I wrote aren’t concerned
with the whole story. They just want numbers, strings, arrays, etc. Because of
that I was able to use them with the Soundcloud version. <a href="http://lab.tylergaw.com/media-seek-from-url/soundcloud.html">Have a look</a> at the
demo. Like the HTML demo, you can add "?t=1m35s" to the URL to jump to that
point in the track.
</p>
<p>
The only differences are how you interact with the media element
and that Soundcloud takes milliseconds instead of seconds
to set the current time. Soundcloud provides events and
methods that you can use to control the player. They aren’t the same as their HTML
counterparts, but the ideas are the same. The Soundcloud example code is in
the <a href="https://github.com/tylergaw/media-seek-from-url">same repo</a> as the HTML
version.
</p>
<p>
This was a fun problem to solve. Both from the UX view and from the code. I’m
sure there are quite a few similar implementations, but I’m sure there are
none just like this one.
</p>
<p>
<i>Thanks for reading</i>
</p>
How I Manage Nginx Config2014-07-24T00:00:00Zhttps://tylergaw.com/blog/how-i-manage-nginx-config/<p class="entry-intro">
I host my personal sites–this one, <a href="http://lab.tylergaw.com/">my lab</a>, <a href="http://againwiththis.com/">my quips</a>,
<a href="http://listentoslayer.com/">nonsense</a>, and others–on a
Rackspace <a href="http://en.wikipedia.org/wiki/Virtual_private_server">VPS</a>.
<a href="http://nginx.com/">NGINX</a> is my web server of choice. Each site
has its own configuration file. I’m definitely not Mr. Sys Admin,
but I have an easy way of organizing those config files to help me make quick
updates and additions.
</p>
<p>
I’m assuming some knowledge with this post. Folks who have middle ground knowledge
of NGINX will get the most out of this. It’s not a how-to NGINX post, so beginners may not find it useful.
It also may not be helpful if you have advanced NGINX knowledge as you likely
have a better way to handle these things.
</p>
<h2>What I Do</h2>
<p>
I keep my config files in a private Github repo called “server-conf”. In it
I have two directories; “local” and “prod”. Local for the NGINX install
on my developement machine and prod for my VPS. I have different sites
locally than I do production. Each site whether local or prod has its
own config file.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-nginx-conf-editor.jpg" alt="The server-conf repo opened in a text editor" />
<figcaption>
server-conf in my current preferred text editor, Atom. Being able to use it instead of something
like Vim is much more comfortable for me
</figcaption></figure>
<p>
Both locally and in production, I clone the server-conf repo to my home directory.
To tell NGINX where the config files are, I use <code>include</code>
in the <code>http</code> block of the main NGINX config file, <code>nginx.conf</code>.
The location of this file is different for each install. Locally, mine is at
<code>/usr/local/etc/nginx</code>. I can never remeber where it is
on my VPS, I just have to peck around until I find it. Here are
the relevant snippets from my local and prod <code>nginx.conf</code>.
</p>
<pre><code class="language-clike"># local
<p>http {
...
include /path/to/home/server-conf/local/*;
}
</p></code></pre><p></p>
<pre><code class="language-clike"># production
http {
...
include /path/to/home/server-conf/prod/\*;
}</code></pre>
<h2>Why the Home Directory?</h2>
<p>
Mainly because I can never remember the default location of NGINX config
files. This is especially true on my VPS since I access it infrequently. Having it in the home
directory allows me to stumble on it since it’s the first directory
I see when I SSH into the VPS.
</p>
<h2>How is this helpful?</h2>
<p>
This helps me in a number of ways. First, like I mentioned above, it
makes locating the config files much easier.
</p>
<h4>Comfort</h4>
<p>
Doing this allows me
to use a text editor I’m most comfortable with to make updates to
production configs.
</p>
<p>
When I need to make an addition or change to production config I can make the changes
on my machine. I then commit and push the changes to Github. Then SSH into
my VPS, and navigate to the <code>server-conf</code> directory. There
I <code>git pull</code> and after an <code>nginx -s reload</code> my
changes are live.
</p>
<p>
Before this, to make production changes I would have to SSH into the VPS,
navigate to the default NGINX config location
and use Vim to make changes. This was a slow and error-prone
process for me because I’m not comfortable with Vim and I get a bit
lost sometimes navigating with the command line. Also, my server
isn’t the fastest so editing there is much slower than it is on my
laptop.
</p>
<h4>Git</h4>
<p>
The ability to quickly roll back breaking changes is huge.
If I pull down changes that cause the prod server to stop working, I
can use git to check out the previous version of the offending file(s) while I fix the problem.
If I’m making larger changes I can create a different branch to make it even
easier.
</p>
<p>
Config backup via Github. If I get a new computer or if mine is damaged,
I won’t have to recreate my configs for local development. I can just clone the repo and
make the quick update the <code>nginx.conf</code> to get back up and running.
</p>
<p>
Another cool thing is having a config revision history. Since everything
is in git and on Github, I can see when and what changes I’ve made.
This isn’t that helpful for me since I’m the only one making changes
and they tend to be small, but it could be a huge plus if you have
multiple people making updates.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-nginx-conf-log.jpg" alt="Github commit log for the server-conf repo" />
<figcaption>
The server-conf git log provides a history of config changes
</figcaption></figure>
<p>
NGINX is relativily easy to configure, but I used to dread making
changes in production because I’m not as handy with the command-line
and Vim as I am a graphical text editor. Having this setup in place
makes it as easy as updating any other files associated with my sites.
</p>
<p>
<i>Thanks for reading</i>
</p>
A Logo Design Process2014-07-17T00:00:00Zhttps://tylergaw.com/blog/a-logo-design-process/<pre><code> <p class="entry-intro">
As part of my site <a href="https://tylergaw.com/articles/how-i-start">redesign</a>
I wanted new branding. It’s not really a logo I guess, but
it’s what I’ll be using on this site and any other promotional crud that I make
like business cards. This was a fun process that let me dive deeper
into hand-lettering than I have before. Here’s a look into the design of the
new logo from pencil sketch to final (enough) image.
</p>
<p>
I’m not trying throw out everything and
start from zero. Because of that, my first step was look at what I have now. My current
logo appears in three variations. The largest appears
on my homepage. On medium size screens a smaller, stacked
version with "web" on top and "designer" below my name is shown. On small screens,
a side-by-side version with my name on the left and "web designer" on the right is shown.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-logo-process-existing.jpg" alt="">
<figcaption>
Variations of my current logo displayed at different screen sizes
</figcation>
</figure>
<p>
I always start as rough as possible. I grabbed the "Tyler Gaw" portion
of the existing logo and plopped it into a new PSD. I didn’t have a
plan for what I’d do with the logo so just started
typing out different titles next to it in different typefaces to get
things rolling. I tried side-by-side, top and bottom, serifs, sans-serifs, handwriting,
pulling, prodding, stretching, scaling, etc. I let myself play and make
as many ugly messes as I could.
</p>
<p>
After a good amount of messin’ I landed on something that definitely
wasn’t ready, but felt like a good path. My name with "Design & Development" and "Brooklyn NY, USA"
below it. I added a slight angle to both sides to make it a more
interesting.
</p>
<p>
The image below shows that everything was out of whack, it’s
not even fully readable. That was OK. It was enough that I could see
where I wanted to go with it.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-logo-process-roughage.jpg" alt="">
<figcaption>
The results of messing with the existing logo. This was bad on
purpose. It got me started down the right path.
</figcation>
</figure>
<p>
In the existing logo I refer to myself as a "Web Designer". That feels like
an accurate title because I design and
write code, but I wanted to be more clear so I went with "Design & Development".
Using "Web Designer"
as a title I often have to answer; "no, not only design. I write code too".
</p>
<h2>Back to Sketching</h2>
<p>
The next step was to get off the computer and do some sketching. I gave
myself some loose guides to follow for the width/height and for the
shape the text would be in. I redrew the same thing five or six times
until I got one that was close enough.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-logo-process-sketches.jpg" alt="">
<figcaption>
Trying to get a solid sketch to start
</figcation>
</figure>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-logo-process-sketch.jpg" alt="">
<figcaption>
The close enough pencil &amp; Ink sketch that was used as a starting point in Illustrator
</figcation>
</figure>
<h2>Moving Into Illustrator</h2>
<p>
I brought the close enough sketch into Illustrator and live traced it.
The conversion to vector was as good as could be expected. With any
hand-drawn text there are a lot of unneeded and rough points.
</p>
<p>
I did a pass where I cleaned up the spacing and removed many points to
make the letters of "Design &amp; Development" smoother. In the image
below you can see some sharp edges in places on the "Tyler Gaw". Those
were caused by me changing the height and rotation of different letters
and portions of letters.
</p>
<p>
When I thought I had the basic size and shape where I wanted it I
brought it over to Photoshop and placed it in a very early layout of
the new site.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-logo-process-first-thought.jpg" alt="">
<figcaption>
This is what I originally imagined the logo would resemble. Very
hand-drawn, close to my existing logo. There would still have been a lot of
clean-up required for this to work.
</figcation>
</figure>
<p>
This felt wrong. It was too spindly. I didn’t feel like it was standing up on its own.
Felt like it could get lost easily at smaller sizes. I wanted
it to feel more involved, more interesting, just more.
</p>
<p>
"What if I add some serifs?" I asked myself. I started with the "D"
in "Design". I widened the stroke and added slight serifs to it.
That’s what it needed. With that small change I could tell immediately that this was where
it needed to go. That also let me know that I was in for a whole lot
of tedious work as I would have to take the same steps for each letter.
</p>
<h2>The Work</h2>
<p>
It took me about about fours days to update all the letters. I
always started by removing as many points as possible. A general rule
I figured out was mo’ points, mo’ problems. Fewer points allow for
smoother curves and fewer pinches and nicks where they aren’t wanted.
After deleting the points it was a matter of moving them around until
the stroke width and serifs matched the rest of the letters. I used
some loose guides to keep the serif sizes similar, but since this is
supposed to have a hand-drawn look I could take liberties with them.
</p>
<p>
The removing, adding, and moving of points feels a lot like working with clay. You start with these
rough shapes and you slowly push and pull and smooth it into the shape
that you’re looking for. I’ve written about this process before
for <a href="https://tylergaw.com/articles/slenderman">Slenderman</a>.
For some reason it felt more like chiseling rock that time.
</p>
<p>
As a way to check that things were shaping up correctly and as a break
from tedium, I would often bring the logo into the rough layout in Photoshop.
Looking at it as a raster image at the approximate size it would be
displayed helped me make sure it was readable and looked how I wanted it.
I worked on the letters in bursts. I would do a letter, maybe two. Then
stop, get some air. Move it to Photoshop, check it, repeat.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-logo-process-ani.gif" alt="">
<figcaption>
Animation showing the rough steps of the process
</figcation>
</figure>
<p>
I’m happy with how it’s turned out. I’m also working on colors
for the redesign so I added bits of color to the logo and put it up on
<a href="https://dribbble.com/shots/1644137-GAW-Mark-WIP">Dribbble</a> for feedback.
From the beginning I’ve had my brain set on a red for some reason, but
all of the feedback points to yellow. I’ve been playing with yellow
as an accent color for the the rest of the site and it feels like it’s working so far.
</p>
<p>
Here’s a final (as-of-right-now) version of the logo embedded as SVG.
I removed the yellow drop shadow from my name that is shown in the Dribbble shot.
It was kind of cool, but didn’t feel necessary. I think I can get more
mileage out of the logo without it.
</p>
<figure>
<svg version="1.1" xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" x="0px" y="0px"
width="3267.3px" height="440px" viewBox="0 0 3267.3 2116.3" style="width:100%; height:auto; min-height:200px;background-color:#1e1e1d;"
xml:space="preserve" title="Tyler Gaw Design & Development - Brooklyn, NY U.S.A.">
<g id="Logo">
<path id="gaw-w" style="fill-rule:evenodd;clip-rule:evenodd;fill:#F9F9F9;" d="M2783.4,977c19.8,1,52-275.5,54.8-279.7
c3.2-4.7-29.4-47.2-22.1-47.5c1.6-0.1,96.1-21.8,124.4,0c8.3,6.4-19.9,14-15.8,33.3c9,41.6,87.5,202.7,94.3,204.2
c12.2,0-9.5-125.3-9.2-143c0.5-28.8-2.9-132.7-4-146.9c-1.5-19.3-6.5-110.3-6.5-127.6c0-79.5,2.8-178,6-199
c1.6-10.6-4-46.6-2.8-65.3c3.1-45.3-37.7-51.7-36.2-84.7c-0.8-8.4,152.9,15.5,151.1,18.2c-13.6,38.8-41.8,33.8-44.4,53.2
c-1.8,13.7-12.7,79.5-20.8,157.7c-0.4,0.4-3.7,78.6-2.4,103.1c1,19.4,10.8,100.4,10.6,106.2c-1.4,40,0.1,30.4,0.5,72
c0.2,19.8,15.4,82,11.2,111.1c-3.3,22.6,6.7,168.1,6,175c-2.1,20.1,41.9,30.9,39.5,55c-0.6,5.9-136.4,50.2-142.1,38.9
c-4.5-9,19.7-36.7,1.5-75c-10.4-21.7-28.6-52.9-45.2-89.6c-27.7-61.2-36.6-128.9-41.1-128.9c-5.7,0-4.7,67.5-15.5,142.2
c-11.9,82.7-35.1,174.1-28.5,192.3c-2.1,9.5-121.1,56.8-125.8,49.6c-3.2-4.9,11.9-62.1,9.7-74.8c-2.4-13.6-23.4-137.5-25.9-183.1
c-0.7-12.3-16.3-114.5-19.6-136.7c-3.2-21.2-15.4-94.7-17.4-102.9c-5.3-21.4-15.1-59.9-23.5-83.5c-5.9-16.4-8.4-33.8-13.4-50.5
c-5.3-18.1-28.4-138.5-32.8-154c-3.9-13.9-39.8-126.7-45.5-155.2c-1.9-9.4-41-58.8-44-68c-2.2-6.7,153.2,4.2,156,6
c17-4.9-30.3,61.4-29.7,65.5c1.8,11.2,34.8,135.9,37.7,144.7c8.1,24.5,27.5,129.3,30.4,145.8c2.7,15.3,48.6,274.5,47.7,289.9
c-0.7,12.1,6.1,92.2,4.7,115C2754.1,878.5,2763.9,976.5,2783.4,977z"/>
<path id="gaw-a" style="fill-rule:evenodd;clip-rule:evenodd;fill:#F9F9F9;" d="M2446.7,797.9c-17.4,2.8-66.3,24.6-74.6,26.4
c-15.5,3.4-88.7,13.5-93.8,17c-5.8,7.7,26.6,280.8,27,304c2,27,19.7,15.7,24.3,47.1c1.8,12.2-159.5,66.3-158.3,57.9
c2.4-15.8,18.3-34.1,21-49.8c2.7-15.8,21.6-110.2,22.2-126.1c0.2-5.5,0.1-11.1,1.1-16.5c5.1-27.7-8.3-171.9-5.7-199.8
c1.5-15.9,1.3-177.4,0.2-200.2c-1-19.8,2.8-291.5-1.3-310.9c-2.6-12.2-13.6-195.6-14.5-205.7c-1.1-12.5-26.4-44.2-26-57
c0-9.4,167,0.6,173,8c2.9,9.1-19.8,23.3-19,39c1.9,36.2,20.1,94.9,38,126.2c4.2,7.3,65.9,180.7,72.9,203.6
c3.3,10.9,58.6,132.3,62.7,157.7c3.1,19,21.2,109.6,21.4,128.8c0.2,18.2,41.2,314.4,40.3,332.7c-1.1,21.2,19,18,25.7,49
c1.2,5.8-133.2,45-135.9,40.3c-2.2-18.6,20.2-18.3,19.2-55.9C2464.6,1035.5,2450.9,797.2,2446.7,797.9z M2283.4,767.7
c1.9,1.2,66.9-1.9,79.3-6.7c10.3-3.9,72.2-22.5,71.9-31.5c-10.2-77.1-45.4-196.1-53.4-224.1c-2.7-9.3-69.2-225-71.8-232.2
c-3.5-9.5-46.9-117.3-52-117.8c-7-0.6-3.4,173.7-2,181c3.1,16.4,5.7,169.2,9.5,180.8C2270.7,535.4,2281.9,763.1,2283.4,767.7z"/>
<path id="gaw-g" style="fill-rule:evenodd;clip-rule:evenodd;fill:#F9F9F9;" d="M2005.4,258.9c-26.7-12.8-33.9-21.7-44.3-56.2
c-39.5-131.4-157.3-82.4-171-71.1c-5.9,4.1,24.3,560.6,23.9,586.6c-0.4,26.7-15.1,252.2-16.9,267.8c-0.2,1.9,1.4,55.8-1.6,80.6
c-2.6,21.5-40.2,246.2,24.8,237.6c50-6.6,114-76,133-121c9.5-17.6,27.9-395.9,23.7-395c-24,5.4-44.5,12.2-65.7,15.7
c-9.9,11.5-18.8,15.1-35.7,14.7c-4.8-0.3-2.2-95,1.3-98.2c5-8.7,44.7-6.6,72.7-7.5c21.1-1.6,77.5-10.4,78.1-8.9
c2.4,6.2,9.9,220.2,8,239.7c-1.4,14.3-2.7,348.2-15.4,350.5c-39.3,4.8-48.2-39.4-48.2-39.4c-5.8,18.2-19.8,70.3-62.1,82
c-16,0.3-125,54.7-159.8,48.6c-35-6.1-33.9-244-32.1-278c0.7-1.9,25.1-241.6,25.1-250c-0.1-5.6,3.2-206,3.1-231
c0-15.9-16.8-235.1-21.6-314c-3.4-19.1,3.2-170.9,10.1-181c14.8-30.9,15.5-67.7,139.2-67.7c84.9,0,78.3,38.2,102.6,41.5
c26.4,3.6,26.4-18.1,37-1.7C2014.7,120.5,2014.6,234.7,2005.4,258.9z"/>
<path id="tyler-r" style="fill-rule:evenodd;clip-rule:evenodd;fill:#F9F9F9;" d="M1488.2,432.6c-0.1,11.3-7.8,101.4-7.7,117.9
c0-21.1,2.1,242.4-18.5,333.9c-5.3,17.7-68.6,34.2-83.6,36.2c-0.3,0,74.8,23.7,69.8,79.7c-2.8,30.9-9.9,186-10.8,217
c-0.4,15.1-24.8,170.5,73.5,112.5c10.9-6.5,22.5-20.2,30.6-25.1c13.3-8,30.9,78.8-56.4,100.8c-4.7,0.1-106.3-36.7-113-40.9
c-13.9-29.4,1.3-79-1.3-104.3s9.2-222.5,9-229.7c-0.6-23.3-5.7-72.3-36.3-64.3c0,0-3.9,66.8-7.3,89.2
c-3.5,23.1-14.7,128.1-18,142.2c-3.1,13.2-6.6,83.7-4.1,95.8c0.8,4,14.5,53.9,10.2,55.2c-4.8,1.5-106.3-41.8-108.6-45.9
c-1.7-3.2,20.5-23.7,21.1-27.5c4.6-29.7,12.5-58.8,19.7-88c3.7-14.8,3.1-30.2,4.7-45.3c2.5-22.8,8.5-96.5,11.6-119.2
c2.4-17.5,6.1-41.9,7.4-59.6c0.3-4.8-0.4-9.7,0.1-14.5c6.6-64.2,4-192,4-177.4c0-20.8,1.4-41.3,2.6-61.9
c0.9-14.5,4.5-68.3,4.1-88.9c-0.4-21.4,4.6-220.4,4.3-225.6c-1.1-20.2-8.4-83.1-8.3-94.5c0.1-11-21.6-195.2-20-209.2
c0.3-2.9-32.7-17.4-31.2-28.4c5.2-4.1,68.9-6,74-6.1C1532,44.8,1480.6,248,1480.4,279.3C1480.4,296.4,1488.3,417,1488.2,432.6z
M1348.5,854.7c0.4,5.6-3,4.7,33.9-0.4c21.1-2.9,36.1-104.4,38.1-122.4c2.4-20.9,9.6-148.1,9.7-150c0.3-10.9-0.6-148.6,0-161.5
c0.5-11.3-4.5-60.4-4-72c1.3-28,1.1-167.5-5.8-188.1c-6.7-20.1-3.3-34.9-21-48c-29.4-21.8-25.5-25.7-63-22
c0,0,10.5,96.4,10.5,114.5c0,17.7,8,233,8,260.5c0,29-4.3,139-4,143c1.2,19.3-0.9,78.2-2.4,88.2
C1344.5,721.8,1348.8,832.7,1348.5,854.7z"/>
<path id="tyler-e" style="fill-rule:evenodd;clip-rule:evenodd;fill:#F9F9F9;" d="M1022.7,887c10.8-55,1.6-97.9,3.7-117.4
c0.8-8.1-9.2-196.6-10.5-214.1c-1.6-20.7-22.1-261.9-22.2-293.8c0-13.6-10.8-144.4-9.4-156.7c-1-20.6-41.9-15.3-42.2-35.7
c0-4.1,76.4-4.4,84.7-4.3c10.6,0.1,45.8-1.2,52.6,0.4c4.2,1,73.9-2.3,85.8-2.2c5.4,0,11.8,63.4-4.8,62.4c-16-1.3,28-32-109.3-32
c0.1,6,21.3,212.8,26.7,265.3c2.5,52.5,11.3,212.2,12.7,236.9c0.5,10.2,6.7,160.8,4,178.4c-2.2,13.9-2.9,75.6-4.6,88.3
c-1,7.9,53.3-3.2,74.6,1.7c0.6,0.1,1.9,76.8-5,74.3c-17.8-6.3-80.4-14.9-81.9-11c-2.7,7.5-2.7,100-9.4,138
c-5.1,24.9-11.8,93.4-15.7,118.5c1.1,4.2,69.4,21.7,81,26.5c4,1.7,27.1-1.9,31.7-2.7c3.8-0.7-2.4,95.9-6.7,94.8
c-4.2-1-112-41.6-122.5-45.5c-10.1-3.7-91.4-29-91.8-29c-9-11,33.3-56.3,33.3-56.3c4.8-9.3,10.6-46,12.9-67.7
C990.7,1100.5,1019.7,913.1,1022.7,887z"/>
<path id="tyler-l" style="fill-rule:evenodd;clip-rule:evenodd;fill:#F9F9F9;" d="M891.5,1107.5c-0.6,15.6,1,118.3-5.5,115.8
c-8.7-3.3-116.2-44.4-137.3-48.9c-3.2-0.7-98-28.7-101.3-29.1c-0.4-25.7,51.8-47.8,52.6-54.6c3.4-23,29.1-94.3,29.6-97.7
c3.4-22.4,19.3-61.8,20.8-84.3c1.6-22.8,6.3-60.8,8.3-83.7c1.5-16.7,27.3-180.5,28.7-195.7c0.9-9.5-2.9-28.5-1.3-38
c4-23.9-7.1-253.7-7.3-272.3c-0.3-20.7-0.8-38.1-3-58.7c-1.5-14.1,10.9-143.1,8.3-157c-1.6-8.6-33.7-14.7-33.7-28.4
c0,0,60.1-0.3,77.1-2.8s55.3,2.8,55.3,2.8c-0.7,19.1-36.2,19.8-33.8,37.1c2.1,12.1-1.1,141-0.2,155c1.7,25.7,3.1,62.1,3.3,77.3
c0.4,23.5,4.7,93.1,6.7,111c1.8,15.9,1.2,125.8,0,138c-1.4,14-9.9,124.2-12,157.7c-1.7,27.6-6.2,54.7-11.2,82
c-2.4,13.3-8.4,57.9-9.8,80.7c-1.4,22.1-15.6,73.4-17,81.3c-2.4,13.7-11.5,36.8-13,50.7c-4.1,38-10.1,61.7-10.1,61.7
c32.7,13.5,36.3,19.4,53.3,24.7C859.2,1138.4,874.2,1104.5,891.5,1107.5z"/>
<path id="tyler-y" style="fill-rule:evenodd;clip-rule:evenodd;fill:#F9F9F9;" d="M711.5,85.9c-3.3,22.8-22.1,14-29.5,36.5
c-4.2,12.7-17.1,70.2-21,83c-3.2,10.8-31.8,102.6-35,108c-3.5,5.9-50.2,223.8-52.5,228.9c-4.1,9.1-7.5,47.5-8.8,54.3
c-5.2,26.6-2.2,60.1-1.3,93.1c-3.7,24.5-9.2,160.9-10.4,182.9c-0.7,14.5-7.7,56.3-10.3,70.7c-0.9,4.8-8,115.5-6.8,131.8
c0.5,7.1,15.8,27.9,12.5,49.2c0,0-158.9-55.2-159.7-61.3c4-10.7,27.3-10.7,36.7-21.5c5.9-10.9,27.9-44.5,29-63.5
c0.7-11.4,15.3-61,20-78c1.3-4.8,16.3-97.8,17-123c0.6-20,0.5-84.3,4-113.9c2.1-44.7-6.3-48.6-7-75.9c-1.9-31.2,2-87-4-106.7
c-5.1-16.5-35.8-223.5-40.4-245.1c-3.6-17.3-13.7-100-22.3-118.4c-4.4-9.3-32.6-15-37.7-24c-2.4-4.3,94.1-8.3,95.5-6.2
c1.8,4.6-26.1,18.4-22,34.7c12.4,49,23.4,136.1,27,151.5c4.9,20.8,38.5,222.8,42.5,222.9c3.9,0.1,34.7-184.7,39.5-195.9
c9.4-22.1,36.9-164.3,42.4-181.5c4.5-14.3-21.9-29-23.4-35.5c-0.4-2,30.7,3.9,61.8,0.5C660,82,711.4,83.9,711.5,85.9z"/>
<path id="tyler-t" style="fill-rule:evenodd;clip-rule:evenodd;fill:#F9F9F9;" d="M137.7,929.3c8.4-14,28.7-258.7,31.3-277.9
c2-14.3,8.4-296.9,7.7-308.6s-3-185.9,0.7-205.2c0,0-42.4,2.7-66,6.9c-9.1,0.2-41.9,32.7-50.4,35.8c-2.6,1,6.8-62,9-63.7
c11.7-3.6,21.2-1.8,33.9-1.2c8.8,0.4,84.5-4.5,84.5-4.5s95.8-11.7,119.3-10.8c9.7,0.4,32.9-1.3,35-1.2c6.3,0.4,16.3,69.9,10,69.4
c-5.7-0.4-38.2-34.7-46-35.3c-16.8-1.5-56.1,1.7-56.1,1.7s6.2,223.5,8.1,245.7c-0.5,15.5-6.7,98.5-7.9,110.6
c-1.8,17.9,1.1,95.3-0.7,108.8c-2.7,20.9-11.2,347.1-11.3,356.5c-0.2,10,24.7,56.6,16.7,72c0,0-155-53.8-153.9-67
C105.2,951,135.3,935.7,137.7,929.3z"/>
<path id="dev-t" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M3188.7,1119.3c0,0,2.3,33.9-1.9,32.8
c-3.8,1.2-6.5-6.5-33.3,1.8c-2.2,0.7-1.7,149.2-5,168.2c-6.1,35.3-4,64.6-7.2,105.9c-2,26.2,3.4,52.1,1.1,78.2
c-1.7,19.3,0.9,38.5,2.3,57.8c1.6,21.2,3.6,42.4,5.4,63.6c2.5,28.8,8.1,171.8,10.7,187.7c1,4.5,16.6,14.5,15.4,21.9
c-1,1.7-63.1,2.5-62.8,0.6c2.3-12.3,13.6-19.5,13.9-24c1.1-13.8-1.2-124.6-1.7-153.8c-0.2-15-11.2-104.7-10.1-135
c0.9-23.8-4.3-47.7-0.5-71.3c3-18.4,3.5-87.9,5.9-104.2c2.1-14.5,2.8-28.6,4-43.3c1-12.9,4.7-148.5,2.7-148
c-36.3,9.2-27.3,17.7-32,19.8c-0.9,0.8-8.9-27.4-7.9-28.5c9.2-5.8,17.5-9,27.2-11.1c17.8-3.8,37.1-11.3,55.8-16.4
C3175.4,1121.5,3181.7,1118.9,3188.7,1119.3z"/>
<path id="dev-n" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M3040.2,1764.4c3.4-2.8-2.9-89.2-2.7-107.3
c0.2-19.2-4.3-38.1-4.6-57.2c-0.2-14.2-6.9-162.2-5-179c3.4-30.2-2-219.6-0.9-235.3c-4,0-15.1-4.8-19-10.3
c-1.2-4,52.4-17.3,52.2-15.7c3.1,12-9.8,11.9-9.2,23.5c0.8,8.2,3.9,123,1.8,143c-1.5,13.9,2.1,57.9,0.1,71.8
c-2.6,18.1,1.3,158.1,1.2,177.1c-0.2,27.7,7.3,219.9,8.4,238.2c0.1,2.7,15.9,17,14.3,23.9c-0.3,1.2-60.3,1.9-60.7-0.2
c-1.5-8.9,9.8-18.1,9.4-24.1c-1-14.6-11-76.9-14.5-89.6c-4.9-17.3-17.5-88-18-103.1c-0.6-18.7-14.1-164.6-15.4-190
c-0.9-17.7-17.8-178.4-24-176.2c-9.5,1.5,4.7,209.8,5.2,218.7c1.3,23.8-0.8,159.6-0.6,171.2c0.5,20.1,0.5,145.1-0.9,167.9
c0.1,5.2,14.1,15.5,16.5,25.2c0.8,3.3-56.9,3-56.8,0.4c-1.7-13.7,10.3-12.4,10.5-24.5c1.7-81.4,7.9-360.4,5.6-396.4
c-0.6-23.9-5.2-193.3-6.7-207.2c-0.8-4.4-12-8.2-15.1-15.1c-0.1-2.7,42.8-11.7,44.4-9.2c7.9,16.2,26.3,58.5,29.1,77.2
c5.5,37,21.6,243.3,25.5,266S3037.4,1762.4,3040.2,1764.4z"/>
<path id="dev-e-3" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M2800.8,1629.3c1.5-17.2-0.2-38.2,1.5-55.3
c5.3-53.1,5.7-217.8,4.3-229c-0.5-3.6-5.7-90.3-6.2-94.7c-0.9-1.7-14.9,1.6-14.9,1.6c-1.4-12.2-3.5-20.7-3.5-20.7
c26.5-11.5,79.9-26.6,90.7-29.9c4.9-1.5,8.5,36.7,7.6,39c-1,1.6-5.3-8-15.5-7.8c-15.8,0.3-31.5,7.3-38.7,10.8
c-1.9,1,0,53.8,2.7,65.8c3.6,16.3,3.8,72.1,3.5,92.7c-0.4,22.5,1.4,60.9,0.2,68.6c-2.8,18.4-2.7,30.9-3.2,49.5
c-0.4,15.2-1.8,31,0,31.2c23.9,1,33.9-6.4,41.6-5.8c0.8,0.8,2.5,37.2-2.1,36.7c-6.2-0.5-21-7.2-42.2-4.5
c-1.8-0.3-4.4,236.6-0.1,236.4c6.4-0.2,29,1.3,35.2,1.2c10.6,0,8.4-11.2,18.8-10c4.1,0.5,0.4,36.6-1,36.7
c-10.5-1.1-81.9-1.7-87.2-2c-0.4,0-4.6-10.4-1.6-25.1c0.5-2.3,10.2-1.6,10.2-1.6C2801.9,1760,2800.2,1636.5,2800.8,1629.3z"/>
<path id="dev-m" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M2659.8,1551.3c-0.1-7.7,6.7-11.2,6.6-16
c-0.3-18.9-25.2-164-27.5-163.5s-0.3,96.4-1.4,107.3c-1.2,11.6,0.5,23.4,0.3,35.1c-0.1,7.2,0,100.8-3.4,122.1
c-1.6,10.1-1.6,138.8-3.2,181.6c-0.3,6.9,11.2,9.3,9.9,19.7c-0.6,1.8-49.5,2.9-49.3,0.2c-0.1-8.6,7.7-11.6,8.7-16.6
c2.9-18.8,5.1-138.8,7.6-157.6c2.9-21.2,1.6-120.6,2.6-137.1c0.8-14.1,0-46.7-0.1-55.1c-0.1-35.5,7-99,4.4-134.5
c-0.9-12.7-2-26.6-4.1-41.6c-0.3-2-16-5.5-15-11.5c0.5-2.5,56-21.2,57-17.5c0.7,2.6-6.1,17.5-5.5,22c0.2,1.7-4.6,13.5,2.2,55.2
c0.6,7.3,32.1,157.9,32.1,157.9c4-4,13.9-130.3,18.6-179.4c4.1-42.5,5.4-42.8,6-55.8c-0.6-5.4-12.1-6.3-12-12.9
c1.1-3.5,47.3-18,47.5-15.1c1.6,7.4-7.6,14.4-7.5,21c-0.1,6.1,8.3,106.7,9.6,124.8c1.7,24.5,5.9,72,3.2,96.4
c-2.8,26-0.2,43.1-2.9,69.1c-1,9.9,2.8,52.9,1.1,62.8c-3.8,23.6-0.2,181.1-1.6,205c-0.2,6.3,9.5,9,8.9,21.1
c0.2,2.5-49.6,2.5-51.2,0c-1.7-7.2,13.8-12.4,13.8-21.2c0.2-36.6-1.2-164.8,4-201c4.4-30.4,0-84,3.3-114.5
c2.5-23,1.7-136.4-0.9-159.5c-0.5-4.5-9.2,83.3-10.5,93.1c-1.3,17.2-10.5,65.1-16.5,94.9c-0.8,4.5,6.3,6.3,6.4,13.5
C2701.3,1547.1,2660.9,1555.3,2659.8,1551.3z"/>
<path id="dev-p" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M2577.5,1532.5c0.2,11.4-0.3,32.8-1.6,44.2
c-3.7,31.6-5.6,40.2-54,40.9c-0.8,0.5-2.8,78.7-2.7,94c0.1,12.6,1.4,25.2,0.2,37.7c-1.4,14.2,2,54.6-0.2,68.6
c-1.7,10.7,12.4,11.8,11.1,21.1c-0.3,2.2-54.9,4.2-55.2,1c-0.6-8,12.9-12.3,13.8-19.9c1.6-14.2,3.2-39.4,5.4-53.5
c4.4-27.4,2.8-55.1,4.3-82.5c0.2-3.8-0.9-44.7-0.3-48.5c1-6.6-2.2-59.7-1.1-77c1.4-23.5-0.2-46.8-0.7-70.1
c-0.5-21.7-5.2-40.3-7.3-61.8c-2-20.6-6.3-67-11.4-95.1c0,0-11.1,4.8-11.9,1.4c-0.4-1.9-11.3-14-10.6-14.7
c11.4-5.4,59.6-17.9,72.5-19.7c9.5-1.3,15.8,2.5,22,10c6.8,8.3,9.9,21.7,13,31.7c3.7,11.9,4.3,45.4,5.4,53.3
c2.4,18.4,3.6,33.5,4.6,52C2573.6,1456.7,2576.5,1502.1,2577.5,1532.5z M2521.5,1552.3c0,12.2,0.3,37.6,1.3,37.6
c2.8-0.2,11.5,0.8,17.5,0.2c20.8-2.6,18.8-6.8,19.2-25.7c0.5-22.7-1.4-55.5-1.3-75.7c0.1-16.7-9-99.2-11.1-117.5
c-1.8-15.3,1.6-26.9-6.9-40c-4.1-6.4-12.2-9.6-17.9-8.5s-19.6,5.1-21.5,5.6c-1.6,0.7,8.6,67.8,9.2,74.7
c2.6,28.1,9.3,53.1,10.2,81.4C2520.3,1490.1,2521.6,1535.3,2521.5,1552.3z"/>
<path id="dev-o" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M2444.7,1488.8c1.2,29.9,2.7,99.7,3.2,131.6
c0.5,31.6-2.3,67.1-4.7,98.5c-2.5,32.5-3.5,64.5-14.7,95c-4,10.7-4.7,32-39.9,32c-14.6-1.7-31.5-12-32.7-24.9
c-0.7-7.5-2.7-14.7-2.8-22.5c-0.2-11.1-0.5-22.3-0.2-33.3c0.7-24.9,2.3-49.9,1.2-74.8c-1-21.6-1-47.1-1.3-68.7
c-0.2-14-3.4-65.8-3.6-79.6c-0.3-20.7-4-40.9-5.1-61.4c-0.9-16.5-2.9-33-3.6-49.6c-1.3-29.3-4.3-54.5,8.8-80.8
c3.6-7.2,12.2-12.5,18.8-16.7c26.1-9.3,44.9-7.7,49.6-0.7c6.1,9.1,9.9,34.4,11.9,41.6c2.8,10,6,20.7,7.5,31.1
C2440.8,1432.5,2444.1,1459.5,2444.7,1488.8z M2382.6,1817.3c1.5,5.4,9,6,12.6,1.8c2.5-3,7.8-12.1,8.8-15.8c5.1-14.6,9-53.7,11-71
c1.7-14.8,1.3-29.9,2.7-44.6c1.9-19.2,3.2-42.4,4.2-61.6c1.1-21.5-2.1-81.1-1.8-102.6c0.2-12.3,0.8-24.7-0.3-36.8
c-2-21.8-5.5-43.4-7-65.2c-0.4-5.2-5.7-44.5-10.3-63.2c-2.7-13.4-16.5-11.7-20.3-11.9c-7.9-0.9-12.1,33-14.1,49.3
c-2.1,16.7-0.7,65,0.7,72c3,16,3.5,32.4,5,48.5c2.3,25.8,5.5,89.6,5.5,115.7c0,19.3,1.6,42.5,1.4,61.8c-0.2,18.3-1.6,36.4-2.1,54.6
C2378.3,1761.3,2380,1807.8,2382.6,1817.3z"/>
<path id="dev-l" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M2319.2,1841.6c-18.2-1.7-75.8,1.1-92.6-0.7
c-1.8-1-1.1-12.5,0-23.7c0.3-3.5,15.9-1.5,16-3.6c0.8-12.5,6.9-83.2,7.9-95.8c1.4-16.9,3.2-33.7,6-50.4c3-17.8,3.1-35.6,4.4-53.5
c0.9-11.6,1.1-72.7,1.4-91.6c0.4-22.7,2.3-33.9-1.3-56.2c-1.5-9.2-8.4-66.8-7.2-76c0.5-3.8-16.9-8.3-16.9-18.8
c0-1.6,53.3-13.6,53.2-12.9c2.6,17.2-8.3,21.8-7.5,26.8c1.7,11.1,4,87.9,3.7,97c-0.3,10.6,2.2,57,1.1,68.5
c-2.2,21.8-2.2,55-3.1,76.8c-0.9,21.2-7.1,84.4-8,89.2c-2.8,14.7-2.1,36.1-4.1,50.9c-2.1,15.4-3.9,46.9-2.9,47.1
c7.6,1.7,24.5,0.5,32.1,0.1c13.9,0.4,12.9-10.6,18.7-8.9C2324.3,1807.2,2321.5,1842.2,2319.2,1841.6z"/>
<path id="dev-e-2" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M2149.6,1691.9c-1,0.1-3.8,47.8-7.4,64.1
c-2.9,13.3-5.2,59.7-1.9,59.5c12.2,0.5,14.3,1.8,25.4,1.8c9.7,0,7-13.3,15.5-13.9c2.4,0.8,1.9,37.4-1,37.2
c-4.6,0.4-23.2-0.9-27.8-0.6c-12.2,0.7-41.8,0.9-55.4,0.5c0,0-1.1-12.1,1.3-22.8c1.1-5.1,11.9-1.4,12.2-2.9
c5.8-39.5,14.6-154.9,17.5-176.4c3.1-22.7,8.1-68.3,7.6-91.8c-0.1-7.3,1.9-111.3,2.2-118.6c-0.1-4.1-15.9,9.7-15.3-2.7
c0.3-5.4-4.7-22.8-3.5-24.3c4.8-0.8,50.6-12.6,63.2-14.7c7.6-1,22.9-10.2,25.2-10.2c4.5,0.1,7.4,44.1,5.8,47.3
c-0.6,1.2-19-13.2-19-13.2c-11.5,3.3-19.5,5.8-29,8.9c-2.9,1.5-3.2,47.2-3,53.4c0.9,30.2-0.4,58.2-0.5,88.4c0,21.9-2.4,33.7-4,55.5
c-1.7,24.6-6.5,45.9-4.9,46c37.5,1.5,22.7-13.1,35.3-11.3c3.5,2,2.8,41.1,1,42.6c-4-0.3-9.6-2.8-15.6-3.3
C2165.6,1689.9,2155,1691.6,2149.6,1691.9z"/>
<path id="dev-v" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M2016.9,1807.9c5.8,0,11.1-75.8,13.4-87.9
c4-20.8,8.4-71.7,9.5-76.4c6.7-28.4,14-120.5,15.6-136c0.1-1,7.4-55.2,5.3-69.6c-0.7-12.8-14.1-6.3-16.5-16.4
c-0.9-4.1,49.3-15.5,50.1-14.3c0.1,14.9-12.7,13.9-12.4,23.4c0.3,11.1-6.2,67-4.6,80.1c0.7,5.4-3.9,91.4-11,127.2
c-1.8,9-20.5,97.4-25.7,131.3c-1.7,9.8-5.3,39-9,50.7c-0.5,8.5,6,5.8,6.8,21.7c0.2,3.5-44.3,1.8-46.9,1.8
c-4.4-11.2,10.3-16.6,8.8-23.8c-1-4.9-3-61.3-3-66.3c0-36.9-2.5-75.2-3.7-112c-1-31.2-3.7-61.6-3.7-92.9c0-15.5-2.9-81.1-3.3-90.3
c-0.2-6.4-16.4-3.5-17.9-10.9c-0.8-4.2,50.1-16.6,51.6-15.1c1.4,9.2-13.2,14.1-12.3,20.6c0.7,5.4,5.3,62.5,4.3,79
c-1.2,20.6,3,37,3,66.7c0,13.3,1.8,82.2,2.3,106C2018.1,1721.6,2012.4,1807.3,2016.9,1807.9z"/>
<path id="dev-e" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M1918.8,1840.6c-11.3,0.8-56.8-1.3-58.8-1.1
c-3.4,0-1.3-14.4-0.5-23.2c0.1-1.3,6.7,1.3,7.4-1.1c6.1-23.6,12.4-172.7,12.2-180.2c-0.4-23.6,0-39-3.8-62.4
c-2.2-13.4-2.2-70-3.3-77.2c0,0-13.5,4.1-13.4,2.3c0.7-4.4-5.3-17.8-3.7-18.2c2.6,0,38.5-11.2,43.7-12.2c10.9-3.2,32.4-8.4,42-9.3
c1.6-0.2,2.1,34.5,0.5,39.4c-0.2,0.6-9.8-10.1-14.8-9.9c-7.4,0.3-22.3,2.7-30.2,3.6c-1.9,0.2-2.2,19.6-1.2,28.5
c2.9,25.4,1,51.8,3.8,77.3c4.1,36.7,2.4,61.5,0.6,98.4c-0.4,8.7-1.4,14.7,0.2,15.1c32.3-0.2,28.3-5.5,36-5.2c0,0-0.1,6.2-0.2,11.7
c0.1,6.4-0.3,19.5-0.3,19.5c-6.9-0.5-6-5-37.1-4.2c-2.4,0.5-2.3,6.4-4,25.9c-1.8,15.5-5.6,57.6-4,57.7c12,0.4,20.1,1.5,31.1,1.6
c12,0.4,12.6-10.5,18.5-10.3c1,3.9,1.8,31.1-0.5,32.8C1937.1,1841.4,1927.7,1840.5,1918.8,1840.6z"/>
<path id="dev-d" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M1740.7,1610c-1.2-31-3.1-35.3-3.1-35.8
c-0.1-1.6-4.5-32.4-4.8-34c-3.1,0.8-15.9,4.4-16.5,2c-1.2-4.8-2.9-22.6-2-23.1c9-4.4,43-14.5,53.4-16.3c3.7-0.1,26-3.1,29.4-1.6
c13.6,6.3,21.4,17.1,22.4,31.3c2.1,30.7,4.8,55.4,5.4,86.1c0.2,13.8,0.3,62.5-0.3,81c-0.9,25.2-2.5,50.2-7.2,75.1
c-3.1,16.1-9.1,31-16.8,45.2c-3.5,6.5-13,13.8-24.2,17.3c-12.1,3.8-48.5,2.7-61.6,2.7c-2-0.7,0.9-18.7,1-22.8c10.8-0.5,13-1,13-1
c4.9-27.3,11.9-135.7,12-152.7C1741.9,1638,1740.4,1618.2,1740.7,1610z M1802.5,1660.2c0.1-6-2.9-105.3-4.4-118.1
c-2.8-23.9-45.2-7.9-45.2-7.9c1.2,11.2,6.8,30.5,7.3,41.9c0.8,16,1.8,16.9,2.9,32.9c0.4,6.6,1.5,20.5,0.3,32.7
c-1.5,15.7-1.9,82.7-2,95.5c0,4.8,0,9-0.4,14.1c-1.1,12.4-11.7,63.9-10.8,64c10.7,0.8,26,0.2,31.6-7.9c8.3-12.1,12-38.9,12.8-41.2
c1.7-11.5,2.1-24.1,3.5-35.5C1800.7,1710,1803.6,1680.9,1802.5,1660.2z"/>
<path id="des-n" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M1172.1,1672.2c-0.7,23.9,0,141.7-1.4,153.1
c-0.7,7.7,10,3,11.3,13.8c0.2,1.5-57.2,3.5-57.2-0.2c0-7.6,9.2-5.6,10.6-11.5c5.6-24,7.6-91.9,9.8-101
c4.3-17.8-1.5-224.9-1.5-245.3c0-10.5-10.4-12.9-8.5-23.4c0.9-5.1,33.3,7.2,35,11.3c2.2,5.4,8,42.2,13.5,54.2
c4.4,9.6,13.8,44.7,15.5,52.4c5.1,23.8,20.3,107.5,24.7,129.9c1.2,5.9,16.5,85.4,17.7,88.7c9.4-2.5-3.1-189-2.8-202.5
c0.4-16.9,1.7-80.8,0.7-92.2c-1.6-4.1-9-11.7-8.9-18c0.9-2,51.2,15.3,48.6,19.6c-3.4,8-11,0.7-12.3,13.6c1.7,15.3-4,44.4-4.1,59.9
c-0.1,16.3,5.5,98.1,3.5,139.4c0.1,25.1,3.6,82.4-1.2,103.4c-3.8,16.5,9.6,8.7,11.3,20.3c0.3,2-58.4,4-59.8,0.3
c-0.9-5.9,11.7-5.3,7.2-20.8c-4.4-15.2-15.6-67.2-17.9-82.8c-2.5-17.7-11.1-34.5-13-52.2c-1.5-13.9-22.6-123.5-24.5-127.3
C1165,1554.4,1172.7,1649.5,1172.1,1672.2z"/>
<path id="des-g" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M1046.7,1434.9c-0.8,1.3-20,49.6-20.9,47.2
c-12.2-12.1,4-8.4-8.2-32.8c-4.4-9.9-36.3-32.3-46.9-25.8c-8.6,5.3-12,19.1-14.1,28.2c-2.6,11.3,4,60.4,4.2,73.5
c0,1.5-0.8,90.1-2.2,97.4c-3,16-6.7,111-10.4,137c-2.2,15.1,0.7,22,5.4,36c7,21.2,36.4,29.8,52,14.4c7.3-7.1,5.6-60.8,7.3-78.8
s0.3-37-2.9-36c-3.5,0-14.5-2-24.4-0.9c-0.3-9.1-14.3-26.2-7.2-29.7c15.5,0.9,67.6,2.4,82.7,0.4c3.3,1.3,1.4,20.5-2.7,27.7
c-0.7,8-15.3,1-16.3,5c-3.1,12.5,2.3,138.3,0.7,143.5c-25.7,1.6-15.1-21.5-25.7-11.4c-11.1,10.4-11,13.8-48.9,13.8
c-49.8,0-54.6-69.1-51.1-87.6c3.4-17.8,15.5-231.8,13.3-241.5c-5.1-23.6-8.3-123.4,46.5-115.6c28.4,4.1,43.1,25.7,52.3,30.2
C1036.9,1432.7,1050,1425.9,1046.7,1434.9z"/>
<path id="des-i" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M817.9,1572.5c0.3,2.4,0.1,22.8,0,27.8
c-0.7,25.8-2.3,72.9-2.8,88.9c-0.1,2.3-5.8,67-7.4,79.2c-1.6,12.3,0.2,35.7,0.1,40.2c-0.2,3.7,4,3.9,11.8,5.2
c6.5,1.2,7.2-6.5,10.8-4.5c-0.6,4.5,1.9,28.7-1.5,29.7c-24-3.7-65.2,1.1-68.1,0.3c-1.1-1.1-2.3-24.7-1.6-29.5c3.8-4,4.4,3.8,10,3.1
c7.5-1.5,9.9,2.6,10.5-3.1c1.7-16.7,1.6-39.9,3-56.7c1.3-15.8,8.2-142.2,8.6-152.5c0.3-8.9-0.8-28.6-0.7-38.5
c0.2-17.2-7.2-133.3-7.9-140.6c-1.5-15.6-0.4-48.9-0.6-50.6c0,0-3.6-2.4-7.8-4.3c-8.2-2.5-9.9,6.2-13.7,2.8
c1.8-4-1.5-31.6,2.3-32.2c5-0.3,25.8,5.7,40.6,12.4c11.7,5.3,7.9,3.6,20.7,8c2.3,1.1,10.8,5.4,10.2,7.8c-3.1,12-3.9,26.9-5.7,26.1
c-4.7-1.5,2.6-2.4-8.7-8.5l-12.4-4.7C807.4,1380.6,816.8,1563.2,817.9,1572.5z"/>
<path id="des-s" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M633.7,1593.6c-5-10.8-42.2-23.9-45.7-27
c-14.3-12.7-29.1-28.2-32.3-46.9c-1.4-8.5-7.9-85.2-7-105.4c0.1-1.3,1.3-124.5,51.9-125c49.5-0.5,61.9,46.1,63.5,61
c1.5,19,8.6,87.4,8.8,106.4c0.1,16.9,6,10.7,6,22.1c-1.2,3.5-38.2,3.4-41.8-0.1c-1.7-11.3,8.9,0,8.9-20.4
c0-13.6-2.9-101.1-3.5-114.7c-0.5-11.1-17.7-32-24.8-33.3c-8.3-1.6-26.3-0.1-31.1,19.5c-4.1,11.5-8.4,78.9-8.4,84.3
c-0.3,31.9,1.6,97,9.2,111.5c6.7,28.1,48.7,28.7,64.6,52.8c5.9,7.7,15.8,88.4,15.4,114.2c-0.2,6.6,10.8,148.7-51.6,150
s-87-72-86.6-96.3c0.3-18.6-2.2-53.2,1.9-76.3c3.9-21.7,29.1-40.7,44-40.7c6.2,0,7.5-5.3,17-12c3.1-2.2,14.3,65.5,10.7,65.2
c-12.7-1.2-7.2-7.3-19.4-14.8c-8.7-5.3-17-8.3-20.8-0.1c-6.4,14.1-3.9,36.3-3.9,52.7c0,13.2,23.6,88.9,51.6,88.3
s35.4-54.3,34.9-90.8S637,1600.8,633.7,1593.6z"/>
<path id="des-e" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M390.9,1837.5c-14.1,0.2-47-1.7-63.2-2.6
c0,0-2.3-8.4,1.4-24.7c7.6,2.2,6.5-0.6,6.6-12.9c0.8-11.5,1.1-87.4,2.2-122.6c0.2-5.8,0.6-24.6,0.5-26.3c0-1.3,1.2-60.1,2.8-65.7
c1.6-5.7-0.1-91.5-1.9-103.6c-0.6-3.9-5.9-97.2-5.6-105.1c0.4-12.5-1.9-96.5-1.2-108.9c1-29.3,3.4-26.6-8.4-27
c-0.9-13.7-1.4-31.6-0.8-31.6c18.3,0,60.6,21.5,76.7,27.6c52.2,15.8,50.4,10.3,59.1,10.9c3.1,0.6-6.7,49.1-9.6,48.6
c-6.4-0.7,2.6-13.5-15-21.1c-20.2-8.6-53.9-24.3-73-28.1c-2.5-0.3,1.9,105.3,2.5,116.2c1,21.2,0.3,48.6,2.4,69.8
c1.2,12.5,3.6,60.7,4.4,72c1.1,16.5-1.6,70.6-1.2,74.8c1.4,16.9-0.5,72.7,0.6,73.2c4.5,1.2,24.5,0.8,28.8,0.9
c28.8,1.2,26.8-7.5,35-7.8c1.1,0.4,1.6,39.9,0.4,39.9c-5.9,0-2.6-8.6-64.7-8.2c-1.8,0-1.2,136.5,2.2,137.5
c74.2,3.4,61.5-11.1,70.8-11.2c5.1-0.7,3.4,33.7,2.8,34C436.5,1837.5,404.6,1837.5,390.9,1837.5z"/>
<path id="des-d" style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFB400;" d="M126.1,1143.7c14.1,4,43.3,12.5,57.2,17
c16.3,5.2,31.2,20.7,38.8,36.3c7.6,15.5,11.1,31.5,14.7,48c3.3,15.3,14.6,210.1,11.9,231.1c-2.2,16.8-6.5,184.4-7.3,203.3
c-1.1,27.2-12.2,123.8-42.6,142.9c-32.2,20.2-98.8,6.3-118.7,11.6c0,0-0.7-8.5,0-23.9c0.9-6.9,17.3,11.1,17.4-17.1
c0.2-53.8-6.2-295.7-6.2-299.6c0-114,3-286.6,2.9-296.1c1.5-45.9-15.1-24.4-16.5-32.9c-0.5-2.8,0.2-27.7,3.5-27.9
C83.3,1136.4,121.2,1142.4,126.1,1143.7z M201.1,1210.7c-5.6-16.7-17.2-23.3-33.5-28.2c-15.9-4.6-51.2-15-50.7-11.2
c0.8,6.9,0.7,61.5,0.9,71.1c0.3,16.2,2.1,171.9,2.8,182.6c1.2,18.9,0.4,132.4,1.8,151.4c0.4,5.3-1.5,123.7-1.8,126.3
c-1.3,16.4,0.9,102.1,0.9,102.1c21.4,6.4,21.4,6.4,43.7-2.8s37.6-42.3,39.4-55c2.7-19.4,10.7-255.5,10.7-265.3
C215.3,1469.3,208.4,1232.4,201.1,1210.7z"/>
<path id="bk-n" style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M1650.6,2036.7c-8.8,0.1-8.7-1.3-10-7
c-1.5-6.9-8.3-9.3-12.2-14.3c-5.4-6.8-11.8-13-18-19.1c-3.3-3.2-5.2-2.5-6.2,2c-3.4,14.9-10.6,28.4-16.3,42.4c-2,4.9-7,6.8-10.7,5
c-4.4-2.1-5.8-6.5-3.5-11.8c6.6-15.1,12.8-30.3,17.3-46.2c1.4-5,2-9.8,2.5-14.8c0.5-5,1.8-9.9,6.8-11.3c5.4-1.5,9.7,1.5,12.7,6.2
c8.6,13.5,21.8,22.8,31.3,35.6c4.9,6.7,8,6,11-2.2c4.5-12.2,13.7-21.5,19-33.1c1.7-3.8,0.6-5.4,9.8-3.2c6,2.7,3.2,4.6,3.3,7.7
c0,2.9-1.5,5.1-3.2,7.3c-8.6,11.1-13.2,24.2-19,36.8c-1.4,2.9-0.8,5.9-1,8.9C1664.2,2036.1,1664.2,2036.1,1650.6,2036.7z"/>
<path id="bk-k" style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M1120.3,1996.2c11.6,13,26.9,20.3,40.8,29.6
c9.6,5.8,9.6,5.8,3.6,13.5c-7,9-7,9-14.1,3.7c-0.9-0.4-1.9-0.8-2.6-1.5c-10.4-10.2-24.2-15.8-35.1-25.2c-1.8-1.5-3.7-3-5.8-4.1
c-8.3-4.5-11.4-3.2-13.2,6.3c-1.2,6.1-3.5,11.6-6.1,17.1c-3.4,8.2-4.4,10.6-12.8,6.9c-10.9-4.6-8.9-4.9-3.6-14.1
c6.6-11.8,9.8-24.9,15.9-36.9c5.5-10.9,13.1-21.3,13.4-34.5c0.1-3.9,1.7-7.1,10.9-3.9c8.8,3.1,7,5.6,5.3,9.3c-1.7,3.7-4,7-5.9,10.6
c-1.1,2.1-2,4.8,0.2,6.5c1.7,1.4,4,0.2,5.5-1c8-6.4,17.8-9.6,26.3-15.1c3.4-2.2,6.4-4.5,10,0.1c7.4,8.7,2.1,7.7-0.6,10.2
c-3.2,7.1-10.7,8.1-16.2,11.3C1130.6,1988.3,1125.3,1991.7,1120.3,1996.2z"/>
<path id="bk-o-2" style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M1001.8,2002c1,8.2-3.7,14.4-8.9,21
c-10.5,13.2-24.9,20.4-40.6,18.3c-16-2.1-31.2-8.7-39.7-25.5c-5.6-11.1-4.3-20.8,1-30.6c5.1-9.6,13-16.1,24-18.3
c4-0.8,11.6-1.4,14-4.6c0,0,3.8-4.4,8.3-2.8c5.4,1.9,11.1,3,16.5,4.8C992.4,1969.6,1002.8,1985,1001.8,2002z M936,1981
c-10.3,4.6-14.5,18.7-7.3,27.4c5,6,11,11.5,19.3,14c9.8,2.9,19,0.7,27.3-3.4c7-3.5,12.6-9.3,12-18.4c-0.7-9.7-7.5-18.3-15.8-21.4
C961.4,1973.9,940.9,1978.8,936,1981z"/>
<path id="bk-o-1" style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M728.3,2009.1c1.7-16.7,6.8-31.7,20.8-42.5
c6.4-4.9,13-7.6,21.3-7.1c11.3,0.7,22.2,1.6,30.8,10.3c6,6,9.4,13,9.6,21.6c0.3,13.4-3.2,25.4-13.1,34.9
c-5.6,5.4-9.8,12.6-19.2,13.6c-11,1.2-21-0.4-31.2-4.8c-7.9-3.4-11.9-10.5-18.2-15.4C725.6,2016.7,728.4,2012.7,728.3,2009.1z
M744.8,2002.9c-0.9,3.7,1.1,7.3,4.5,10.6c4.7,4.7,10.7,7.6,16.8,8.7c4.7,0.8,10,3.8,14.7,0.9c9.2-5.8,15.6-14.4,15.9-25.4
c0.2-8.2-3.6-16.4-11.2-20.2C768.3,1969,746.1,1982.6,744.8,2002.9z"/>
<path id="bk-r" style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M627.9,1978.4c1.8,6.7-6,8.4-9.6,12.8
c-1,1.2-2.9,1.6-4.4,2.4c-3.1,1.8-9,1.1-9.3,5c-0.3,4.5,5.5,6.5,8.9,8.2c14.3,7,31,18.4,45.1,25.5c3.9,2-4.8,15.9-10,14.7
c-4.5-1-9.5-2.3-13.1-4.9c-15-11-32.4-17.8-48.3-27.1c-2.3-1.3-5.1-1.7-7.7-2.4c-7.6-1.8-9.3-1.2-9.6,6.8
c-0.3,6.7-4.7,9.2-8.7,12.7c-2.3,2-4.3,0.9-6.5,0c-2.8-1.2-2.9-3.4-2.3-5.9c3.5-14.7,1.2-30.1,4.9-44.8c0.9-3.7,0.9-7-0.7-10.7
c-1.8-4.1-2.3-8.5,0.6-12.6c1.5-2,2.9-3.3,5.8-2.7c10.3,1.9,20.5,2.4,30.9,0.4c6.3-1.2,12.7,0.9,19,1.8
C620.4,1958.6,627.7,1969,627.9,1978.4z M591.7,1970.9c-3.3,0.4-6.6,0.8-10,1.3c-1.3,0.2-5.8-1.4-7-0.9c-3.8,1.4-2.8,21.5,2.7,19.7
c13.4-4.2,7.2-1.9,22.7-7.2c3.5-1.2,8.1-2.8,8.1-6.4c0-4.1-5.1-4.5-8.5-5.5C597.1,1971.2,594.4,1971.2,591.7,1970.9z"/>
<path id="bk-b" style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M420.5,1946.9c7.5,2.3,14.3,0.5,21,1.7
c11,2,18.7,10.7,19.1,21.6c0.1,3.6-1.9,6.5-3.4,9.4c-4.5,8.6-4.2,8.1,2.2,14.6c10,10.2,13.3,20.8,4.2,34.3
c-2.6,3.8-6.5,5.8-10.5,7.6c-12.6,5.6-25.4,10.8-39.2,12.9c-4.9,0.8-11.8,1.7-16.3,4.6c-3.9,2.5-7,2.5-11.7,2.3
c-6.8-0.4-8.5-3.3-6.9-9.9c3.4-14.1,8.6-27.5,15.3-40.3c4.9-9.5,5.7-20,9-29.9c1.9-5.7,4.2-10.8,0.5-17.2c-2.2-3.8,2.2-7.5,6-9
C413.2,1948,417.3,1947.6,420.5,1946.9z M436.8,2005.3c-5,0.2-24.5,3.4-28.8,5.7c-3.6,1.9-12.3,25-6.7,25c2.7,0,14.3-2.4,16.9-3.2
c9.3-2.9,18.6-6.2,27.9-9.2c4.1-1.3,5.8-3.6,5.9-8.1c0.1-5-2.2-7.5-6.2-9.3C443,2004.9,439.9,2005.1,436.8,2005.3z M444.1,1972.4
c-1.7-2.6-1.7-6.9-5.8-7.4c-4.7-0.6-8.9-2.8-13.8-2.8c-4,0-6.1,1.5-7.6,5.1c-1.9,4.8-2,9.9-3,14.8c-2.3,11.2-4.7,11.2,6.1,8.7
c8.1-1.9,15.7-7.9,21.4-13C443,1976.5,444.2,1974.8,444.1,1972.4z"/>
<path id="ny-n" style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M1893.7,1997.2c-3,9.2-11.7,27.1-17.1,35.1
c-3,4.4-5.9,8.9-7.1,14.4c-0.6,2.8-1.7,7-6.1,7.1l-11.3-7.1c-1.7-6.7,2.9-14,6.8-18c10-10.3,13.7-23.3,17.3-36.3
c2.3-8.2,7.8-14.1,12.4-20.6c4.5-6.3,4.5-6.3,15.2,3.3c2.5,3.1,18.4,33.6,22.1,41.9l9.4-14.6c0,0,12.8-27.5,15.9-36.1
c2.9-9.7,2.9-9.7,14.6-4.7c5.8,3.2,5.8,3.2,2.8,9.4c-3.5,7.4-6,15.1-9.8,21.9c-3.8,7-7,14.4-11,21.4c-3.9,6.8-13.8,32.3-14.2,35.7
c-2.2,8,0.1,8-9.1,8c-7.9,0.1-6.8-2.8-7.8-6.5C1912.9,2037.1,1894.9,1997.5,1893.7,1997.2z"/>
<path id="us-a" style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M2774.7,1972.3c0.7,7.8,1.3,16.8,1.1,26
c-0.1,3.4,1.8,4.8,4.7,5.3c10.5,1.7,10.5,2.7,0.7,10.6c-5.2,4.2,0.8,11.3,1.9,16.4c2.8,13.2,8,19.5-6.7,19.5
c-10.7,0-9.2-3.6-9.9-9.2c-0.9-6.8-0.6-21.9-2.1-28.4c-11.1-3.6-10.1-3.1-16.2-3.7c-4.4-0.4-11.8-4.3-16.2-4.3
c0,0-13.2,14.5-17.5,25.4c-0.9,2.2-0.9,6.9-2.2,8.8c-1.6,2.3-20.9,2.3-23.4-1.5c-2.5-3.9,4.6-15.1,6.3-16.2
c3.6-6.6,35.2-41.2,39.2-45c6.2-5.9,13.2-13.3,17.4-20.8c5.4-9.5,4.2-7,10.4-6.8c6.4,0,7.5,0.4,9.8,8.3
C2773.9,1961.5,2774.1,1963.2,2774.7,1972.3z M2735.7,1993.9c15.2,2.8,29.7,6.5,29.7,6.5s1.9-20.8-5.8-35.7
C2754.6,1968.6,2739.4,1987.9,2735.7,1993.9z"/>
<path id="us-s" style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M2537.2,2045.4c-3.7-0.7-9.4-1.5-14.9-2.8
c-6.5-1.5-10.1-3-16.6-1.8c-6.3,0.7-17-3.8-16.2-14.9c0.9-12.5,2.2-6.7,6-7.4c9.5,1.7,31.5,13.4,58,5.1c3.6-1.1,3.9-3.6,1.3-6.1
c-2.7-2.5-5.5-4.9-8.9-6.5c-3.9-1.9-7.3-4.8-10.9-7c-10.3-6.1-10-14.9-8-24.5c0.9-4.5,11.1-13,17.3-13.9s12.3-2.8,37.7-3.9
c1.2,0,4.8,9,3.9,9.7c-4.6,3.5-7.8,5.8-13.8,5.5c-6.7-0.3-18.5,0.7-23.7,5c-2.5,2.1,1.7,10.3,15.7,14.4
c10.1,2.3,16.9,16.3,16.3,24.5c-0.6,8-6,17.6-14.4,19.8C2557.1,2042.8,2548.3,2045.1,2537.2,2045.4z"/>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M2325.7,1966.4c3-13.2,3.5-12.6,16.4-8.9
c4.1,1.2,4.1,1.2,2.3,7.5c-1.5,4.9-4.4,19.2-6,24c-3.3,9.9-0.3,18.8,4,27.2c4.9,9.3,18.3,8.2,24-1.6c7.1-12.3,10.7-25.9,14.1-39.6
c0.5-2,0.9-3.9,1.3-5.9c2.7-11.7,2.7-11.7,13.5-9.2c10.9,2.5,9.6,3,7.7,12.6c-0.6,3.2-1.2,6.8-2.7,9.4
c-6.9,12.4-11.5,25.7-16.9,38.7c-5.6,13.5-23.8,24.5-37.4,20.7c-7.7-2.1-13.1-8.8-19-14.3C2312.5,2013.3,2323.6,1976,2325.7,1966.4
z"/>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M2032.6,2061c-6.7,0.1-9.2-2.4-6.9-8.4
c4.5-11.6,7.2-23.6,11.6-35.2c3.9-10.3,2.6-14.9-6.2-21.7c-6.9-5.3-13.7-10.9-20.2-16.7c-5.8-5.2-5.7-9.9-0.6-14.4
c5.3-4.7,11.6-4.5,15.5,1.4c2.8,4.2,6.7,6.6,10.7,8.9c3.6,2,7.3,4,9.6,7.2c3.5,4.9,6.5,3.4,10.1,0.6c5.4-4.2,7.5-11,12.2-15.8
c3.7-3.8,7.6-6.7,12.8-7.5c4.4-0.6,8.3,1.2,10,5.3c1.5,3.8-1.5,6.4-4.3,8.3c-11.8,8.4-19.4,20.2-25.7,32.9
c-6.2,12.3-10.5,25.1-12.2,38.9C2047.7,2055.3,2041.3,2060.8,2032.6,2061z"/>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M1414.7,1960.6c3,0.1,5.4,1,7.4,3.2
c6.5,7.4,15.6,10.2,24.1,14.1c2,0.9,8.5,7.2,9.9,6c6.5-5.3,22.6-16,28.5-22.3c4.5-4.8,8.6-4.9,12.1-1.3c3.1,3.2,2.2,9.6-2.9,13.1
c-7.8,5.4-15,11.4-22,17.6c-13.1,11.5-23.8,24.5-25.6,43.1c-0.7,7.1-5.1,13.1-12.5,15.9c-3.1,1.2-6,1.5-8.4-1.1
c-2.1-2.3-3.7-4.9-2.6-8.2c0.1-0.3,0.2-0.7,0.3-1c5.4-10.2,7-22.1,13.8-31.9c5.8-8.3,5.5-11.4-3.2-16c-8.4-4.4-15.4-10.8-23.8-15.1
c-4.1-2.1-7.6-4.7-5.8-10.3C1405.8,1961.1,1410.6,1961.5,1414.7,1960.6z"/>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M1283.1,1969.3c-4.7,8-3.6,19.3-7.5,29.5
c-1.9,5-3.1,10.3-4.3,15.6c-0.7,3.1,0.5,5.6,4,5.7c13.4,0.4,26,7.6,39.8,4.8c1-0.2,2-0.1,3,0c4.5,0.7,9.1,5.1,8.6,8.2
c-0.9,5.9-6.7,11.4-11.6,10.8c-20.6-2.6-48.1-5-68.9-5.8c-4.3-0.3-8.9-0.7-0.4-12.7c3-4,10.7-21.9,12.7-26.8
c1.8-4.7,4.2-9.2,4.8-14.3c0.4-3.7,1-6.9-0.2-10.8c2.5-14.1,1.5-15.6,14-13.8C1286,1961.3,1283.8,1962.9,1283.1,1969.3z"/>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M1717.4,2048.4c-2.5,11-12.2,22.2-16.2,19.8
c-2.6-2.8,3.4-5.5,4.8-14.3c3.2-20.4-9.2-12.8-13.5-15.6c-3.9-2.4-6.2-5.7-4.9-10.4c1.3-4.3,4.6-6.9,8.9-7.8c4.5-1,9,1.2,11.2,5.2
C1710.8,2035.1,1719.5,2031.9,1717.4,2048.4z"/>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M2411.4,2040.5c-5.5,2.7-7.9,2.1-9.4,1.4c-3.4-1.5-5.7-4-5.1-8
c0.6-3.7,3.1-6.1,6.5-7.3c3.6-1.3,7.5,0,9.7,3.1C2417.9,2036.4,2415,2037.2,2411.4,2040.5z"/>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M2601.6,2031c2,5.4,1.1,7.5,0.2,8.9c-2,3-4.7,4.7-8.5,3.6
c-3.5-1-5.6-3.6-6.4-6.9c-0.8-3.4,1-6.9,4.3-8.5C2598.4,2024.5,2598.8,2027.2,2601.6,2031z"/>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#D8D8D8;" d="M2807.2,2031.4c3.7,2.6,4,4.4,3.9,5.7
c-0.2,2.8-1.5,5.2-4.8,6.3c-3.1,1-5.7,0.2-7.7-1.6c-2-1.9-2.2-5-0.4-7.5C2802,2028.6,2803.5,2030.2,2807.2,2031.4z"/>
<g id="ampersand">
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#F9F9F9;" d="M1445.7,1604.9c0,0-21.7-10.2-24-37.3
c-2.7-31.7,23.3-58.3,62-58.3c20.6,0,39.1,15.8,41.3,26.3c2,9.7-1.2,19.5-2.7,26.3c-12,4.8-32.6,0.5-34-5.3
c-1.1-4.3,19.7-11.1,16-22.3c-2.5-7.8-19-11.3-33.3-9.3c-15.5,2.2-43.3,57.9,1.6,70.6c13.4,3.8,19.4,3.7,19,13
c-0.3,8.4-6.4,10.4-18.6,10.4s-56.9,26.9-66,44c-32,60.3-27,149.2,96.2,146.4c85-2,69.9-151.8,69.1-155.4
c-0.8-1.9-41-0.7-65.6,10.3c-6.1,2.7-24.4,23.7-13.7,39.7c6.7,10,21.9,8.2,25.7,7c14.3-4.3,2.9-21.7,9.3-22c2.3-0.1,22.3,14,6,29
c-9.1,8.4-36.2,9.5-45.5,0.8c-26.2-24.3-6.2-85.5,54.4-86.4c7.8-0.1,31.8-2.6,39.4-1.7c15.6,1.8,58,3.3,64.3-24
s-6.8-42.9-15.7-46.7c-12.8-6.3-28.4-4.6-37,0.3c-25.1,14.4-15.4,44,9.7,34.7c5.7-24.7,39,9.7-6.3,16c-8,1.1-22-2.3-26.8-9.8
c-5.5-6.3-8.3-17.6-6.7-24.5c3.7-16.2,15.6-24.2,32.4-29.4c16.4-5.1,32.9-4.3,46.1,3.9c32.6,19.5,30.7,76.9,2.6,92.5
c-15.7,8.7-20.1,13.6-45.7,12.3c-2,1,1.6,25,2,55.3c0.2,16.3,16.6,99.1-88.1,118.9c-48.7,9.2-111.2,5.3-135.3-44.7
C1312.4,1649.4,1445.7,1604.9,1445.7,1604.9z"/>
</g>
</g>
</svg>
<figcaption>
An embedded SVG version of the final(ish) logo. SVG is amazing
</figcation>
</figure>
<p>
I’m looking forward to working with the logo more. Keeping
it vector was important for me because I want to display it using SVG.
I also think there will be some interesting things to do with it using
CSS transitions and animations further down the road.
</p>
<p>
<i>Thanks for reading</i>
</p>
</code></pre>
How I Start2014-07-10T00:00:00Zhttps://tylergaw.com/blog/how-i-start/<pre><code> <p class="entry-intro">
I'm beginning a design iteration of this site. I've started
freelancing full time and I want the site to display more of my work
and explain it in finer detail to give potential clients and collaborators
a better idea of what I do and how I go about it.
I love this early part of the process where everything is on the table,
and up in the air, and I can get arty-farty and a little weird. Hold
on tight, it's a bumpy ride.
</p>
<h2>Nonsense</h2>
<p>
This part of the process has no structure. I just like to
make things as messy as possible. I immerse myself in the types of
ideas I want to convey and the type of designer I want to be. I open
100 browser tabs across 10 different windows. I keep multiple sketch books in front
of me, a No. 2 pencil and busted, cheap sharpener. I drag out every
micron pen I have, even though I'll probably not use any of them.
I haven't yet, but I'll probably get out the paint and brushes to
make a really big mess.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-starting-sketches.jpg" alt="">
<figcaption>
Bad handwriting. Worse block lettering.
</figcation>
</figure>
<p>
In my sketchbooks I don't really sketch interface wireframes, but I do write lots of words
and phrases. Some of them are ideas I want to convey. Some of them
are just nonsense that I think is funny. "Hollerin'" "Sweatin'" "Braggin'"
"BLACK" "MAKE IT EXTRA LARGE"
"NO STRAIGHT LINES" "ALL RIGHT ANGLES" "Don't limit" "Do limit"
and plenty more non-sequiturs, contradictions, and fibs
that will have me scatching my head when I look back at them at some
point in the future. I also draw plenty of skulls on fire and anarchy signs
and all other types of bad drawings that look awesome on the back
of 8th grader's notebooks.
</p>
<h2>Folks</h2>
<p>
I listen to artists and designers talk about making cool art and design.
I have seen all of these multiple times, they never get old. I love them and
they get me excited to design:
</p>
<ul>
<li><a href="https://vimeo.com/39441590">Portland/CreativeMornings - Draplin</a></li>
<li><a href="http://www.aiga.org/video-HHH-2013-draplin/">More Draplin - Tall Tales from a Large man</a></li>
<li><a href="https://vimeo.com/20456428">Even more Draplin</a></li>
<li><a href="https://vimeo.com/20777627">Draplin, shit! He's the best</a></li>
<li><a href="http://hillmancurtis.com/artist-series/lawrence-weiner/">Design Matters: Lawrence Weiner</a></li>
<li><a href="http://buy.beautyisembarrassing.com/">Beauty is Embarrassing - Wayne White</a></li>
<li><a href="https://www.youtube.com/watch?v=JyRAHKTy6hI">Beautiful Losers - Buncha great folks</a></li>
</ul>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-starting-art.png" alt="">
<figcaption>
Art, beards, grumpy men
</figcation>
</figure>
<p>
With all of those it's not even really about the crud they make. I
don't sit and stare at the work they produce. I get inspired and excited
hearing them talk about how much they love what they do, what it
means to them, and what they think it means to other people.
</p>
<h2>Letters and Such</h2>
<p>
This time around I wrote names of different fonts that
I was interested in using or just like the name; "Franklin Gothic Condensed",
"Futura", "Roboto Slab", "Neue Helvetica 55 Roman".
</p>
<p>
I hopped into ole sparky (PS) and threw together some of the type
to see how it would look in the colors I have in mind. Again, no structure,
just bashing on the keyboard and tweaking some settings here and there to
see how things might work later.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-starting-type.png" alt="">
<figcaption>
Playing around with some type treatments
</figcation>
</figure>
<h2>Music</h2>
<p>
Music is always a huge part of my process. A <a href="http://www.rollingstone.com/music/videos/interpol-comes-out-of-the-black-in-all-the-rage-back-home-video-20140709">new Interpol song</a> was
released yesterday. It's awesome. I've listened to it about 999 times so far.
It's having an impact on my brain and designs.
</p>
<iframe width="100%" height="325" src="//www.youtube.com/embed/-u6DvRyyKGU" frameborder="0" allowfullscreen></iframe>
<p>
Here's some other crud I have on repeat that will also have an impact on the design:
</p>
<ul>
<li><a href="http://www.rdio.com/artist/Interpol/album/Our_Love_To_Admire/">Our Love to Admire - Interpol</a></li>
<li><a href="https://twitter.com/ANTEMASQUE">ANTEMASQUE - ANTEMASQUE</a></li>
<li><a href="http://www.rdio.com/artist/AFI/album/Burials_(Digital_Booklet_Version)/">Burials - AFI</a></li>
<li><a href="http://www.rdio.com/artist/Every_Time_I_Die/album/From_Parts_Unknown/">From Parts Unknown - Every Time I Die</a></li>
<li><a href="http://www.rdio.com/artist/Lana_Del_Rey/album/Ultraviolence_(Deluxe)/">Ultraviolence - Lana Del Rey</a></li>
<li><a href="http://nadiesound.com/album/cry-is-for-the-flies">Cry Is For The Flies - Le Butcherettes</a></li>
<li><a href="http://www.rdio.com/artist/Mastodon/album/Once_More_%27Round_The_Sun_1/">Once More 'Round The Sun - Mastodon</a></li>
</ul>
<p>
That's pretty heavy list. This design is most likely going
to be dark, loud, bold, and brooding. Simple and clean can take a
long walk off a short pier.
</p>
<h2>6th Grade Art Class</h2>
<p>
Here's something I haven't done for designs in the past, but felt
compelled to for this one. I pulled out a stack of old magazines and
have been tearing through them cutting things out to make crap little
collages. I'm not putting much thought into the pieces. These aren't
"mood boards" or whatever. Just flipping the pages and if something
catches my eye I hack it out and throw it on some paper. A piece of
text that's bright. Some dumb phrase. Creepy dude eyes. Anything that's
interesting.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-starting-collage.png" alt="">
<figcaption>
Cuttin’ out Hetfield's eyeballs for shits and giggles
</figcation>
</figure>
<p>
These collages aren't going to be part of the design, they're
just a way for me to get moving. Computers are too damn slow. I
need to use my hands to move fast and toss shit around and scribble and just
be a wreck in general. It helps me get my entire self into it.
</p>
<h2>These Silly Paragraphs</h2>
<p>
This post is part of it too. This isn't meant to be some how-to or
"10 Clever Ways to Inspire Your Next Responsive E-Commerce Design" post.
Like the rest of the process, it's messy. It's unedited and maybe unfinished.
It's also a chance to stop and think about what I'm doing. Or to put
it another way; procrastinate.
</p>
<p>
What about you, how do you start?
</p>
<p>
<i>Thanks for reading</i>
</p>
</code></pre>
CSS True Titles2014-02-25T00:00:00Zhttps://tylergaw.com/blog/css-true-titles/<pre><code> <figure>
<a href="http://lab.tylergaw.com/css-true-titles/">
<img src="https://tylergaw.com/articles/assets/post-image-true-titles-intro.jpg"
alt="Image of the title cards from the True Titles demo">
</a>
</figure>
<p class="entry-intro">
The <a href="https://youtu.be/ZRPpCqXYoos">opening titles</a> to the
show True Detective are incredible. CSS contains the building
blocks necessary to create a similar style title sequence. I tried my
hand it and came up with fun results. Here's the
<a href="https://lab.tylergaw.com/css-true-titles/">demo</a> and the
<a href="https://github.com/tylergaw/css-true-titles">source code</a>.
</p>
<p>
The first thing I have to do is issue the disclaimer. This is an
experimental project. Things will most likely go wrong when you view
it. I'm pushing on the browser pretty hard to get it to do things it
maybe wasn't intended to. Also, due to limited suppport
of <a href="https://www.w3.org/TR/css-masking-1">CSS masking</a>
you'll only see the full effect in Chrome, Opera, and possibly Mobile Safari.
The animations will work in other browsers, but things will look strange.
</p>
<h2>The Masking</h2>
<p>
There's a whole lot going on in each shot of the TD titles. You should
read the write up about it on <a href="https://www.artofthetitle.com/title/true-detective/">Art of the Title</a>.
The main effect that I wanted to recreate was the use of human silhouettes
to mask shots. They use video clips for the masks and the content being
masked, but I stuck with still images to keep things a little easier.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-true-titles-mask-example.jpg"
alt="">
<figcaption>
An example of using a human to mask footage in the True Detective titles
</figcaption>
</figure>
<p>
I'm accomplishing masking using the CSS <code>mask</code>
property. As I mentioned, browser support for it is currently
limited. I'm going to break down the first title
card to explain how the masking is
implemented.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-true-titles-opener-card.jpg"
alt="">
<figcaption>
A still of the first title card.
</figcaption>
</figure>
<p>
Each title card is an <code>li</code> element. They contain base
styles like width, height, position, etc. Within each <code>li</code>
is markup specific to that card. Below is the markup for the elements
that make up the opening card.
</p>
<pre><code class="language-markup">&lt;div class="composition animated"&gt;
</code></pre>
<p><div class="town animated"></div>
<div class="human"></div>
</div>
<h1 class="copy animated">
<b class="role">This is a</b> CSS Experiment
</h1></p>
<p>
The containing <code>div</code> element with the class of "composition"
houses all the images. I'm using a containing element here so I can move
and scale the "human" and the "town" together, while still being able
to move and scale each independently. I'm also applying the mask
to the composition element. The "human" and "town" elements have background
images applied to them.
</p>
<figure>
<img src="https://lab.tylergaw.com/css-true-titles/img/opener-human.png" alt="" />
<figcaption>
The partially transparent human image, which doubles as the mask
for the opening card.
</figcaption>
</figure>
<p>
All of the images are from the <a href="https://flickr.com/commons">Flickr Commons</a>.
I cropped, clipped, and pushed their pixels around in Photoshop until
they felt right. Some of them have kind of ratty clipping paths, but
you can't really seen them with everything moving. I was able to bring
the file size down some because of that.
</p>
<figure>
<img src="https://lab.tylergaw.com/css-true-titles/img/opener-town.png" alt="" />
<figcaption>
The background image for the town element.
</figcaption>
</figure>
<p>
I want to be able to move the "town" element from right to left and
left to right. If I applied the mask to that element, the mask would
move with the town causing it to not line up properly with the human image.
The mask needs to be applied to an element above it, the composition element.
Since CSS masks are only concerned with the opaque and transparent
portions of an image, I can reuse the human image as the mask for the
"composition" element using the CSS below. Areas appearing under
opaque portions will be visible, those under transparent portions will
be invisible.
</p>
<pre><code class="language-css">.opener .composition {
...
-webkit-mask: url("opener-human.png") top left/cover no-repeat;
mask: url("opener-human.png") top left/cover no-repeat;
}</code></pre>
<p>
The shorthand <code>mask</code> has similar properties as <code>background</code>.
URL, position, size, repeat. It was important that all of the images and mask
had the same <code>background-size</code> setting. In this case they
are all set to <code>cover</code>. That prevents the images from becoming
too short or too narrow so that you see the edges of them. I could
have set a fixed width and height on the cards to avoid this
altogether, however it was more fun to embrace the unstable
environment that is a Web browser by letting everything remain fluid.
</p>
<p>
The rest of the title cards follow a similar pattern with adjustments
based on the the number of images that were needed and the animations
that were put into place. You can see the source scss for each
card <a href="https://github.com/tylergaw/css-true-titles/tree/master/css/scss/partials">here</a>.
</p>
<h2>The Animations</h2>
<p>
There are a few different animations happening during the sequence.
The main animation–or timeline–is responsible for cutting from one card to the next
and looping back to the first after the last. Within each card there
are one or more animations that control composition elements and individual
elements.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-true-titles-ani-diagram.jpg" alt="Sketch showing the length of the main timelime is seconds and the length of each sub timeline as a percentage." />
<figcaption>
A diagram showing the main and sub timelines of the sequence.
</figcaption>
</figure>
<h3>The main timeline</h3>
<p>
Once I came up with all the cards I wanted, I tweaked
the total length until the pacing felt about right at 36 seconds. The .54 seconds was
added the to make the sequence length match the length of the audio track.
The track ended up an odd length because I edited it with <a href="http://audacity.sourceforge.net/">Audacity</a>
and I've never been able to tame that beast. Has anyone?
</p>
<p>
As the diagram shows, the length of each title card is a percentage
of the total sequence length. That length is how long that card will
be visible. Figuring out the length for each card took quite a bit
of trial and error to get right. If each card was the same length, coming
up with a percentage for each would be easy: <code>100% / number of cards = length</code>.
That was not the case with this sequence.
There are four title cards and one "bump" card.
The bump card contains no text, it's there as a break and
extra visual information (AKA, it looks cool). Because of that, the
bump needed to be shorter than the title cards. The photography card
doesn't contain as much motion as the other cards and that was making it feel too long.
I stole time from it to give to the last card. I wanted the last card with my Twitter info
to be longer than the others.
</p>
<p>
Each card needs keyframes at multiple positions. I'm going to show
the second title card–the director card–as an example, because they get a little strange.
</p>
<pre><code class="language-css">@keyframes director {
0%, 22.9%, 46%, 100% {
opacity: 0;
transform: translateX(-100%);
}
<p>23%, 45.9% {
opacity: 1;
transform: translateX(0);
}
}</p></code></pre><p></p>
<p>
I'm going to explain this backwards. Starting at 23% of the main
timeline length I want the card to be visible and on the screen. It should stay
visible until 45.9% of the main timeline length.
</p>
<p>
The first set of frames says that the card should be invisible and
off screen from 0% until 22.9% and again from 46% to 100%.
</p>
<p>
I'm using the 0.1% intervals to make sure there is no visible interpolation
of the opacity or transform. I want the transition from one card to
the next to be a quick cut. The opacity is still interpolating from
0 to 1 and transform from -100% to 0, but it's too fast to be seen.
</p>
<p>
I'm using <code>translateX</code> to move the invisible cards
out of the viewport so they don't obsure any elements of the current
visible card. That was happening when I was only using <code>opacity</code>.
</p>
<p>
Again, getting the timing correct on each card was a lot of trial
and error. Making changes a percentage at a time. You can see
the full scss for the main timeline animation
<a href="https://github.com/tylergaw/css-true-titles/blob/master/css/scss/partials/_general.scss#L115">here</a>.
</p>
<h3>A sub timeline example</h3>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-true-titles-director-card.jpg" alt="" />
<figcaption>
A still of the director title card.
</figcaption>
</figure>
<p>
The director card is the most complex card with five separate animations happening
simultaneously. Here's the markup for the card.
</p>
<pre><code class="language-markup"><div class="composition animated">
<div class="street animated"></div>
<div class="trees animated"></div>
<div class="human"></div>
</div>
<h2 class="copy animated">
<b class="role">Directed by</b>
<a href="https://tylergaw.com">Tyler Gaw</a>
</h2></code></pre>
<p>
Anything with the class of "animated"–you guessed it–has an animation
applied to it. The fifth animation is on a pseudo-element. Below is
list of each animated element and description of what is animated.
</p>
<ul>
<li>
<b>composition</b>: Houses all the images. Over the span of 17 seconds
it is slightly scaled, skewed along the X axis, and translated on the X axis.
</li>
<li>
<b>street</b>: A <a href="https://lab.tylergaw.com/css-true-titles/img/director-street.png">masked image</a>.
Its X and Y positions are changed over 18 seconds using <code>translate</code>.
</li>
<li><b>trees</b>: A <a href="https://lab.tylergaw.com/css-true-titles/img/director-trees.png">masked image</a>.
Its X and Y positions are changed over 18 seconds using <code>translate</code>.
</li>
<li>
<b>copy</b>: The card info is slightly scaled and translated
on the X and Y axes over 17 seconds. The copy moves somewhat in
tandem with the composition.
</li>
<li>
<b>pseudo-element</b>: I'm using the <code>::before</code> selector
on the trees element to add an additional subtle detail I called a "blip". It is a
<a href="https://lab.tylergaw.com/css-true-titles/img/director-blip.png">image of fire</a>
that is displayed shortly to give a little more detail to animation.
</li>
</ul>
<p>
A quick note on using <code>translate</code>. Always use it to animate
elements on X and Y axes instead of using positioning properties like
left, right, top, bottom. Also try to use it instead of animating
the <code>background-position</code> property. Using <code>translate</code>
gives much smoother animations and uses less CPU. <a href="https://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/">Paul</a>
explains it better than I can.
</p>
<p>
All of the animations have their <code>animation-direction</code> set
to <code>alternate</code>. I did this so there would be no visible jumps
back to the starting position of the properties. If you watch the
sequence multiple times, you're likely to see the animations running
in different directions.
</p>
<p>
You can see the full source scss for the director card
<a href="https://github.com/tylergaw/css-true-titles/blob/master/css/scss/partials/_card-director.scss">here</a>.
</p>
<h3>Pausing the animations</h3>
<p>
In the markup there is a class of "animated" applied to any animated
element. I'm using that as way to pause and unpause all of the
animations using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/animation-play-state"><code>animation-play-state</code></a>.
</p>
<pre><code class="language-css">.paused .animated,
.paused .title-card .animated {
-webkit-animation-play-state: paused;
animation-play-state: paused;
}</code></pre>
<p>
There is some <a href="https://github.com/tylergaw/css-true-titles/blob/master/js/controls.js">light JavaScript</a> to allow the pause/play
button to add and remove the class of "paused" from the
body element, the CSS above takes care of the rest.
</p>
<h2>This was tough!</h2>
<p>
When I was working on this project, I realized there was a lot going
on to make it happen, however I didn't realize how much I was on
autopilot during the process. Sitting down and trying to explain—in
words—what I did was difficult. If you read this far I hope there's a
useful technique or trick that you picked up along the way. If not,
I hope it was an interesting look into what goes on in my brain when
building this type of thing.
</p>
<p>
<i>Thanks for reading</i>
</p>
2000 Days2014-02-17T00:00:00Zhttps://tylergaw.com/blog/2000-days/<p class="entry-intro">
That’s roughly the number of days I was a member of the team at
<a href="http://arc90.com/">Arc90</a>. Five years, five months, and a couple
weeks. Feb. 14th 2014 was my last day. Leaving was a difficult and
emotional decision, but after months of mulling it over I decided it
was the right one to make.
</p>
<p>Summer 2008. I was living in St. Louis, MO and working as a freelance
designer/developer. For months I’d had a nagging feeling that I couldn’t
articulate. The best I can describe it was a “pull” that was
originating from my gut. The feeling was telling me there was
something out there and I needed to go. The feeling didn’t bother to
let on what was out there, or where “there” was. It wanted somewhere
big though. Somewhere completely different from where I was then or
where I had been before. Los Angeles? Nah, too glossy. New York City?
Rough, the opposite of glossy. I’d only been to New York once before
for a few days years earlier. At the time I described it as “a cool
place to visit, but I couldn’t imagine living there.” The fact that
I didn’t see New York as a place for me became a reason to go. This
move wasn’t about comfort, it was about changing my life. Once I
knew where “there” was I decided quickly, almost cavalierly. When my
lease was up, I’d pack everything I owned into my Dodge Durango and
move to a new city half way across the country.</p>
<p>The freedom that comes with freelancing and working from home was
great, but I needed something new. I needed to be around other
designers and developers. I wanted to share the work I was doing
every day and learn from people that loved the Web like I did.</p>
<p>In my job search for “NYC Web designer”, “Front-end Web NYC”,
“NYC Web PHP”, etc I came across this
<a href="http://blog.arc90.com/2008/07/18/were-looking-for-the-lone-wolf-mcquade-of-php-development/">job post</a>.
PHP was my language of choice in ‘08 and wow, what a great post. No
long list of buzz word requirements, no arbitrary years of experience
necessary. Just three lighthearted, pointed paragraphs. Who was this company?
Who was behind it?</p>
<p>Time has claimed some of the styles, but the Arc90 jobs page from
July ‘08 is still available via the
<a href="https://web.archive.org/web/20080715112955/http://arc90.com/jobs.php">Wayback Machine</a>.
Here's a small portion:</p>
<blockquote>
<p>
“...if you appreciate (and violently defend) standards-based
technologies like <strong>XHTML</strong> and <strong>CSS</strong>...”
</p>
</blockquote>
<p>It's easy to take for granted in 2014, but in 2008 an agency
saying they wanted people that cared about Web Standards was
not the norm. That description was speaking to me.
It was saying all the things I wanted
a company to say. That sealed the deal. Arc90 was where I needed to be. I
hadn’t applied to anywhere else, and I wouldn’t. I wasn’t sure
that my PHP skills were good enough to get hired for the developer
position, but I was going for it anyway. I wanted to work for this company.
I <em>had</em> to work for this company.</p>
<p>When I got to New York on August 27th 2008 I sprinted to the Arc90
office. I didn’t start work for a few more days, but I couldn’t wait
to see the place and meet the people I’d only seen in
<a href="http://www.flickr.com/search/?q=arc90">photos</a> up to
that point. I was here. This place was real. I was a part of it.</p>
<p>Five years didn’t just happen. Working for a company is a
relationship. Relationships are hard and require work to maintain.
Over the years there’ve been ups and downs, too much work and too
little, I saw good people leave and great people join. There were
times I thought I was ready to throw in the towel, but didn’t. I
knew how special my relationship with Arc90 and the humans there was
so I stuck with it.</p>
<p>I wore a lot of hats. I designed a boatload of interfaces for
insurance software. Designed and built sites and an app for a
<a href="http://en.wikipedia.org/wiki/GradeGuru">failed education startup</a>.
Designed and helped build <a href="http://donahueapp.com/">an app</a>
to experience conference talks in a novel way. I also made a couple
brief design visits to <a href="http://www.kindlingapp.com/">an app</a>
that I saw grow from weekend project to successful business.</p>
<p>The best work I’ve ever done I did for
<a href="http://readability.com/">Readability</a>. On Readability I
was able to stretch my abilities in ways that I never imagined I
would. My design and development skills were challenged and
strengthened like never before. As the Product Lead I had the
opportunity to shape the vision and direction of a product that I
used and continue to use every day. I was able to try my hand at
leading a team of incredible people to build incredible software.
Working to make the lives of the people I worked with more enjoyable
has been by far the most challenging thing I’ve done in my career.</p>
<p>I’m forever grateful for Arc90, the people there today, and all the
people that have come and gone. The opportunities it’s given me have altered
my life for the better. At Arc I was able to be a designer, developer,
writer, customer support, marketer, advocate, lobbyist, conflict
mediator, and–when needed–ad hoc therapist.</p>
<p>So why am I leaving now? It’s back. That feeling. That pull from my
gut that I felt back in St. Louis in ‘08. It’s telling me it’s time
to go again. Telling me something is out there. Telling me I need to
go find it. Like before, it’s not doing me any favors of being
specific about what “it” is. It’s not looking for a new “there” this
time. It’s just looking for a “what”. It wants change. It didn’t
let me down last time, so I’m not inclined to ignore it this time around.</p>
<p>I’m not sure what’s next, and that feels OK. I don’t have a new job
lined up. I don’t have any interviews or prospects. I’m on the hunt.
I’m going to spend my next handful of a days looking for it.
Meeting and talking with passionate people about what they’re working
on and what’s out there. I’m going to spend my next few days searching
for how I’ll spend my next 2000.</p>
<p>The world is bigger than ever right now. I can't wait to see what I find.</p>
<p><i>Thanks for reading</i></p>
Papersaver2014-02-03T00:00:00Zhttps://tylergaw.com/blog/papersaver/<pre><code> <figure>
<a href="http://lab.tylergaw.com/papers">
<img src="https://tylergaw.com/articles/assets/post-image-papersaver-intro.png"
alt="Papersaver text on a crappy Photoshopped background">
</a>
</figure>
<p class="entry-intro">
<a href="http://www.fiftythree.com/paper">Paper</a> is an excellent
product. Like all great products, it not only does a thing well, it
also makes you want to do that thing more. Paper makes me want to
draw more. After drawing with Paper for a few weeks, I wanted a
quick way to share my drawings from Paper to the Web. I built
<a href="https://github.com/tylergaw/papersaver">Papersaver</a> so I
could get that done.
</p>
<h2>In Dependence</h2>
<p>
<a href="http://madewithpaper.fiftythree.com/">Made with Paper</a>
is a showcase of what people are drawing with Paper. I went to
submit one of my silly sketches to it and saw that they ask for an
attribution link. They suggest–and most submissions are hosted
on–Tumblr. Twitter and Facebook are also mentioned as hosting choices.
I don’t care for that. I want the canonical location of my brain
dumps to be on a server that I control. This is my natural tendency,
but I was also inspired by <a href="http://adactio.com/journal/6620/">Jeremy</a>
and <a href="http://frankchimero.com/blog/2013/12/homesteading-2014/">Frank’s</a>
articles on the topic of pushing ourselves to take more control over
our digital artifacts by self-hosting.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-papersaver-attribution.jpg" alt="A portion of the Made with Paper submission form">
<figcaption>
Host my masterpieces on a Tumblr? Nope.
</figcaption>
</figure>
<p>
At this point it was clear that I had a fun side project in front of
me. With a few days off work for the holidays, I started letting my
brain run wild with possibilities. The plan was to build some type
of site where I could publish my drawings while digging into a
technology or two that was new to me.
</p>
<h2>Planning</h2>
<p>
I generally start a new project with a few bullet points for
what I want, what I don’t, technologies I’ll use, etc. These points
don’t always make it into the final project and some are just
questions to think about during the process. Here’s a few I jotted
down for Papersaver:
</p>
<h3>Project goals</h3>
<ul>
<li>Publish images from Paper to my personal Web site</li>
<li>Do not rely on third-party platforms for storage</li>
<li>“In Dependence”</li>
</ul>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-papersaver-notes01.jpg" alt="Papersaver notes taken in the Paper App">
<figcaption>
I used Paper to take notes.
</figcaption>
</figure>
<h3>What does it do?</h3>
<ul>
<li>Static HTML site</li>
<li>Displays a list of images (maybe thumbnails + click to enlarge)</li>
<li>Git repo backed up to Github</li>
<li>Different input methods: email, Twitter, direct to repo</li>
<li>generate(): image creation, HTML generation, Git commit, publish</li>
<li>How can others use this?</li>
<li>Permalinks to each item</li>
</ul>
<p>
At this point I’ll also jot down some pseudo-code to start thinking
about core functions of what I’ll be building. In this case I wanted
to visualize what the main method of saving a Paper would look like:
</p>
<pre><code class="language-clike">newPaper() // takes img
</code></pre>
<p>// contents/papers/Date.getTime()
// " "/index.md
// date: D.getTime()
// template: paper.html
// image_path: tylergaw-paper-{date}.jpg
// save img: image_path.jpg</p>
<h2>Getting rolling</h2>
<p>
After a few hours of writing down goals and researching similar
projects I had a good idea of what I would build and the tools I
would use to build it. I needed a way to get my drawings out of Paper.
The App allows drawings to be shared to Tumblr, Facebook, Twitter,
Camera Roll, and Email. The first three were out
since they’re third-parties. I entertained the idea of using a
combination of export to cameral roll and Dropbox to publish, but
went away from it because it didn’t feel flexible enough.
I’d be going the route of publishing via email.
</p>
<pre><code> <h2>How I use Papersaver</h2>
<ul>
<li>
Draw in Paper
</li>
<li>
Email each drawing I want published to a dedicated email address
</li>
<li>
A Node.js <a href="https://github.com/tylergaw/papersaver/blob/master/papersaver-mail-listener.js">script</a>
listens for new email at the dedicated email address
</li>
<li>
When a new email arrives, the script grabs the attached
image and hands it off to
<a href="https://github.com/tylergaw/papersaver/blob/master/papersaver.js">Papersaver.js</a>
</li>
<li>
Papersaver.js creates the necessary directories and files
for Node.js static site generator, <a href="https://github.com/jnordberg/wintersmith">Wintersmith</a>
</li>
<li>
Wintersmith regenerates the site with the new drawings
</li>
<li>
The newly added and created files are commited to the Git
repo and those commits are pushed to the remote repo at Github
</li>
<li>
The drawings live at <a href="http://lab.tylergaw.com/papers">http://lab.tylergaw.com/papers</a>
</li>
</ul>
<h2>The pieces</h2>
<p>
Papersaver is a fairly simple hack, but there are a few interesting
parts worth talking about in more detail.
</p>
<h3>Good enough email security</h3>
<p>
I'm using a <a href="https://github.com/chirag04/mail-listener2">node package</a>
to access Papersaver emails over IMAP. I don't want to leave the
security of my primary email address in the hands of my programming
skills so I use a dedicated email address–tylergaw.papersaver@gmail.com–for
Papersaver.
</p>
<p>
I just gave you the email address. So what's to keep you from
flooding my Papersaver with all the images you please? (aside from your trustworthy nature)
I use a settings file with to and from whitelists to only allow emails
to/from certain addresses. Nothing good would come from me adding
my actual settings to the public Github repo, but I do include a
<a href="https://github.com/tylergaw/papersaver/blob/master/mail-settings.sample.json">sample settings file</a>.
The from address is really easy to fake, but the to address is a pretty
decent way to lock things down.
</p>
<p>
I use a "+slug" on the email address that I don't publish anywhere.
Fake example; tylergaw.papersaver+71ksk27817@gmail.com. I put that address
in my <code>toWhitelist</code> settings. The <a href="https://github.com/tylergaw/papersaver/blob/master/papersaver-mail-listener.js#L69">mail listener script</a>
checks to make sure any emails received are to an address in the whitelist,
if not, it bails. Fort Knox an email slug don't make, but for this
little project it gets the job done.
</p>
<h3>Papersaver Command-line interface</h3>
<p>
While I was working on Papersaver, I didn't want to have to send an
email each time I needed to test that the saving code was working.
To make things faster I wrote a <a href="https://github.com/tylergaw/papersaver/blob/master/papersaver-cli.js">simple CLI</a>
that allows me to create new Papers from images on my machine. I used
the super-cool node package <a href="https://github.com/substack/node-optimist">Optimist</a>
to handle the arguments I wanted to provide. The Papersaver CLI isn't
any type of technological achievement, but it was the first time I've
written something like it, so it gets a mention here.
</p>
<h3>Static site generator</h3>
<p>
Since I decided to go the Node.js route for this project, I wanted
all the pieces to be written in Node.js. <a href="http://wintersmith.io/">Wintersmith</a>
is a static site generator written in Node.js. These types of static generators
have been <a href="http://davidtucker.net/articles/introduction-to-wintersmith/">written about at length</a>
so I don't have anything more to add.
I'll just say that Wintersmith gets the job done and if I need a
generator written in Node.js, it will be my go-to.
</p>
<h2>Steal this project!</h2>
<p>
I didn't build this project just for me. My hope is that other folks
that use Paper will find it interesting and start self-hosting the
notes, drawings, and sketches they create. Papersaver isn't yet a
turnkey project. I had that in mind from the start, but it will require
more work to get it there. In the meantime, I'd love to see forks of
it that remedy some of the issues that are currently making it not
ready for quick re-use. I'm also 100% open to pull requests, so
<a href="https://github.com/tylergaw/papersaver">fork it!</a>
</p>
<p>
<i>Thanks for reading</i>
</p>
</code></pre>
Slenderman2013-07-03T00:00:00Zhttps://tylergaw.com/blog/slenderman/<pre><code> <figure>
<a href="http://lab.tylergaw.com/slenderman">
<img src="https://tylergaw.com/articles/assets/post-image-slenderman-intro.png" alt="Slenderman in use">
</a>
</figure>
<p class="entry-intro">
Fonts have always felt like a black box to me. The thought of creating
a new one seemed like a task reserved for a select few with decades
of experience, a deep knowledge of typography, and cumbersome,
expensive software. Not me, I thought. I proved myself wrong by
making my first font,
<a href="http://lab.tylergaw.com/slenderman">Slenderman</a>.
</p>
<h2>Made to Scratch an Itch</h2>
<p>
I’m working on a small design project that felt like it needed hand drawn type.
I could have gone the route of using images, but
that just doesn’t feel right on the Web these days.
I want my web type to be scalable and maintainable. Using images for
type is neither of those. It was clear that I’d need to either find an
existing font that fit the style I wanted, or create my own.
</p>
<p>
I didn’t look around for existing fonts that fit. I jumped straight
to creating my own. The project is a personal one so there’s no
time limit. When I have that choice I love to let projects expand to
as many different areas as they need to. This same project also led
me down the path of building <a href="https://tylergaw.com/articles/webvtt-cuemarker">another small tool</a>
for working with HTML video subtitles.
</p>
<h2>The Process</h2>
<p>
I knew how I wanted the capital letters to look from the outset. I
started by just drawing them out on paper. My first thought for the
lowercase letters was to make them not match the style of the caps,
but to be a complement to them. I though a cursive handwriting style
would be a nice contrast to the tall, skinny caps.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-slenderman-sketch01.jpg" alt="Initial pen and paper sketches of Slenderman">
<figcaption>Capital and unused lowercase letter sketches</figcaption>
</figure>
<p>
After drawing the letters, I scanned them and used Illustrator’s
Live Trace to convert them to vectors. The cursive lowercase letters didn’t
convert well. They lost a lot of detail, and just kind of turned
into muck. It was clear they weren’t going to work out so I ditched them.
</p>
<p>
I went back to the pen and paper and drew a new set of lowercase
letters as well as the numbers, punctuation, and assorted symbols.
You can see in the drawing that the “@” and “&” are tough to decide on.
I followed the same scan then Live Trace with these.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-slenderman-sketch02.jpg" alt="Initial pen and paper sketches of Slenderman">
<figcaption>Numbers, punctuation, symbols, and two sets of lowercase letter sketches</figcaption>
</figure>
<p>
With all the characters drawn, scanned, and converted to vector the next
step was to start refining them. All the charaters needed to have similar
stroke weights, similar widths and heights, and a handful of other
defining characteristics to make them feel like a family.
</p>
<p>
I started with the capital letters. They converted to vector really well.
For them I mostly just needed to make sure each letter was the
correct width and height. I also needed to remove extra, uneeded
points that Live Trace had added during conversion.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-slenderman-vector-caps.jpg" alt="A few Slenderman capital letters in Illustrator">
<figcaption>Working with the capital letters in Illustrator</figcaption>
</figure>
<p>
While I was refining each capital letter, I was bringing each into
<a href="http://glyphsapp.com/glyphs-mini/">Glyphs Mini</a>, my font
editor of choice. It allows vectors to be pasted in from Illustrator.
Once the vector is in Glyphs I could set the position and spacing.
Glyphs also allows characters to be typed out in
the edit tab to get a real feel for how each one interacts
with the others. This allows for creating kerning pairs of characters.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-slenderman-glyphs01.jpg" alt="Editing Slenderman glyphs using Glyphs Mini">
<figcaption>Glyphs Mini edit tab</figcaption>
</figure>
<p>
The refining process for the lowercase letters took a lot more work
than the capitals. For each of the 26 letters, I needed to make the strokes narrower,
and for a lot of them completely change the shape from what I had
drawn. This involved manually moving the points that made up each
letter. It felt a lot like the original vectors were chunks of
rough granite that I needed to chisel down into their final forms.
As you can imagine, it’s a long, slow process.
</p>
<p>
I followed the same process for the punctuation, numbers, and symbols.
While I was refining the numbers, I did get a little overzealous. I
ended up chipping away a bit too much off each one, leaving them very
skinny. When they were next to other characters it looked like they
were from a different family. This required me to go back and make
each number a bit heavier. Lesson learned; compare each character to
as many others as you can while you’re tweaking the vectors.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-slenderman-glyphs02.jpg" alt="A view of most of the glyphs of Slenderman in Glyphs Mini">
<figcaption>A bird’s-eye view of Slenderman</figcaption>
</figure>
<p>
Glyphs Mini allows for exporting to the OpenType format. While I was adding characters
I wanted to get a better feel for how they were working. I would export
the font, install it and then open up a test Photoshop document. Seeing
full sentences and different combinations of letters, numbers, and symbols
is really key to determining if things are working. I could better
see any issues with spacing and sizing and pick out pairs of
characters that could use attention to kerning. Also, it’s a really
great feeling to be able to use a font after spending so much time
working with it.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-slenderman-tester-psd.jpg" alt="Testing Slenderman in Photoshop">
<figcaption>Testing Slenderman in Photoshop</figcaption>
</figure>
<h2>Detail of a Gotcha</h2>
<p>
This is very specific to users of Glyphs Mini, but I spent a few hours
banging my head against the desk before I figured it out
so I’m going to write about it in the hopes that I’ll save some
other poor soul’s forehead.
</p>
<p>
Glyphs has components. A component is a Glyph that is made
up of a base glyph. Example components are right and left double quotes
and the ellipsis; made up of right and left single quotes and the period,
respectively. When you open a component to edit, the base
glyph is grayed out, it seems like it is uneditable. It also only shows
the base glyph. In the case of double quotes only a single right
or left quote is shown. For the ellipsis only a single period. For some reason
Glyphs stacks the base glyphs on top of each other. The base
glyph needs to be clicked and dragged to reveal the other base glyph(s)
under it. I found this answer on the
<a href="http://glyphsapp.com/forums/index.php?action=vthread&forum=1&topic=282">Glyphs forum</a>.
</p>
<p>
Part two of the gotcha took longer to figure out. After I’d separated
the base glyphs of the compenents, I spaced them, tested them with
other characters in the edit tab, and exported like normal.
Then I tried to use right and left double quotes and the ellipsis. Nothing
would show up. I could tell the glyph exported because I didn’t get
the usual missing glyph character and neither Photoshop nor TextEdit
tried to replace it with another font. This seemed like one of those
what in the hell?!/computer voodoo moments.
</p>
<p>
<b>Cmd + Shift + D</b> was the magic key combo that ended my confusion.
Components need to be Decomposed before they can be exported properly.
The option is available under the Glyph menu > Decompose Components.
Once a component is decomposed it is no longer tied to a base glyph
so it can be edited like any other glyph and it exports normally.
</p>
<h2>Slenderman is Open Source</h2>
<p>
I’ve always felt like when I download a font that’s that. The font is
done and nothing can be added or modified. This might not be the
reality, but without a knowledge of how fonts are
made, that’s how I’ve seen things. With Slenderman, I’m not aiming for
it being “done”. I want to keep iterating and improving on it.
All of the source files are available on
<a href="https://github.com/tylergaw/slenderman">GitHub</a>. That means
anybody can fork the repo and make any changes or additions needed.
They can make new versions of it that fit their needs, or open Pull
Requests to have their changes added into the main fork of Slenderman.
I haven’t seen that before and I’m interested in seeing if anyone
is interested in working on it.
</p>
<h2>What Did I Learn?</h2>
<p>
Slenderman does not make me a typographer by any means. It did open up
the possibilty though. Working on it has given me a greater
appreciation for the skill and patience required to create the
beautiful type that we use and often take for granted.
</p>
<p>
<i>Thanks for reading</i>
</p>
</code></pre>
WebVTT Cuemarker2013-06-08T00:00:00Zhttps://tylergaw.com/blog/webvtt-cuemarker/<pre><code> <p class="entry-intro">
<a href="http://dev.w3.org/html5/webvtt/">WebVTT</a> is a text format
that can be used to provide captions and subtitles for HTML video.
While working on a project that uses it I found myself needing a
way to find and mark precise cue in/out times.
<a href="https://github.com/tylergaw/webvtt-cuemarker">webvtt-cuemarker</a>
is a small tool I whipped up in JavaScript to do just that.
</p>
<h2>A WebVTT Primer</h2>
<p>
The format is still fairly new and not yet widely supported, so I'll give
a quick example of how it's used.
</p>
<pre><code class="language-clike">WEBVTT FILE
</code></pre>
<p>00:00:01.265 --> 00:00:06.210
This is text that will appear over the video at during the in/out points.</p>
<p>00:00:05.500 --> 00:00:10.250
You can use <b>HTML</b> within WebVTT, pretty cool.
</p>
<p>
That's an example of a very simple WebVTT file with two cues. We'll
refer to it as <code>captions.vtt</code>
</p>
<p>
The next thing to do is to let your HTML video know about the WebVTT file.
That's done using the <code>track</code> element.
</p>
<pre><code class="language-markup"><video controls>
<source src="/path/to/video.mp4" type="video/mp4">
<source src="/path/to/video.webm" type="video/webm">
<track kind="captions" label="English captions"
src="/path/to/captions.vtt" srclang="en" default></track>
</video>
</code></pre>
<p>
That's all there is to it. If we played the fictional video above
we would see the two captions appear at the times provided in <code>captions.vtt</code>.
For a more in-depth look at the <code>track</code> element I suggest
<a href="http://www.html5rocks.com/en/tutorials/track/basics/">Getting Started With the Track Element</a>
on HTML5 Rocks. That's where I first learned about all this goodness.
</p>
<h2>Why Is Cuemarker Needed?</h2>
<p>
WebVTT is very specific about the time format for cues. Cue times must
be represented exactly as shown in <code>captions.vtt</code>. It's
easy to type out the times in that way, but how do you find
precise times using HTML video? HTML video controls display the time
of the video, but only down to the second. And not in the format needed
for WebVTT. This is why I wrote Cuemarker.
</p>
<p>
Cuemarker provides a keyboard shortcut–the period key–for setting in/out
points and outputting the cue time in the required format.
</p>
<p>
Cuemarker also provides shortcuts to interact with HTML
video. You can play/pause using the space bar and
seek forward/backward using the right/left arrow keys. Those controls
allow you to arrive at more precise moments in video than you can
using the default video scrubber provided by the browser.
</p>
<h2>How Do I Use It?</h2>
<p>
I'll go back to the HTML in the previous example, but I'm going to
remove the <code>track</code> element. For marking cue times you only
need an HTML video.
</p>
<pre><code class="language-markup"><video controls>
<source src="/path/to/video.mp4" type="video/mp4">
<source src="/path/to/video.webm" type="video/webm">
</video>
</code></pre>
<p>
Once your HTML is in place, you'll need a bit of JavaScript.
Cuemarker creates a variable–<code>cuemarker</code>–in the global scope.
<code>cuemarker</code> is a function that takes one required parameter;
the video element. It also takes a second, optional parameter to
specify a seek interval and output function.
</p>
<pre><code class="language-javascript">cuemarker(document.querySelector('video'));
</code></pre>
<p>
That's a bare-bones example and may be all that you'll need. The cue
times will be output using <code>console.log</code>.
</p>
<p>
Here's another example using both the available options.
</p>
<pre><code class="language-javascript">cuemarker(document.querySelector('video'), {
seekInterval: 0.5 //default is 0.03,
output: function (cuetime) {
var times = document.getElementById('cuetimes'),
li = document.createElement('li');
li.innerHTML = cuetime;
times.appendChild(li);
}
});
</code></pre>
<p>
In that example, the seek interval is less precise than the default
and the cue times will be injected into the page as <code>li</code>
elements that are children of the <code>ul</code> with an id of "cuetimes".
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post-image-cuemarker-output.jpg" alt="Screenshot of Cuemarker output" />
<figcaption>Cuemarker output</figcaption>
</figure>
<p>
Cuemarker doesn't try to get cute by attempting to transfer cue times to
a WebVTT file. All it's interested in doing is outputting the
in/out times. Once you've marked in/out times you
can copy them over to your WebVTT files. That's how I've been using
it and it's working really well for me.
</p>
<p>
A working demo of the latter example is located
<a href="http://tylergaw.github.io/webvtt-cuemarker/demo.html">here</a>.
</p>
<h2>That's It?</h2>
<p>
Yep, that's what I got for now. Again, the <code>track</code> element
and WebVTT are not yet widely supported. As of this writing only the
latest Chrome and Opera are cool enough to do so. The Web moves fast though and
I know it won't be long before Firefox and Safari follow suit.
</p>
<p>
Limited support for the underlying technology means that this is a
tool you may not need today, but you might tomorrow.
<a href="https://github.com/tylergaw/webvtt-cuemarker">Fork it!</a>
</p>
<p>
<i>Thanks for reading</i>
</p>
Get Together. Get Excited. Get to Work.2012-09-24T00:00:00Zhttps://tylergaw.com/blog/get-together-get-excited-get-to-work/<figure>
<a href="http://brooklynalpha.org/2012">
<img src="https://tylergaw.com/articles/assets/post-image-brooklyn-alpha-2012.png" alt="Brooklyn Alpha 2012" />
</a>
</figure>
<p>
<b>Brass tacks:</b> <em>
<a href="http://brooklynalpha.org/2012">
Come to my build-a-thon, make cool shit!
</a>.
</em>
</p>
<p class="entry-intro">
If you haven't heard, Brooklyn is <b>the</b> place to be the
second week of October 2012. That's the week that the Internet
is getting together for a week of conferences, meet ups, drink-ups,
shinanagans, and the friendliest little web build-a-thon that could.
It just so happens that that build-a-thon is being organized and
hosted by myself and <a href="https://twitter.com/xiian">Tom Sartain</a>.
We'd love for you to join us!
</p>
<h2>What's this “build-a-thon”?</h2>
<p>
Alright, here's the deal. If you're a designer or a developer I
know you have at least one project in your mind right now
that you want to work on. And there's a good chance you've
been thinking about working on this project for a while and for
one reason or another haven't been able to dive in and get it done.
</p>
<p>
Brooklyn Alpha is a perfect excuse to give yourself three days
to put your idea into motion. Not only will you have three days, you'll
have access to other motivated folks who may have complementary
skills. Maybe you're a designer who needs a Python developer. Or
you're Mr. Django himself and you need a designer to help
make your project easy to use and easy on the eyes. Or maybe you're someone
who has a head full of great ideas and you're looking for
designers and developers to lend a hand in materializing all that
greatness coming from your grey matter. Whatever your situation
we'll have you covered.
</p>
<h2>“I don't have any ideas”</h2>
<p>
OK, OK. The brain well done gone dry. It happens to the best of us.
Can you sling hot pixels or code? Of course you can. Then you should
come to Brooklyn Alpha. Here's why. Last year when we opened up
the floor to anyone to get up and pitch ideas, nearly every
single one of our incredible attendees got up and did just that.
And more than a few got up a second or third time. There will be
no shortage of excellent ideas at Alpha and many of them will need
your brain to get them moving.
</p>
<h2>OK, cool. What else?</h2>
<p>
I'll tell you what else. You get to be a part of something that's
going to be huge. Brooklyn Alpha is sharing the week with the
legendary <a href="https://brooklynbeta.org/">Brooklyn Beta</a>
and we're an official part of <a href="https://brooklynbeta.org/beyond">Beyond Beta</a>.
If you haven't heard of those stop reading this now and go find out
about them (then hurry back and pick up your ticket to Alpha).
</p>
<p>
Brooklyn Alpha will be taking place during the day each of its
three days. That leaves the evenings free for maximum mingling
with a whole host of excellent web folks that will for sure be
inhabiting our incredible Borough of Brooklyn during the week.
</p>
<h2>Hope to see you there!</h2>
<p>
This is our second year hosting Brooklyn Alpha. It's a challenging,
fun, and rewarding task to take on. We're doing this because
we love the work we do, we love the city we live in, and we love
the web community that makes events like this possible. We're
honored to be able to surround ourselves with so many people that
share our passions.
</p>
<figure>
<a href="https://www.flickr.com/photos/brooklynalpha/6251904083/in/photostream">
<img src="https://farm7.staticflickr.com/6042/6251904083_1347d7870f_b.jpg" alt="Brooklyn Alpha 2011" />
</a>
<figcaption>Brooklyn Alpha 2011 Group Photo</figcaption>
</figure>
<p>
<i>Thanks for reading</i>
</p>
Reacting to Media Queries in JavaScript2012-05-17T00:00:00Zhttps://tylergaw.com/blog/reacting-to-media-queries-in-javascript/<p>
<b>Brass tacks:</b> <em><a href="http://tylergaw.github.com/media-query-events/">A demo</a> and <a href="https://github.com/tylergaw/media-query-events">some code</a>.</em>
</p>
<p class="entry-intro">
<code>window.matchMedia</code> provides a way for Javascript to react when a media query condition is met or unmet. While the functionality it allows is great, the necessary code duplication required to use it leaves a bit to be desired. I'm going to walk through a work-in-progress approach to getting around that duplication.
</p>
<h2>window.matchMedia</h2>
<p>
The method is simple enough to use and works the way you'd expect it. You give it a media query string it gives you back a <a href="https://developer.mozilla.org/en/DOM/MediaQueryList">MediaQueryList</a> object.
</p>
<pre><code class="language-javascript">var mql = window.matchMedia("(min-width: 480px)");</code></pre>
<p>
That sets the value of <code>mql</code> to a MediaQueryList object with two members, something like:
</p>
<pre><code class="language-javascript">MediaQueryList: {
matches: true,
media: "(min-width: 480px)"
}</code></pre>
<p>
The boolean value of the <code>matches</code> member will be determined by the width of your browser window at the time.
</p>
<p>
You can add event listeners to MediaQueryList objects. An event will fire each time the condition is triggered. This allows you to be updated on the status of the media query without having to resort to polling or a <code>window.resize</code> event. Using <code>mql</code> from above we can set up a listener and handler like so:
</p>
<pre><code class="language-javascript">mql.addListener(handleMediaChange);
handleMediaChange(mql);
<p>var handleMediaChange = function (mediaQueryList) {
if (mediaQueryList.matches) {
// The browser window is at least 480px wide
}
else {
// The browser window is less than 480px wide
}
}</p></code></pre><p></p>
<p class="note">
You can find similar code examples and further explanation of MatchMedia on the <a href="https://developer.mozilla.org/en/DOM/window.matchMedia">Mozilla Developer Network</a>
</p>
<p>
MatchMedia works pretty well. As usual there are some caveats that I'll mention later. My issue is with needing to specify the media query when you create the MediaQueryList. If you wanted an event to fire for every media query you might have, you'd have manually copy each from your stylesheets to you JS. Every time you update a media query in CSS, you'd have to do the same in JS. What I want is a script to look at a page's stylesheets, pick out all the media queries and create a MediaQueryList for each one.
</p>
<h2>
Getting Media Queries from CSS to JS
</h2>
<p>
When I first started looking into this I thought I was in for some really hairy stuff. I imagined myself having to make ajax requests to fetch stylesheets, use weird regexes to find the <code>@media</code> rules, and employ other types of not-so-fun things. Luckily, this did not turn out to be the case.
</p>
<p>
I've written small script that accomplishes the tasks I'm after, <a href="https://github.com/tylergaw/media-query-events/blob/master/js/mq-events.js">mqEvents.js is on Github</a> and a working example is at <a href="http://tylergaw.github.com/media-query-events/">tylergaw.github.com/media-query-events</a>.
</p>
<pre><code class="language-javascript">(function () {
var mqEvents = function (mediaChangeHandler) {
var sheets = document.styleSheets,
numSheets = sheets.length,
mqls = {},
mediaChange = function (mql) {
console.log(mql);
}
<pre><code> if (mediaChangeHandler) {
mediaChange = mediaChangeHandler;
}
for (var i = 0; i < numSheets; i += 1) {
var rules = sheets[i].cssRules,
numRules = rules.length;
for (var j = 0; j < numRules; j += 1) {
if (rules[j].constructor === CSSMediaRule) {
mqls['mql' + j] = window.matchMedia(rules[j].media.mediaText);
mqls['mql' + j].addListener(mediaChange);
mediaChange(mqls['mql' + j]);
}
}
}
}
window.mqEvents = mqEvents;
</code></pre>
<p>}());</p></code></pre><p></p>
<p>
I'm going to go through the code here and explain what's happening each step of the way.
</p>
<pre><code class="language-javascript">var mqEvents = function (mediaChangeHandler)</code></pre>
<p>
The <code>mqEvents</code> function takes a single parameter, a function that will be called each time a media query is triggered.
</p>
<pre><code class="language-javascript">var sheets = document.styleSheets,
numSheets = sheets.length</code></pre>
<p>
The document contains an object of all loaded stylesheets. Our <code>sheets</code> variable is a list of <a href="https://developer.mozilla.org/en/DOM/stylesheet">StyleSheet</a> objects. <code>numSheets</code> is stored for convenience for when we loop over the list of stylesheets.
</p>
<pre><code class="language-javascript">mediaChange = function (mql) {
console.log(mql);
}
<p>if (mediaChangeHandler) {
mediaChange = mediaChangeHandler;
}
</p></code></pre><p></p>
<p>
If the <code>mediaChangeHandler</code> argument is not passed to <code>mqEvents</code>, a default function, <code>mediaChange</code> will handle each media query event. The default doesn't do much of anything. For the purpose of this script we just want to have something there.
</p>
<pre><code class="language-javascript">for (var i = 0; i < numSheets; i += 1) {
var rules = sheets[i].cssRules,
numRules = rules.length;</code></pre>
<p>
Here we're looping over our <code>sheets</code> list to look at each loaded stylesheet. The <code>rules</code> variable is list of all the rules of the current stylesheet represented as <a href="https://developer.mozilla.org/en/DOM/cssRule">CSSRule</a> objects. <br /><em>This is where things take a turn for the awesome.</em>
</p>
<p>
At the start of this I was aware that all a document's stylesheets could be accessed and that all the rules of the stylesheets were represented by CSSRule objects, but what I didn't know was that CSSRule objects that contain a media query have a unique name. Take a look at this screen shot of the console when logging out each CSSRule object:
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/mqevents-cssrules-in-console.jpg" alt="Screenshot of the Chrome developer console showing a number of CSSRule objects being logged." />
<figcaption>Logging out each CSSRule object reveals a unique name for media query rules. Badass.</figcaption>
</figure>
<p>
Regular CSS rules have the name "CSSStyleRule" while media queries have the name "CSSMediaRule". This is great because it gives us an easy way to pluck out only the media queries from our stylesheets without needing to resort to string parsing, which can get ugly quickly.
</p>
<p class="note">
(It looks like other types of rules like font-face and keyframes have <a href="https://developer.mozilla.org/en/DOM/cssRule#section_2">unique names too</a>.)
</p>
<pre><code class="language-javascript">for (var j = 0; j < numRules; j += 1) {
if (rules[j].constructor === CSSMediaRule)</code></pre>
<p>
We can now loop over each rule in the stylesheet and check to see if it is a media query. The condition here, checking to see if the constructor matches the name "CSSMediaRule", was also new to me. I found that approach in a thorough <a href="http://stackoverflow.com/a/332429/368634">Stack Overflow answer</a> on the topic.
</p>
<pre><code class="language-javascript">mqls['mql' + j] = window.matchMedia(rules[j].media.mediaText);
mqls['mql' + j].addListener(mediaChange);
mediaChange(mqls['mql' + j]);</code></pre>
<p>
Now that we know we're only dealing with media queries, we're free to use them with matchMedia to create MediaQueryList objects, bind events to those and handle them with a given handler function. This bit of code is nearly the same as the matchMedia example. The noticeable exception here is that each MediaQueryList object is being added to a hash, <code>mqls</code>, that we created earlier. We could accomplish the same thing without putting each object in the hash, this was more of a forward-thinking thing. I have a feeling that there could be a use for holding on to all of the objects to access them later.
</p>
<h2>Usage</h2>
<p>
So why would you use this and what happens when you do? In the demo I linked to, this is the implementation:
</p>
<pre><code class="language-javascript">var msg = document.getElementById('condition'),
handleMediaChange = function (mql) {
<pre><code>// For some reason Firefox has trouble always running this code.
// The console.log seems to help it.
// TODO: Figure out what the hell that's all about
console.log();
if (mql.matches) {
msg.setAttribute('class', 'met');
msg.innerHTML = 'The condition "' + mql.media + '" was met.';
}
else {
msg.setAttribute('class', 'unmet');
msg.innerHTML = 'The condition "' + mql.media + '" was not met.';
}
</code></pre>
<p>};</p>
<p>mqEvents(handleMediaChange);</p></code></pre><p></p>
<p>
Notice the big comment about Firefox there, I still don't know what that is. Like I said, work-in-progress here.
</p>
<p>
What this does is update a the DOM element, represented by <code>msg</code>, each time a media query is triggered. <code>mqEvents</code> doesn't try to react differently to specific media queries. It calls the same handler each time one is triggered. The handler function, however, does receive information about the media query. It knows what the media query is–which I'm placing in the DOM with <code>mql.media</code>–and if the condition was met by the window–which I'm checking with <code>mql.matches</code>.
</p>
<h2>What Else Can This Be Used For?</h2>
<p>
Maybe parts of the page layout are done with Javascript and need to be updated each time a media query is triggered. Maybe it could be used to do conditional loading of images, scripts, fonts, or the like. This type of conditional loading of assets could be used to help reduce the bandwidth usage on mobile devices.
</p>
<h2>Concerns and Caveats</h2>
<p>
If a page has a lot of media queries, <code>mqEvents</code> is going to add a lot of event listeners and it's going to be calling the handling function a lot of times. I haven't run into anything problematic with my small demo page, but I'd be curious to see the impacts on performance on a page with a substantial number of media queries.
</p>
<p>
With all these fancy new things browser support is a big question. <code>matchMedia</code> is not supported very well yet. According to <a href="http://caniuse.com/#search=matchMedia">caniuse.com</a> we're looking at Chrome 17+, Firefox 9+, Safari 5.1+, iOS Safari 5.0+, IE 10+ (with an ms prefix), and no support yet for Opera. For unsupported browsers a polyfill could be employed. Here's a good one right <a href="https://github.com/paulirish/matchMedia.js/">here</a>.
</p>
<h2>Other Cool Stuff</h2>
<p>
As I was working on this I found out about a related project named <a href="http://harvesthq.github.com/harvey">Harvey</a>. I haven't used it yet, but it looks really good. It looks like it still has that duplication of media queries issue, but if you're looking for targeted reactions to specific media queries it might be the way to go.
</p>
<p>
<i>Thanks for reading</i>
</p>
Fun with HTML Form Validation Styles2011-08-23T00:00:00Zhttps://tylergaw.com/blog/fun-with-html-form-validation-styles/<p>
<b>Brass tacks:</b> <em>Demos; <a href="http://lab.tylergaw.com/html5forms/errorStyles/01/">Style 01</a>, <a href="http://lab.tylergaw.com/html5forms/errorStyles/02/">Style 02</a>, <a href="http://lab.tylergaw.com/html5forms/errorStyles/03/">Style 03</a> and <a href="https://github.com/tylergaw/html5forms/tree/master/errorStyles">the code</a>.</em>
</p>
<p class="entry-intro">
If you haven't heard already, the current HTML Living Standard or HTML5 spec has in it some super cool form validation. This is client-side form validation that is meant to remove some of the burden of writing validation with Javascript. If you've ever written form validation with Javascript I think you'll agree that this is a very good thing.
</p>
<p>
Using this new feature of html can be as easy as creating a form and within it creating an input with a <code>required</code> attribute. When the form is submitted, browsers that support validation will take the reigns and make sure that the required input has a value. If it does not, the browser will display a validation error message.
</p>
<p>
In addition to validating required fields, the form validation will also check to make sure inputted values match given input types. New in the Living Standard are quite a few new input types such as; "email", "url", "number" and so on. Check out the full list located at <a href="http://developers.whatwg.org/the-input-element.html#the-input-element">http://developers.whatwg.org/the-input-element.html#the-input-element</a>.
</p>
<h2>Validation error messages</h2>
<p>
As of this writing, there are three browsers that both support html form validation and display error messages when needed. Browser support includes; Chrome 10+, Firefox 3.6+, and Opera 11+. Safari doesn't care about validating your forms and I haven't checked any versions of IE, maybe 9 or 10 support this? Each browser handles the styling of validation messages differently.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post_image_htmlFormErrors_defaultChrome.jpg" alt="Chrome 12 default error messages" />
<figcaption>Chrome 13 default error message</figcaption>
</figure>
<figure>
<img src="https://tylergaw.com/articles/assets/post_image_htmlFormErrors_defaultFirefox.jpg" alt="Firefox 5.0 default error messages" />
<figcaption>Firefox 6.0 default error message</figcaption>
</figure>
<figure>
<img src="https://tylergaw.com/articles/assets/post_image_htmlFormErrors_defaultOpera.jpg" alt="Opera 11.5 default error messages" />
<figcaption>Opera 11.5 default error message</figcaption>
</figure>
<p>
Of the three, only Chrome currently offers a way to apply custom styles to the message. Through non-standard, Webkit-specific pseudo classes you are able to style the messages in any way that floats your boat. That's what I'm focusing on with this article.
</p>
<h2>What about the other browsers?</h2>
<p>
That's a good question. I haven't heard anything about Opera, but it seems that the style and even handling of the error messages is an open discussion with Firefox. I did quite a bit of digging around in <a href="http://www.bugzilla.org/">Bugzilla</a> and in the FF 6 source code, but couldn't find any solid information on what the plan is. My hope is that a standard will emerge for styling the messages, but that could be far off.
</p>
<h2>The pseudo classes and markup</h2>
<p>
When I first started looking into if/how the error messages could be styled I quickly came across a "Rosetta Stone" in an article by Peter Gasston, <a href="http://www.broken-links.com/2011/06/16/styling-html5-form-validation-errors/">http://www.broken-links.com/2011/06/16/styling-html5-form-validation-errors</a>. In the post Peter explains the pseudo classes that webkit makes available to target the error message elements. He's done the leg work of digging through the Webkit source code to find this stuff, thanks Peter! To reiterate some of what is in Peter's article, the following are the classes that are available to hook into:
</p>
<pre><code class="language-css">::-webkit-validation-bubble {}
<p>::-webkit-validation-bubble-message {}
::-webkit-validation-bubble-arrow {}
::-webkit-validation-bubble-arrow-clipper {}</p></code></pre><p></p>
<p>
Each of those target a <code><div></code> element that is inserted into the DOM when a validation error is triggered. The markup looks like this:
</p>
<pre><code class="language-markup"><div -webkit-validation-bubble>
<div -webkit-validation-bubble-arrow></div>
<div -webkit-validation-bubble-arrow-clipper></div>
<div -webkit-validation-bubble-message>Error Message</div>
</div></code></pre>
<p>
There are four more pseudo classes that were added as I was writing this article. These are currently only in the nightly builds of Chrome/Webkit and seem to just allow further control of the styling.
</p>
<pre><code class="language-css">::-webkit-validation-bubble-icon {}
::-webkit-validation-bubble-body {}
::-webkit-validation-bubble-text-block {}
::-webkit-validation-bubble-arrow-heading {}</code></pre>
<p>
As of this writing and Chrome 13.0.782.112 these classes do not seem to have corresponding html elements. Only the <code>bubble-text-block</code> and <code>bubble-heading</code> contain default CSS rules and they are minimal and any CSS rules applied to these do not seem to affect any changes to the current validation error messages. We'll have to wait and see how these elements get put to use.
</p>
<h2>Default styles</h2>
<p>
A huge help when styling the messages is knowing the default styles that Chrome applies. Luckily the default Webkit CSS is available to peruse so you can see the exact properties that need to be modified. The default styles live here; <a href="http://trac.webkit.org/browser/trunk/Source/WebCore/css/html.css">http://trac.webkit.org/browser/trunk/Source/WebCore/css/html.css</a>
</p>
<h2>Demos</h2>
<p>
I set up three different demos using markup for a simple login form containing two fields. The first field is an email input that is required:
</p>
<pre><code class="language-markup"><input type="email" name="email" required></code></pre>
<p>
The second field is a password input that is required:
</p>
<pre><code class="language-markup"><input type="password" name="password" required></code></pre>
<p>
The rest of the markup for each demo is nearly identical, differences include; using <code><label></code> elements for the first demo and some different structural elements for each.
</p>
<p>
To really work with the message styles, I wanted each demo to have a distinct style from the others. Demo 1 has a kind of standard-looking style with error messages that are big, bright and bubbly. Demo 2 has a sleeker, higher-tech, control panel-y look with a slimmed down error icon with no text. Demo 3 has an artsy, Draplin-inspired, Futura-filled look that carries over into big-ole, text only error messages.
</p>
<figure>
<a href="http://lab.tylergaw.com/html5forms/errorStyles/01">
<img src="https://tylergaw.com/articles/assets/post_image_htmlFormErrors_demo01.jpg" alt="Demo 01 style showing an error" />
</a>
<figcaption>Style 01; 37 Signals called, they want their form back</figcaption>
</figure>
<figure>
<a href="http://lab.tylergaw.com/html5forms/errorStyles/02">
<img src="https://tylergaw.com/articles/assets/post_image_htmlFormErrors_demo02.jpg" alt="Demo 02 style showing an error" />
</a>
<figcaption>Style 02; this is the login form for your Starcraft forum</figcaption>
</figure>
<figure>
<a href="http://lab.tylergaw.com/html5forms/errorStyles/03">
<img src="https://tylergaw.com/articles/assets/post_image_htmlFormErrors_demo03.jpg" alt="Demo 03 style showing an error" />
</a>
<figcaption>Style 03; hard to go wrong with fire engine-red Futura</figcaption>
</figure>
<h2>Fun with CSS animations</h2>
<p>
Something to keep in mind when styling the messages is that most any styles that are available in Chrome can be applied to the message bubbles, this includes animations. With demos 1 and 2 I created CSS animations to bring the messages into view. Since the message elements do not exist in the DOM until an error is triggered, the animations will not run until that time. So, in demo 1, to get that little shimmy and shake I'm using the following:
</p>
<pre><code class="language-css">@-webkit-keyframes bounce {
0% {
opacity: 0.5;
margin-left: 85px;
}
<p>25% {
margin-left: -35px;
}</p>
<p>50% {
margin-left: 50px;
}</p>
<p>75% {
margin-left: -22px;
}</p>
<p>100% {
margin-left: -15px;
opacity: 0.9;
}
}</p></code></pre><p></p>
<p>
The named animation is applied to the main message container;
</p>
<pre><code class="language-css">::-webkit-validation-bubble {
-webkit-animation-timing-function: ease;
-webkit-animation: bounce 0.45s 1;
...
}</code></pre>
<p>
Demo 2 uses a similar, but less complex animation to slide the serious looking icon in from the left. This demo also introduces something new; custom error message text. The message text, among many other things, can be customized through the Constraint Validation API.
</p>
<h2>Custom message text with the Constraint API</h2>
<p>
The Constraint API is a new one in the HTML Living Standard, it allows you to further customize form validation using Javascript. The API is large enough for a full article to explain its features and how to use it, here I'll just point out the code used to set a custom error message.
</p>
<pre><code class="language-javascript">var inputs = document.getElementsByTagName('input'),
len = inputs.length,
i = 0,
cur = null,
errMsg = '!';
<p>for (i; i < len; i += 1) {</p>
<p>cur = inputs[i];</p>
<p>if (cur.getAttribute('type') !== 'submit') {
cur.setCustomValidity(errMsg);</p>
<pre><code>cur.oninput = function () {
if (!this.value || this.validity.typeMismatch) {
this.setCustomValidity(errMsg);
} else {
this.setCustomValidity('');
}
}
</code></pre>
<p>}
}</p></code></pre><p></p>
<p>
This is a fairly simple block of code, we start by looking at each <code>input</code> element on the page through a <code>for</code> loop, if the <code>input</code> is not a submit button we then use the Constraint API method <code>setCustomValidity</code> to apply the custom message of "!" that we stored in the <code>errMsg</code> variable. In my opinion that should be the end of the work needed, but there is more.
</p>
<p>
For some reason, when we set a custom error message with <code>setCustomValidity</code> that also removes the error checking from the input. If we were to stop here and submit the form, it would submit without triggering any errors. Starting at the line <code>cur.oninput</code> is the code needed to get around this oddity. The <code>oninput</code> event is fired each time a key is pressed that will change the contents of the input, each time the event fires we are checking to make sure the input has a value and that the value is valid according to the input type. The Constraint API makes available a <code>validity</code> property for <code>input</code> elements. This object contains a number of properties to determine the validity of the input, the one that we're using to make sure the value given is Ok is <code>typeMismatch</code>. If the input either has no value or the given value is not of the correct type, we need to set the custom message again. If a correct value has been entered we then set the custom validity to an empty string, which does a kind of reset on the validation. The input will then be validated normally again.
</p>
<h2>Going forward</h2>
<p>
This is definitely new, new stuff, and with all fun new things it will take some time before it can be used on a wide scale. I don't think that should be seen as a huge hinderance though. The Constraint API seems mature enough to be put into use right now, with the aide of a poly fill for browsers that don't yet support it. I haven't used it yet, but at least one exists already; <a href="http://afarkas.github.com/webshim/demos/demos/webforms.html">http://afarkas.github.com/webshim/demos/demos/webforms.html</a>. The error message bubbles are not really ready though. With only one browser supporting custom styles, and with no small amount of finagling needed to get them looking right, for now we'll need to keep creating our own error messages. I'll take it though, this is a huge step in the right direction and I can't wait for wider browser support and even more fun new things that will no doubt come with it.
</p>
<p>
<i>Thanks for reading</i>
</p>
A Small Redesign with a Little Sugar2011-07-09T00:00:00Zhttps://tylergaw.com/blog/a-small-redesign-with-a-little-sugar/<p>
<b>Take aways:</b> <em>Look at <a href="https://lab.tylergaw.com/jribbble/">this site</a> and fork <a href="https://github.com/tylergaw/jribbble.com">this code</a> if you don't feel like reading.</em>
</p>
<p class="entry-intro">
Since reading <a href="http://www.abookapart.com/products/responsive-web-design">Responsive Web Design</a> by Ethan Marcotte I've been chomping at the bit to design a site using all of the fancy things written about in that little, yellow, different book. To really take those new ideas and techniques for a ride I needed a small project to work on where I could just go crazy and try every new thing I could pull out of the book and my brain. Luckily I have a never-ending stream of personal projects that can always use a little TLC. The project that I picked to revamp is a small one page site for a jQuery plugin that I wrote last year, Jribbble. The site is quick overview of the methods that the plugin makes available, documentation of those methods and some working demos.
</p>
<h2>The design process</h2>
<p>
Before starting a design I like to set some rules for myself. These aren't hard and fast rules, just some general thoughts that I keep in mind to guide the process and keep myself in check. The rules for this design were nothing outlandish, the design should:
</p>
<ul>
<li>Use a flexible grid that remains comfortable from 320px to 1280px in width</li>
<li>Be designed mostly in the browser with CSS, limited Photoshop work</li>
<li>Not use any gradients</li>
<li>Not use any Photoshop noise!</li>
<li>Have some bits of CSS "sugar"</li>
</ul>
<p>
The last rule, "sugar", I think of as something extra, something possibly new, that you might not see that often. I'll talk about it more later.
</p>
<p>
The first graphic I envisioned for the design was the name, Jribbble. I saw the "J" and the "b"s in a large, swooping style. A style that I would not be able to accomplish with a computer. I would need to use my hands. The vision I had for it was too large for a pencil or pen or marker. I grabbed my box of acrylic paints, brushes, my 14" x 17" drawing pad and spread them out on my definitely-not-for-painting-on Ikea coffee table. With a medium sized brush and Primary Magenta I scrawled out a couple of "Jribbble"s as well as some random strokes. The toothbrush you see in the photo I used for making paint spatters and for getting paint all over the damned place.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post_image_jribbble_painting.jpg" alt="Jribbble painting" />
<figcaption>The start of the Jribbble redesign; a hand-painted logo.</figcaption>
</figure>
<p>
After the painting I took the "Jribbble" I liked most into Photoshop and fiddled with it a bit. I isolated the color from the background, applied the Twirl filter a few times to give it more character and added some of the random strokes and splatters around it for a more haphazard look. In Photoshop I set up a 958px, 12 column grid to work off of. For the banner and assorted page headers I selected <a href="http://typekit.com/fonts/botanika-mono-web">Botanika Mono Web from Typekit</a>. I don't own that font so I have to use a very advanced and elegant method for playing around with initial type layout. I input the text I need into Typekit's Type Tester and take screenshots of the text from there. See, fancy ain't it?
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post_image_jribbble_design.jpg" alt="Jribbble design" />
<figcaption>Getting out of Photoshop as soon as possible, this was the extent of the design accomplished with the graphics monolith.</figcaption>
</figure>
<p>
The image above is the extent of the design accomplished with Photoshop. From that point on the rest of the design was created with good ole' CSS. Just like I have always done in Photoshop I went through quite a few style iterations; colors, type sizes, positioning, but instead of spending that time in Photoshop and only moving to the browser when it was "final" I used the medium that would be delivered to create the design. Designing in the browser, it feels good man.
</p>
<h2>The flexibility</h2>
<p>
To accomplish my flexible grid, I started with a overall width of 80% on the content area. That number was somewhat arbitrary, I started with 100% and decreased the number until I felt there was a comfortable amount of room between the edge of the browser and the beginning of the content. I am setting a <code>max-width</code> of 1280px on the content area. Any wider than that and things started to get a little unwieldy; line-lengths were too long and in general things just didn't look good. At browser widths of 768px or narrower I wanted to gain back some of the margin so I used a media query to increase the width to 95%.
</p>
<pre><code class="language-scss">#content {
<p>margin: 0 auto;
max-width: 1280px;
width: 80%;</p>
<p>@media screen and (max-width: 768px) {
width: 95%;
}
}</p></code></pre><p></p>
<p>
<em>Quick note on the media query. I'm using <a href="http://sass-lang.com/">Sass</a> which allows for nested styles. You'll see more of this in later code examples.</em>
</p>
<p>
In a number of areas of the design I use a two-column layout. To determine the widths of each column I used the oh-so-helpful formula that Ethan describes in <abbr title="Responsive Web Design">RWD</abbr>: <br />
</p>
<pre><code class="language-clike">target ÷ context = result</code></pre>
<p>
For the columns I use the starting width of 958px as a context then determine a pixel value based off the width of each of my grid columns, 69px, to come up with widths like:
</p>
<pre><code class="language-scss">.method-description {
h3 {
float: left;
width: 40.1878914%; /_ 385 ÷ 958 = .401878914 _/
...
}
<p>p {
float: right;
...
width: 56.4718163%; /_ 541 ÷ 958 = .564718163 _/
}
}</p></code></pre><p></p>
<h2>More on media queries</h2>
<p>
Throughout the design I used media queries to add more fine tuning of styles that I couldn't accomplish with percentages alone. I decrease font sizes, switch to single column layouts and make other miscellaneous tweaks as the browser width decreases or increases to specified break points. For cross browser media query support I used the <a href="https://github.com/scottjehl/Respond">Respond.js</a> poly fill. It works great, media queries in IE7, you can't beat that with a stick! I needed to make some specific enhancements based on features as well so I used <a href="http://www.modernizr.com/">Modernizer</a> for feature detection. I included Respond.js in my Modernizer build.
</p>
<h2>The good stuff, the sweetness, the sugar</h2>
<p>
How does a die-cut, scaleable background image sound? I thought it sounded pretty rad. I'll explain. I had the idea that I wanted the "Jribbble" graphic to be different colors at different times. One way to accomplish that would have been to make a number of different images in the various colors that I wanted. That would work, but I thought I could do it a more efficient way. What I did was create an image that had a set background color of <code>#ececec</code> with the text itself transparent allowing for a background color to show through.
</p>
<figure>
<img src="https://tylergaw.com/articles/assets/post_image_jribbble_transparent.jpg" alt="Jribbble background image" />
<figcaption>
The die-cut .png used to create the color-shifting Jribbble logo.
</figcaption>
</figure>
<p>
With die-cut taken care of, let's look at scaleable. At its base size of 1149px by 663px the logo graphic is pretty large. At medium to large browser widths, this works fine, as the browser width decreases or increases the graphic starts to lose its intended effect and also starts to cause readability issues for other page elements. <code>background-size</code> to the rescue!
</p>
<pre><code class="language-scss">#bigAssJribbble {
background-image: url(../images/branding-die-cut.png);
background-repeat: no-repeat;
background-position: 15% 100%;
-moz-background-size: 190% 140%;
-webkit-background-size: 190% 140%;
-o-background-size: 190% 140%;
background-size: 190% 140%;
padding-top: 45%;
position: absolute;
width: 100%;
}</code></pre>
<p>
A couple things are going on here. It's important to note that the branding-die-cut.png image is much wider than it needs to be. The graphic is flush left and the right side extends ~1049px and is filled with our background color <code>#ececec</code>. I do this so I am able to position the background image off the left side of the browser and not leave a gap on the right side. If there was a gap that the image didn't cover, any background color would show through where I didn't want. The width of the element is set to 100% so it is always the size of the browser. The <code>background-size</code> property does exactly what it says, it scales the background image to the percentages that I used. There isn't a science to those numbers, I just tweaked them until they looked right.
</p>
<p>
Another cool thing in that block of CSS is the <code>padding-top</code>. Since an element has to be taller than 0px for a background image to show and I didn't want to set an explicit height due to the changing graphic size I used the percentage padding to give the element a height. Again, no science with that number, just changed it until it worked.
</p>
<p>
Something that's not accounted for in the above CSS is the background color, the color that should show through the die-cut image. This is where I started having a lot of fun. I set up 11 media queries at different pixel max-widths from 320px to 2360px, at each of those I set a different background color for the logo as well as a number of different elements on the page. As the browser width increases or decreases the background color changes. Another little extra I use is a CSS transition to smoothly change from one color to another on each of the chameleon-like elements. An example element with a media query colorway:
</p>
<pre><code class="language-scss">#bigAssJribbble {
-moz-transition: background-color 0.2s;
-webkit-transition: background-color 0.2s;
-o-transition: background-color 0.2s;
transition: background-color 0.2s;
}
<p>@media screen and (max-width: 480px) {
#bigAssJribbble {
background-color: #4400ff;
}
}</p></code></pre><p></p>
<p>
I didn't get incredibly specific with what colors I chose. I started with a base of <code>#ff0066</code> and then apply cooler colors as the width decreases and warmer as it increases.
</p>
<p>
That's CSS sugar in my view. It's not something that everyone will see, but it's just a little extra for those that are looking and into that sort of thing.
</p>
<h2>That's all</h2>
<p>
All in all a very fun process full of experimentation and learning for the sake of experimentation and learning. It probably took too long, so it goes. Side projects live in the off hours, the small windows of time before and after the "real" work and in the hours when you probably need to be sleeping.
</p>
<p>
Again, the completed site lives here <a href="http://lab.tylergaw.com/jribbble">http://lab.tylergaw.com/jribbble</a> and if you're interested in more of the code, fork it from its Github repo <a href="https://github.com/tylergaw/jribbble.com">https://github.com/tylergaw/jribbble.com</a>.
</p>
<p>
<i>Thanks for reading</i>
</p>
All Talkie Talkie2010-05-27T00:00:00Zhttps://tylergaw.com/blog/all-talkie-talkie/<p>
</p><figure>
<a href="https://lab.tylergaw.com/whatsgoingonwithcss">
<img src="https://tylergaw.com/articles/assets/post_image_whatupcss.jpg" alt="View the presentation slides" />
</a>
<figcaption>
View the slides
</figcaption>
</figure>
<p></p>
<p>On Tuesday, the 25th I presented an introduction of CSS3 features to a great group of folks at <a href="http://refreshnyc.org/">Refresh NYC</a>. The new features included in CSS3 are something that I'm really interested in and very excited about so it was a good opportunity to really dig in and learn a whole lot about them in a short amount of time. For my slides I decided to not take the Keynote route and instead decided to create the presentation as HTML, CSS, and Javascript. It turned out great, not only did I get to explore the sweetness of CSS3 for the sake of presenting the information, I also used a lot of the features I was talking about to build the presentation.</p>
<p>This was the first time I had put together a presentation like this and got up in front of a crowd to present it, and I loved every minute of it. You really gain a deeper knowledge of a topic when you are trying to explain it to someone else. I'm definitely going to do this again in the near future and if you've had any interest in doing so I would highly recommend it.</p>
<p>
<i>Thanks for reading</i>
</p>
You Did What with What?2010-04-06T00:00:00Zhttps://tylergaw.com/blog/you-did-what-with-what/<figure>
<a href="https://lab.tylergaw.com/themanfromhollywood">
<img src="https://tylergaw.com/articles/assets/post_image_hollywood.jpg" alt="View the demo" />
</a>
</figure>
<h2>Stuff You'll Most Likely Do Before Reading Any of This Post</h2>
<p><a href="https://lab.tylergaw.com/themanfromhollywood">View the demo</a> <a href="https://github.com/tylergaw/themanfromhollywood">Fork it</a>
<br /><em>You'll need Safari or Chrome to view the demo</em></p>
<p>There have been a ton of new, really exciting things going on with CSS over the past couple years and I've had an itch for a while to work with them. I started out just wanting to do a small project to get familiar with some of the newer, more advanced CSS3 features, and it quickly grew into a two month immersion into CSS3 land.</p>
<h2>Do they speak English in what?</h2>
<p>A few years back Jarrat Moody, a super talented designer, created a kinetic type <a rel="external" href="http://www.youtube.com/watch?v=HePWBNcugf8">motion graphics piece</a> based on a scene from the movie Pulp Fiction; that I am still floored by to this day. Since I saw that I have wanted to create a similar piece and even made a few attempts using After Effects, none of which ever really materialized.</p>
<h2>Let's do this!</h2>
<p>So I have my excitement over a new feature set, and inspiration from Mr. Moody, time to get to it. <a href="http://lab.tylergaw.com/themanfromhollywood">The Man From Hollywood</a> is a kinetic type animation using only HTML, CSS and Javascript. All of the animation is done using Webkit CSS transition, transforms, as well as standard CSS properties. Javascript just acts as a helper to turn CSS classnames on and off at the appropriate times. All of the content you see on the demo is HTML and CSS, no images were used. The audio clip is scene from the movie <a rel="external" href="http://www.imdb.com/title/tt0113101/">Four Rooms</a></p>
<h2>Is it practical?</h2>
<p>No, not really, but it's not really supposed to be. My goals were to teach myself the ins and outs of a new feature set, make something fun to look at, and hopefully help and/or inspire others to work on their own CSS3 awesomeness.</p>
<p>If you enjoy it, you should <a href="http://twitter.com/home?status=@thegaw%20is%20the%20awesomest%20dude%20on%20the%20planet%20because%20of%20this:%20http://bit.ly/9rOtd4">Tweet it</a> or share it on your blog or just whisper the URL into a co-worker's ear. That last one would definitely be the best.</p>
<p><i>Thanks for reading</i></p>
I Less Than Three IE62009-07-27T00:00:00Zhttps://tylergaw.com/blog/i-less-than-three-ie6/<p class="entry-intro">
I am not a fan of the Die IE6 Campaigns, can't get behind them. You will not see me sporting some goofy <a href="http://twibbon.com/join/IE6-Must-Die" rel="external">IE6 Must Die Twibbon</a> any time soon. I take a pretty unpopular position on IE6; I do not mind developing for IE6, working with it over the past few years has made me a better, smarter developer and I am going to miss it a bit when It's completely out of the picture. Whoa, look out, you're probably thinking I'm bat-shit crazy by now, but hear me out. I have reasons.
</p>
<p><strong>1. It pushes you to write proper, lightweight HTML markup: </strong>
IE6 has well-known issues with the box model, strange padding and margin issue, etc. One very good way to stop these potential CSS issues is to write good markup. Easy things like making sure you are writing semantic, valid markup and using <code><div></code> tags sparingly can knock out probably 95% of these issues that seem to be such a problem for so many. In my experience, most folks' CSS issues aren't CSS issues at all, they are HTML issues.</p>
<p><strong>2. It gives you yet another chance to get creative: </strong>
So what if IE6 doesn't support transparent pngs. Find a way to make that image a transparent gif or a jpg. Doesn't look as crisp as the png? So what, this is where progressive enhancement steps in, this is why we have conditional comments. Set up an IE6 stylesheet and dump all of the not so desirable CSS in there. Chances are most of your users are not on IE6 anyway, and if they are this can be a friendly little jab at them for using it. I took this route when building my site. I wanted to let anyone viewing my site with The 6 know that they are missing out so I created a completely stripped down version just for IE6, plus a nice reminder of why. It still works, you can access all the same content, it's just not pretty.</p>
<pre><code> <figure>
<img src='https://tylergaw.com/articles/assets/post_image_tgawie6_thumb.jpg' alt='tylergaw.com IE6 Screenshot'>
<figcaption>Users of IE6 receive a version of tylergaw.com that contains all the same content, but has a very pared-down aesthetic.</figcaption>
</figure>
<p>
<strong>3. It forces you to clean up sloppy code that other browsers ignore: </strong>
I've seen this a number of times when writing Javascript. Take a look at this bit of JS:
</p>
<pre><code class="language-javascript">var someCrud = ['item01', 'item02', 'item03',];
</code></pre>
<p>alert(someCrud.length);</p>
<p>
What's the length of the array? Firefox and Safari say 3. IE6 says 4. I'm siding with The 6 on this one, the comma at the end of the array is erroneous, it should not be there. IE6 sends the message that you should clean up your code by recognizing the extra item while the more advanced browsers ignore it and let you go on your sloppy way. This is just one example, I've come across a number of similar examples over the years, I'm sure others have too.
</p>
<p>
<strong>4. It keeps you on your toes: </strong>
If you're building websites you need to be flexible. Not all environments are going to be picture perfect, and knowing how to find solutions to problems that arise when working with a browser like The 6 is key. It's easy to just say that a browser is the sole reason for not getting something to function well. Another look at it may reveal that the first approach taken was maybe not the best. You just have to put on that thinking cap a bit.
</p>
<p>
<strong>5. It's good to have a nemesis: </strong>
What good is Batman without the Joker? You have to have someone or something that challenges you and makes your job harder, and sometimes downright frustrating. Web development is hard, that's why it's fun. How good a feeling is it to confront a problem in development and find some interesting way around it? I got a whole crappy article out of doing just that.
</p>
<p>
This viewpoint is 100% from that of a developer. As a user, I agree using IE6 would just be a nightmare. The security issues, the lack of features that modern browsers have, etc., etc. That is one merit that the Die IE6 campaigns have. But every time I see some Johnny Developer complaining about how hard it is to build websites for IE6 and how much they hate it, I can't help but think that that person is missing out on opportunities to use their noggin. And that they are a bit of a wimp. :)
</p>
<p>
After IE6 is gone some other browser will take it's place as the whipping boy. IE7? So, instead of just joining a campaign of wishing death on an inanimate object, I say learn to take away as much knowledge as you can from a less-than-ideal situation. Like Kuato said, "<a href="http://www.youtube.com/watch?v=xO1kKemcwYk" rel="external">Open your miiinddddd.</a>"
</p>
<p>
<i>Thanks for reading</i>
</p>
Ch-ch-ch Changes!2009-07-18T00:00:00Zhttps://tylergaw.com/blog/ch-ch-ch-changes/<p class="entry-update">
<b>Update:</b> This post was about the previous version of this site, <a href="http://v3.tylergaw.com/">version 3</a>.
</p>
<p class="entry-intro">
I slapped a fresh coat on the old website. Well, new paint, siding, frame, foundation...who am I kidding? I just burned down the old one and built anew on its smoldering ashes.
</p>
<p>
This is the third incarnation of tylergaw.com and just like the past two versions, I've had a damn good time working on this one. I've been able to take the time to work with so much amazing code that I've never really dug into before. This is the first full site I've built using the <a href="http://framework.zend.com/">Zend Framework</a> which has just made me realize how completely rad it is working with a rock-solid framework. It also makes me kick myself a little for not picking it up a long time ago. I'm integrating with a number of different third party APIs, this is also the first time I've built a site doing that. How freaking cool is working with well-written, well-documented APIs?
</p><p>
</p><p>With the visual design, I've really tried to push it in a direction that I think is, "me". This was one of those designs that I could really see in my head before I opened Photoshop or even put one thing down on paper. The biggest inspiration came from the cover art for <a href="http://en.wikipedia.org/wiki/Women_%28novel%29">Charles Bukowski's Women</a>. The whole time I was reading the book, I was constantly looking back at the cover. I don't know exactly what it is but it is just so damned cool. For this round, I started the design process the old fashioned way; I grabbed a six-pack, some colored pencils, a big-ass sketch book and then went to work.
</p>
<figure>
<img class="image-vertical" src="https://tylergaw.com/articles/assets/post_image_women.jpg" alt="Bukowski - Women" />
<figcaption>The cover of Charles Bukowski's "Women" was a main source of inspiration for the aesthetics.</figcaption>
</figure>
<figure>
<img class="image-vertical" src="https://tylergaw.com/articles/assets/post_image_tgaw2009sketch.jpg" alt="tgaw2009 Sketch" />
<figcaption>Starting a design with paper and pencil allows for quick iteration and time to really nail down the overall vision.</figcaption>
</figure>
<p>
I'm a big fan of organic textures and knew that I wanted to incorporate some into the design. When I sat down in Photoshop to start turning my sketch into a working design, I realized quickly that I was going to need some real-world help getting the organic look and feel that I was going for. When it's time for real-world textures I just start grabbing <a href="https://tylergaw.com/articles/assets/post_image_art01.jpg">anything</a>, and I mean <a href="https://tylergaw.com/articles/assets/post_image_samich.jpg">anything</a>, and throw it on the scanner. You never know what you might use that scan of an old broken toothbrush for. Just to be sure, scan it!
</p>
<p>
<i>Thanks for reading</i>
</p>