For years, whenever I’ve needed to create a CSS gradient I’ve this tool, which has served well. But of late, a new tool has come to the forefront. When you google “css gradient” the first result is now cssgradient.io. Today I checked it out and I was floored. Every now and then you enter a place that you immediately can tell has been carefully assembled by someone who really knows their craft. This is one of those places. The site can sing and dance. It’s built by a designer named Moe Amaya. He’s in a whole other league and I recommend checking out his other stuff too. Meanwhile, this gradient site is the site for all things gradients. It’s such a delightful app. Fantastic work.

The Dots Do Matter  

Fascinating story. You can argue that this is primarily Google’s fault, but if I were Netflix, I would change the product so you’re required to confirm your email address. If Netflix enforced that then this entire story could never have occurred. Confirming your email address is standard app behavior. I’m surprised Netflix doesn’t do this.

GitHub Requests a Username Change  

If I had to guess, the engineers were working through a weird bug or security patch issue and had a facepalm moment when they realized that the solution was to remove the malware username. It’s an embarrassing enough situation that I get why the company wouldn’t feel comfortable divulging to Joël the nitty gritty reasons of why the change was necessary.

Or maybe the change was just a stuffy corporate change dictated by the cyber security team. But I like to think it was the former.

Fibonacci in Javascript

Apple’s event in Chicago last month involved some fibonacci code. This intrigued me to refresh my memory at Wikipedia as to how the algorithm works, and then I played around with some JavaScript implementations for generating a user-specified sequence of fibonacci numbers. The terse two-line version looks like this:

const fib = (amount = 15, nums = [0, 1]) => {
  nums.length >= amount ? nums.length = amount : nums.push(nums[nums.length - 1] + nums[nums.length - 2]);
  return nums.length < amount ? fib(amount, nums) : nums;

In real life, that’s too terse; I’d have a hard time approving a PR for a real app that had that sort of code in it. Here is a more reasonable implementation:

const fibonacci = (amount = 15, numbers = [0, 1]) => {
  if (numbers.length >= amount) {
    numbers.length = amount;
    return numbers;
  numbers.push(numbers[numbers.length - 1] + numbers[numbers.length - 2]);
  return fibonacci(amount, numbers);

Moreover, if we’re not worried about the edge case scenario in which the user enters fibonacci(n) for all n < 2, then we can simplify the function to this:

const fibonacci = (amount = 15, numbers = [0, 1]) => {
  if (numbers.length >= amount) {
    return numbers;
  numbers.push(numbers[numbers.length - 1] + numbers[numbers.length - 2]);
  return fibonacci(amount, numbers);

If we want to find the fibonacci at a specific number, we can simply reference the above function in a convenient wrapping function like so:

const fibonacciAt = (nth) => {
  const numbers = fibonacci(nth);
  return numbers[numbers.lenth - 1];

Or we can modify the core function to achieve the same thing, like this:

const fibonacciAt = (nth = 15, numbers = [0, 1]) => {
  if (numbers.length >= nth) {
    numbers.length = nth;
    return numbers[numbers.length - 1];
  numbers.push(numbers[numbers.length - 1] + numbers[numbers.length - 2]);
  return fibonacciAt(nth, numbers);

What’s interesting is that this Medium article tries to demonstrate a performant recursive implementation to arrive at an nth fibonacci number, but its most performant implementation still requires n * 2 function calls to arrive at the answer; whereas mine only requires n function calls.

Windows Is Alive and Well  

If anyone thinks that Windows is on its way out, they’re not paying attention to the numbers. Windows has 81.9% market share in desktop operating systems. macOS has 12.55% market share. It’s downright amusing how much insular thinking there is on this subject.

Like Facebook and the President, Windows isn’t going anywhere.

Facebook’s Real Mistake  

Facebook wanted desperately to be a platform company. Thus years ago, it wrote an API that was very generous in how much data it gave out, in order to attract developers. Eventually it became clear to the company that it should instead be an advertising company, so it updated its API to be more protective of its data instead. A platform company wants to be generous with its data; an advertising company wants to be stingy with its data. During those intermediary years, companies like Cambridge Analytica took full advantage of the richness of Facebook’s platform-centric APIs.

When foreshortened, that backstory has given the appearance that Facebook, an ad company, is sloppy with its data. It’s allowed some people, including Tim Cook, to say that Facebook has been knowingly selling its users’ data abroad. That’s factually incorrect.

The whole reason this is in the limelight right now is because some people are in search for a scapegoat that neatly explains how Donald Trump became president, and Facebook is an easy target. It’s sheer hypocrisy however, because operatives from the Obama campaign made exactly the same use of this Facebook data, whilst the gatekeepers of media heralded it as a forward thinking use of social media. It’s a double standard.

That’s a high level tl;dr of this explosive episode of Exponent. It provides clarity to an issue that’s caused my eyes to glaze heretofore. I highly recommend listening to this in its entirety.

Amazon’s Under-Appreciated Philosophy

Among other things, The Everything Store is an eye-opening read into the business philosophy at Amazon. As a core value, Amazon strives to put the customer first by selling products and services as inexpensively as it possibly can. Doing so allows a few things:

  1. It frees the company from products that create such high profit margins that the company can’t venture into lower-profit markets for fear of displeasing its shareholders. The end result of a company with exorbitantly high profit margins is that eventually the company becomes risk-averse and consequently uninnovative. It might take decades, but it’s ultimately inevitable, especially as the vision of the original founders wears away with their departure.
  2. It frees the company from the competition that results from selling products with high profit margins. Lower profit margins mean other companies are less likely to be interested in hotly pursuing those markets.

Amazon is a somewhat boring company in comparison to its competitors. I’ve been listening to an audio recording of The Everything Store on my runs and commute and it’s not an exciting book, really. But in terms of customer satisfaction, Amazon is soaring at the #1 spot while Google and Apple are dropping in reputation. Meanwhile Amazon has closed the gap in market cap to most of its competitors and is arguably on a trajectory to become the most valuable company in the world.

A company whose philosophy is to charge inordinately high prices will initially succeed if its products are inordinately superior. But eventually the competition catches up, allured by the lucrativeness of the markets. Meanwhile, that company has to keep charging high prices for all new things it creates, because that’s the playbook it’s created for itself and the landscape it’s taught its shareholders to expect. It’s a fundamentally flawed, non-sustainable philosophy compared to Amazon’s. The pricing structure alienates customers and is built on the hubris that the company can achieve and — this is key here — sustain inordinate superiority in all markets. That just doesn’t hold up indefinitely. Eventually the competition arrives and nibbles at the edges. Eventually the cookie crumbles.

Jeff Bezos’ company has the high ground. This is going to become much more clear to everyone over the next decade.

That Building Looks Familiar  

A. G. Sulzberger, writing for the New York Times in 2011:

As the anniversary of Sept. 11 approaches, the energy firm that owns the building, the Williams Companies, remains so concerned about the similarity that it has tried to keep the connections secret. Even longtime employees making their way inside still take care not to let their eyes linger upward too long. Some cringe at the sound of passing planes, with one saying, “It makes your heart stop.”

I have a direct view of this building where I work. It’s just a few hundred yards away. I hadn’t realized until this week that it was designed by the same architect who built the World Trade Center. No wonder the building looks so similar.

iOS Bugs Appearing in Apple Ads  

If there was any doubt that the golden era of Apple was over, this is the nail in the coffin. The company will continue to achieve great things for some time to come much in the same way that Holywood continued to flourish subsequent to its golden era. But Apple is not the same perfectionist underdog for which its original enthusiasts were first attracted to it.

Death by Self-Driving Cars  

More people die in the U.S. from opioids (prescription painkillers) and heroin than from firearms. In 2015, nearly 4 times as many people died from drug overdoses than from guns.1 If we derive our morals from desired outcomes2 and buy into the notion that more gun control will reduce gun violence, then to be consistent, for every protest we make that there should be more gun control, we should protest 2-3 times as often in favor of outlawing painkillers.

I don’t know anybody who does that. I’m guessing you don’t either.

My point is this: it may be that humans are more dangerous behind the wheel than are computers. But I bet 20 years from now, computers will slip up and still kill pedestrians, such as in this tragic story from The Verge. Even if computers are 4x safer than humans behind the wheel, the public won’t allow it. There’s something about the computer-driven deaths that’s sickening and perceptively avoidable in ways that the human-driven ones aren’t.

  1. I’d be willing to bet that about half of Americans would guess that this ratio is roughly flipped. In doing so, they’d only be reflecting what they’re directionally led to believe by mainstream media. ↩︎
  2. As opposed to absolute morals that govern, as a matter of secondary consequence, social outcomes; which in broad strokes is the more correct approach. ↩︎

Yearly 1.2  

Yearly 1.2 is now available for download in the iTunes Store. The biggest change I made was enabling a custom start date other than January 1 for the reading schedule (a request I’d received from friends and strangers alike). For the settings panel I implemented SlideMenuControllerSwift, which was really a joy to work with.1

While I was at it, I added a few more things to the settings panel, including a handy link to go to the app’s Settings for notification management, as well as a link to iTunes for leaving a review.

One new change in 1.2 that I didn’t mention in the (admittedly scanty) release notes is this: when you download the app for the first time, instead of just blindly showing a standard alert dialog requesting that you grant permission for notifications, I first show a custom dialog box that contains this message:

Yearly works best when you receive a morning notification. It silently appears on your lock screen at 5:00 AM and shows what your reading is for the day. To enable notifications, please tap “Allow” in the next window.

Underneath this custom dialog box is a single button whose title is, “Ok, got it,” upon which tapping inside brings up the aforementioned default dialog.

All of Yearly’s UI is built in Storyboard and I’m starting to seriously wish I’d taken the time to learn how to do it programmatically instead. At this point, whenever I want to rearrange anything, the number of constraints I have to redo is tedious. Sometimes I even have to dip into the XML source code of the Storyboard to remove, say, an outlet reference to a view that’s been added, hooked up, and subsequently deleted.2

  1. I’ve learned that you can’t judge a book by its cover when it comes to 3rd party code. The documentation screenshots for SlideMenuControllerSwift were a turn-off at first, but once I realized how smooth, reliable, and flexible its underlying UX was and that its UI was completely determined by what you brought to the table, I decided to give it a go. If I can be vain for a moment, I think the design of Yearly’s settings panel is better than the screenshots for SlideMenuControllerSwift and does a better job “selling” the library. ↩︎
  2. Perhaps the bigger lesson here is that unless you’re an iOS ninja, you want to do your design prototyping elsewhere, and only go into Xcode once you have a pretty solid idea of what you want to design. It doesn’t have to be drafted elsewhere to the pixel per se, but if you’ve got the basic building blocks nailed down before you start implementing them, you’ll save a lot of time. The pitfall I often run into is that I have exactly what I want to build in my head and think it’s all a solved problem, but then when I go to implement it, I’m forced to reckon with scenarios (relating to both UI and UX) that just don’t fly in real life. ↩︎

3 Days a Juror

Early February I received a summons in the mail to serve as a juror for the District Court of Tulsa County. My start date was March 5. This is a recounting of the three days in which I served.1

I arrived at the Civic Center Parkade parking garage in downtown Tulsa on Monday, March 5 at 8:40 AM, blaming the 10 minute tardiness to the backed up traffic at a dysfunctional light at 96th and Riverside. At the parkade, I asked the lady if parking would be free for me since I summoned for jury. She maintained that it would still cost me $5, which meant I’d be making $15 that day, not the $20 we were promised as reimbursement for our service.2

The car safely parked on the second floor of the garage, I stumbled about on foot downtown, unfamiliar with the area. I finally arrived at the courthouse at 8:50 AM, now a solid 20 minutes late. I was exhausted and weak with hunger, having run a 10K that morning with no breakfast. There was a long line outside the Tulsa County Courthouse thanks to the rigamarole of going through security. It was a crisp and rather windy 50°F, and people were wishing aloud that they’d brought coats as they shivered in line. The security setup was similar to an airport’s. When it was finally my turn to put my backpack through the scanner, an agent flagged it for containing a fork utensil, an item the courthouse had recently begun banning. My fork was intended for some grilled eggs which would serve as my breakfast once I had a moment to myself. The agent made it clear that he couldn’t throw away the fork himself; it was my responsibility to dispose of it outside the courthouse, which meant exiting and starting over. I walked the four blocks back to my car and wolfed down my breakfast, using my precious fork. Then I left the fork in the car, walked back to the courthouse, and started all over again at the back of the line.

By 9:40 AM I was finally in the courthouse basement, which serves as a waiting room for summoned jury candidates. There were hundreds of people down there, as well as a free wifi hotspot that couldn’t handle the load. I tried tethering from my smartphone but its connection was E (extended) down there in the concrete bunker. In the back of my mind I planned to arrive at 8:00 AM the next morning to beat the crowds, but I learned a lady next to me had arrived at that exact hour and been surprised to see the line already fully formed. It made zero difference apparently.

Around 10:30 AM we were given a 10-minute break and reminded that if we left the building, we would have to go through security again. I went to a vending machine, still faint with hunger, eager to get some chips or most anything. The vending machines only accepted cash, and I only had credit cards. There was an ATM next to it but ATM’s don’t accept credit cards. Luckily there was a little shop open next to the vending machines that accepted credit cards. I got a mediocre hot dog with cheese for $2.50. The shop was so slow and overwhelmed with jurors that I arrived back to the basement 5 minutes late, but lots of other people were still coming from break too, and nobody cared.

At 11:45 AM we were given a 90-minute lunch break and reminded that if we took our cars anywhere to eat, we’d have to re-enter the parking garage and pay an additional $5. As the room cleared out, I noticed that the wifi started working as its load went to manageable levels. I had to go eat lunch myself though, so after a few minutes of computer work on a side project, I walked the 4 blocks back to my car again. I’d begun noticing that the heels of my feet were hurting from a new pair of dress shoes. When I got back to the car, I realized that my also-brand new socks were wearing down right where the top back of the shoe met the heel, and they were soaked in blood. Worried, I took off my shoes and socks and discovered that my heels were rubbed raw. All of that walking had caused a real ruckus. After finishing my sandwich I limped back to courthouse basement, wincing with every step, trying to make it look like no big deal.

By 2:00 PM, stuck in a chair with white noise reminiscent of an airport terminal, with wifi intermittently usable, I had grown suicidal. I started to wonder if maybe the best course was to just hang all the defendants regardless of their status and release all of us prisoners from the basement. It was dreadful down there. A lot of people were on their phones, some were trying to nap, others were putting together jigsaw puzzles. It was crowded and difficult to get work done as we watched our lives tick away.

But then at 2:30 PM, a bailiff arrived and summoned a group of us into a courtroom for a criminal case. It involved a man accused of pointing a gun at someone else back in 2016. The judge excited us by promising that the trial would be over by end of tomorrow, or Wednesday morning by the very latest. She asked us some preliminary questions, a process called voir dire. The goal was to weed out from the pool of potential jurors anyone who might not be able to offer a fair assessment due to conflicts of interest, biases in favor of one party or the other, etc. One of the jurors recognized the defense attorney as someone who had defended his brother in a previous case, and this juror was consequently summoned to return to the basement as an illegible candidate for this hearing. The judge’s preliminary questions were over by 3:30 PM. We were told to return to the basement the following day no later than 9:30 AM, at which point our bailiff would take us back to the courtroom.

The next day, Tuesday, I put on some band aids on my bloody heels, and used a different pair of shoes that unfortunately also rubbed in the exact same spot. The line through security was much shorter this time, almost nonexistent. It dawned on me that since jury summons occur on Mondays and each day thereafter people are dismissed or told to come back at varying times throughout the day, the Monday line wait is probably always the worst.

Back in the courtroom at the appointed hour, the prosecuting attorney and the defense attorney questioned us about various things, continuing the voir dire process from the day before. We were asked whether we owned guns, where we kept them, if we were members of the NRA, and if we thought there was a responsible way to handle guns. A good half of the room were gun owners. Next, the prosecuting attorney asked if anyone would have a hard time convicting someone over purely circumstantial evidence. One fellow did, and the attorney said that they would like to dismiss him (each side gets to choose a total of 3 people to dismiss). Because this dismissal process is supposed to happen while the court is adjourned, the judge indicated that the attorney’s dismissal request was premature. I’d later learn that this was the very first trial in the prosecuting attorney’s career. After the questioning was complete, we took a 15 minute break. When we returned, the jury selection was announced. It was comprised of a total of 7 people — 6 jurors and one backup, called an alternate juror. I wasn’t chosen.

I’d learn the following day that the defense attorney had been at his profession for twenty years and that the jury acquitted his client of the charge. I have no idea if the man was guilty or innocent, but sometimes the winner is determined by who’s the better attorney, not by who’s telling the truth. When talking about this with another fellow juror, a bartender who had also been dismissed, and we both agreed that, though we’d heard none of the evidence of the case, we didn’t like the vibes from the defendant and suspected him to be guilty of the charge. As an aside, imagine if we’d been selected for that jury. And then imagine that this sort of thing really does happen all the time in criminal courts. Jurors affirm that they won’t make up their minds until they’ve heard all the facts, but from the very outset, they have intuitions about how they feel about a case. Of course they do. They’re already leaning towards a verdict. Nobody is truly objective, no matter how much they want to think that they are.

Those of us who weren’t selected for the case were told to return to the basement. We anticipated we’d be dismissed, done for the week, and done for the next 5 years.3 Alas, when we descended from the 5th floor around 11:15 AM and reported at the basement desk, we were told we could take a 10 minute break and then we had to return. There were still cases for that week that didn’t yet have juries, and everyone had to stay in the pool until all of the cases had their juries selected.

One thing I started noticing was that there were three types of people down in that basement. First, there were the down-and-outers, the bums who lived on Netflix all day long, who got out of bed assuming the day was a wash anyway, and who consequently didn’t seemingly mind being there in the least. Second, there were the upper middle class elitists, who made plenty of money and were kind of bummed that they had to be there, but were taking it in stride as they fielded important phone calls, who knew they wouldn’t really feel the loss of money that they’d otherwise be making at their cushy jobs. Third, there were the average hard working middle to lower middle class country boys, who were somewhere between upset and furious that they had to be there, and eager to get out as soon as they could, who didn’t have any hesitation loudly dropping lines like, “THEY’RE GUILTY” as soon as their names were called, in hopes that they’d be dismissed for cause before their plaintiff even arrived.

At 11:45 AM we were once again given a 90-minute lunch break. “It’s not very often you get to take a ninety minute lunch break,” the lady at the desk said over the intercom, trying to give us something to be grateful for amidst the misery of the basement. I met up with a friend working a few buildings down the street. We went to a company cafeteria, got some nachos, and exchanged notes of how our weeks were going. After returning back around 1:00 PM, I opened my MacBook Pro and got back to work, doing some freelancing on a WordPress project. We were told that there was one more jury that needed to be assembled and that once that was complete, the rest of us were free to leave the courthouse for good. About 2:00 PM they started calling the names for this final panel. I listened as the names rattled off, one by one. And then they called mine.

I cringed when I heard it, knowing that this meant I’d have one more day to go at a bare minimum. A cowboy who’d been in the pool for the previous criminal trial, who had been particularly eager to leave and had gone on and on about his gun collection in hopes of getting dismissed from the case, and who’d had his wish granted, heard his name called too. As I picked up my backpack and prepared to meet the bailiff for this new case, I asked him with a rueful grin, “Why is this happening to us?” His loud, grim answer was, “I guess we just got f****** lucky.” Once we were all assembled, the bailiff told us that he had some “good news,” which was that the trial wouldn’t start until the following day at 2:00 PM. You could hear the jurors grumbling, wondering what was good about this news. As far as we were concerned, it simply meant an extension to our jail time.

The bailiff revealed that this would be a civil case, and I could tell from the number of juror candidates for this trial that the jury would be a full 12, not 6 like the previous criminal case.

Two or three people, including the cowboy, approached the bailiff and told him they had a problem. They didn’t get paid except for days they actually worked, they said, and if they lost any more days of work at their regular jobs, they would stand to lose some serious coin for the month. The bailiff replied, “I’m sorry, but there’s nothing I can do about that. You’ll have to talk to the judge about that tomorrow at two o’clock.” “At two o’clock,” repeated the cowboy with a long face. He began plotting a scheme.

Wednesday rolled around, and this time I was finally forced to discard my new dress shoes and put on an old disheveled pair instead. They were the only shoes that didn’t murder my heels with every step. I arrived at the 5th floor of the courthouse by 2:00 PM and we walked into the courtroom. The case was a lawsuit in which someone had rear ended another driver. The voir dire process would go on to expose cause for removal for three jurors before the plaintiff and defense attorneys exercised their three respective strikes.

To start with, there was a man who worked for a lawn care business. He tried to make a case that if he was not excused to go mow his clients’ lawns, his company would lose those contracts, and since he was the newest employee at the company, he’d be the first victim of the resulting layoffs that he seemed so sure of. At that point he would be unemployed, and that wasn’t groovy because he needed to put food on the table for his wife and daughters. In other words, serving as a juror would be undue hardship. He made it clear that he had expected to be done with his jury duty by mid-week. The judge tried to be sympathetic but emphatically told him that jury duty typically lasted for one week and that the balance of regular work and civic duty as juror was something to which everyone was subject. Mr. Lawn Mower wasn’t going to get off that easy.

After hearing this rant, the cowboy from the previous day offered a better excuse for himself. He said that yesterday he had gotten word that his uncle had passed away and that the funeral would be held tomorrow, Thursday. I was floored when I heard this. He hadn’t said anything about this to the plaintiff yesterday, I thought. What’s going on here? The judge told him that she was sorry for his loss, and excused him. Later in the hallway, I asked a fellow juror if he believed the cowboy’s story. He laughed and said it seemed suspicious. It was the perfectly concocted story. The cowboy had chosen the death of an uncle — a family relation that’s not too far removed to be deemed trivial, but not too close to raise eyebrows. It was the perfect sweet spot; too sweet. What’s the judge to do, heartlessly demand a death certificate as proof?

Maybe the story was true; I’ll never know for sure. But the previous day, this very cowboy had been complaining about how he needed to get back to earning real money, and then, just like that, sometime after Tuesday at 3:30 PM, his uncle had conveniently given up the ghost, a story he offered only after it was clear from Mr. Lawn Mower’s unsuccessful rant that a work-related concern wasn’t a sufficiently undue hardship for which to be excused from jury.

And then there was the hispanic gentleman who stated he had been on the faulty end of a car collision and that as a result of this experience, it would be impossible for him to be fair in the trial, and that he was therefore unqualified to serve as a juror in the case. I’m still not sure if he was using this purely as an excuse to be done with jury for the week or if he was genuinely being that impossible about it. I’m leaning towards the former explanation. Like all of us at that point, he just wanted to be done. We all knew that this was the final case for the week, so being dismissed from this particular courtroom meant being dismissed from the courthouse. In response, the plaintiff attorney (the one suing for damages to the defendant for his client’s sustained ongoing injuries, allegedly from this long-ago car collision) requested that the judge dismiss the juror for cause. The judge glanced at the defense attorney, who motioned that he was in agreement. And that was the end of that juror. He picked up his things and walked out.

After the judge asked some questions and we took a 15-minute break, the attorneys began their questioning. The plaintiff attorney had the burden of proof, so he started first. One of the first things he did was address Mr. Lawn Mower with his southern drawl. “I’m really getting the feeling that you don’t want to be here,” he said as kindly as that sentence can be said. Mr. Lawn Mower sighed, “I think everybody here has that feeling.”4 “Are you able to give this case your full attention over the next couple of days, or is your mind constantly going to be drifting to your work?” “I think about my work all the time. Especially so when I’m at a place like… this.” The plaintiff attorney knew not spend one of his three strikes on someone who could be dismissed for cause. At the consent of the judge and the defense attorney, he relieved Mr. Lawn Mower of his juror duty, and the latter shuffled out, clearly grateful that someone had finally freed him from the courthouse cage, albeit grieved that it had taken this long. As he walked out, a sense of relief swept through the whole room. You could feel it.

After the attorneys finished their voir dire, we took a 15 minute break whilst they selected the final jury. In the hallway, I chatted with a fellow juror who was a bartender — the same aforementioned bartender — about what our chances were of getting selected. Both of us were hoping we wouldn’t get selected so we could go home and be done with it, but we wanted to go about it more classily than those three who had been dismissed. After all, it would be downright embarrassing to have to explain to your family and friends that you were dismissed for cause.

The bailiff summoned us back in the courtroom a few minutes shy of 5:00 PM, and the judge announced the jury. As she called out the names, neither Mr. Bartender nor I were selected. But there was one little problem when she was finished: there were only twelve people in the jury seats. The alternate juror had not been announced. An attorney pointed this out to the judge and they huddled around the desk, comparing notes to see who the 13th person was. Finally they figured it out, and alas, it was Mr. Bartender. He walked to his juror seat, sat down, and looked back at me, grinning sheepishly. I grinned back. He would have a very long Thursday and Friday. This was going to be a boring lawsuit for him; since it was a civil case and not a criminal one, only 9 out of the 12 jurors would have to come to an agreement, but because he was the alternate juror, he likely wouldn’t even get to contribute to that verdict.

All I knew was that it was 5:00 PM, I’d served 3 days as juror, been in two courtrooms for two cases, been selected for neither one, and it was time to go home. Those of us who had not been selected walked out of the courtroom and celebrated in the hallway that we had not been chosen. We were free.

I went in search of an elevator.

  1. Drinking Caffeine is a technology-focused column but I’m realizing that vivid, unique tales are also interesting and hard to pass up. Also, for full clarity, the trials in which I was made privy have been completed. Everything in this story is non-sensitive information. Still, you’ll notice I’m vague with the names of pretty much everybody in here. That’s attributable to the simple fact that I’m terrible with names. ↩︎
  2. I would later learn that at some earlier point, the court had been tasked to decide between either providing free parking or reimbursing for milage, and it’d opted for the latter. I also learned that mileage reimbursement is calculated automatically based on your documented address of residence. ↩︎
  3. If you’re summoned to jury any time during the next five years after a previous summons, you are excused. ↩︎
  4. His answer here was vague. I’m not sure if he meant, “Everyone here has the feeling that I don’t want to be here,” or, “Everyone here has the feeling that they themselves don’t want to be here.” At first I thought he meant the former, but upon reflection I think he meant the latter. ↩︎

What Good Is Your Mac Doing You?

When a user visits your web application, is it clear to them that you must’ve designed it on a Mac?

We say we use Macs because we deeply care about the user experience, but then all to often, we go and use those Macs to design web applications with UI / UX that could’ve been achieved using a Windows machine.

There are people out there right now building better websites using Windows than many with macOS. Developing on a Mac is not a guarantee that you’ll build something great. Your app’s polish is dependent first and foremost on your hand, not your paint brush. If you’re determined, achieving greatness apart from the Mac is within grasp.

Don’t hide behind the excuse that you can’t get your work done on anything other than macOS. Windows 10 has bash support, VSCode, Chrome, Slack, and iTunes. Most web developers don’t use any apps or software that are actually unique to the Mac.

If your Mac isn’t making a material difference in the quality of your output, you need to stop and ask yourself who you’re kidding. If your app is just as mediocre as everyone else’s, then you simply paid an awful lot of money for a pretty piece of aluminum. There’s one and only one person who’s benefiting from that aluminum.


Nathan Kontny, CTO at Highrise, writing at Medium:

So I made Trick A Journalist as satire on how bad marketing has gotten. But I also made it for a much more important reason.

It’s a Bad Marketing Honeypot. The people signing up for this, and unfortunately there’s quite a few, are banned from using Highrise.

In Shoe Dog nomenclature, I imagine that Phil Knight’s dad would call building TrickAJournalist.com “jackassing around” on the web, but still, this is awesome.

Calculate Your Average Interval Pace and Total Interval Distance for a Strava Run

Lately I’ve been implementing Jack Daniels’ Running Formula step-count interval workout. The way it works is this: you run at interval pace for 20 steps, then you jog for 20 steps. Then you run at interval pace 40 steps, then you jog 40 steps. You keep doing this until you do 200 steps of each pace, then you run another 200 of each, and wind your way back down: 180 interval, 180 jog, 160 interval, 160 jog, so on and so forth until you arrive once again at 20 of each.1 This run comes out to be between 3 and 4 miles in total distance and about 2 miles of it is at interval pace. If you’re running 20-30 miles per week, this is a good amount of interval running for that week.2

Once your activity is uploaded to Strava, you’ll see a zipper pattern of a fast lap, a slow lap, a fast lap, a slow lap. Lurking amongst this data are two things that Strava doesn’t reveal out of the box: the total distance of your interval-pace laps, and this total distance’s average pace.3 Everything you need to compute these two data points exists; you just need a way to harvest it. This is where Strava’s API comes in.

First you’ll need an access token. To do this, create a Strava app4 and then head to this URL:


Change out the [clientID] with your app’s client ID.

Once you’ve authorized your own app, you’ll get redirected to localhost, which unless you have a local server running will result in a “This site can’t be reached” Chrome page.5 That’s ok though, because your payload is the code parameter in the URL. Grab that and do a POST request to https://www.strava.com/oauth/token with these params:

  • client_id
  • client_secret

I like to handle POST requests like these using the Advanced REST client. If you’re using that for this, you’ll want to select multipart/form-data as your body content type in order to get this to work.

This POST will return a payload with your athlete information, but the piece you’re after is the access_token. Store this somewhere safe. You worked hard for it, it won’t expire, and you’ll reuse it later.

Now, get the ID of the activity you’re interested in. You’ll find the ID in the URL of the activity. E.g. https://www.strava.com/activities/1428717700 has an ID of 1428717700. Now go here to get the payload of your activity:6


Copy all the JSON from the response of this request, pop open your Chrome console, and write this:

const activity = [paste from clipboard]

Then run this in the console:

const totals = { distance: 0, seconds: 0 };
activity.laps.forEach(lap => {
  if (lap.pace_zone === 6) {
    totals.distance += lap.distance;
    totals.seconds += lap.elapsed_time;

totals.distance =  totals.distance * 0.000621371;


You now have, logged at the bottom of the console, the total number of miles and seconds of your interval pace. Head over to pace.martynchamberlin.com (or similar — there are a number of online calculators that achieve what that one does, so use your favorite) and plug in these numbers, and you’ll get your output. This is the process by which I can know that my run today contained a total of 2.15 miles at a 5:33/mi pace, even though the overall activity was 3.45 miles at a 7:46/mi pace.

Groovy, right?7

  1. I don’t keep track of my footfalls in my head, although you could do that if you had to. Instead, I manually lap each set of steps using my Garmin vívoactive 3, which conveniently lets you see your number of steps per lap and lets you manually create new laps. As an aside, it’s exactly this sort of functionality that Garmin offers and that Apple Watch doesn’t that explains why serious athletes still prefer Garmin. ↩︎
  2. Jack Daniels recommends that your weekly amount of interval distance be the lesser of 10K or 8% of your weekly mileage. This is right in that sweet spot. ↩︎
  3. These two data points are immensely useful, because they let you know how you’re performing compared to previous workouts. Your total distance and total average pace aren’t as helpful for this sort of workout because those are influenced heavily by how quickly or slowly you’re jogging in between interval laps. The question we’re trying to answer is this: how much quality interval time did we get? How fast were the intervals and how much distance did they cover? In my mind, the answers to those questions are some of the most valuable things we can know when analyzing a completed interval workout. ↩︎
  4. This app is for your eyes only, so name it whatever you want, and upload a picture of your dog as the app’s icon. As an aside, it’s bizarre to me that an icon is required when creating a Strava app. Why? ↩︎
  5. I assume you’re using Chrome for all this. 😛 ↩︎
  6. Strava access tokens are per-athlete which means you’ll only be able to do this on your own activities. ↩︎
  7. If you’re thinking, “This is quite a hassle and this entire process should be automated in software,” you’re on the right track. I eventually want to build this out similarly to how I built out distance.martynchamberlin.com, but it all takes time, and right now I’ve got too many other irons in the fire. So for now, computing this number takes some elbow grease. Our only consolation is that the coaches and runners who lived pre-Strava and pre-Garmin must’ve done an awful lot of number crunching by hand on yellow notepads. I don’t envy those days. ↩︎

The Stack Overflow Question That Led to Ross Ulbricht’s Arrest  

Moments after posting this question, Ross Ulbricht changed his email address on Stack Overflow from one in which his name was associated to the fictitious “[email protected]” Stack Overflow keeps track of all previous email addresses used, and when an IRS agent approached Stack Overflow with a subpoena, the company provided the original email address, thereby revealing the identity of the user.

Ross Ulbricht is in jail for life for masterminding the Silk Road website. This Stack Overflow post was a critical clue.1

  1. I highly recommend reading American Kingpin for the full story. ↩︎

Michael Lopp’s iPhone Home Screen  


Strava. Because smaller more connected [villages] are bringing us a healthier internet. I have a lot to say about Strava in a future article. I pay a subscription fee for Strava.

It warms my heart that the Vice President of engineering at Slack has Strava on his home screen.

Five Minutes and Fifteen Seconds

Yesterday I wrote a somewhat cryptic comment on a Strava activity headlined 40.87 miles ahead of pace, as of this millisecond:

If you want a fun math problem, you can find out when I changed the title of this post, to an accuracy range of 5 minutes and 15 seconds. The only data points you need to derive this number are (1) my precise distance up to this point (2) my yearly goal. The latter is publicly available on my profile, but I suppose to get the former you’d have to hack my Strava account, since OAuth tokens are per-athlete. So never mind. 🙃

What in the world am I talking about? Well, I have a one thousand mile running goal this year. When we do the math in a 365-day year, that comes out to about 2.73973 miles that we must average every day.

1000 / 365 = 2.7397260274

In my headline, I gave away the fact that I was 40.87 miles ahead of pace at the time that I modified the headline. That’s rounded to the hundredth place. How many times per day would this number change? We can easily find out. We need to run 2.73973 miles per day, and since we’re keeping track of this in hundredths, we multiply that by 100:

2.7397260274 * 100 = 273.9726

This resulting 273.9726 is how many times per day that 40.87 number will regress by a hundredth of a mile. To answer how many times per day this would change in seconds, we find out how many seconds are in a day:

60 * 60 * 24 = 86400

And then we divide that by the number of times our pace will regress:

86400 / 273.9726 = 315.3600

In other words, that number will regress every 315.36 seconds, or every 5 minutes and fifteen seconds. This is why I said in the Strava activity that we could get within “an accuracy range of 5 minutes and 15 seconds.”

With that explanation in the backdrop, we can get to the fun math problem of which I spoke. We need my precise distance year-to-date in order to be able to solve that problem, and I have that precise distance. It’s 262824.7 meters. Let’s figure out how many miles that is:

262824.7 * 0.000621371 = 163.3116466637

Next we need to find out what my distance for the year up to this point should be in order for me to be on pace. To do this, we take 163 miles and subtract from it the 40 miles:

163.3116466637 - 40.87 = 122.44

Now all we have to do is find out how many 315.3600-second intervals occur from January 1 to the time that we’d need to have run 122.44 miles, get the total number of seconds that occurred in those intervals, and we’ll have our answer.

First, we need to establish the pattern. For the first (315.3600 / 2) seconds of the year, we need to have run .004978437341451041 miles. Since we’re rounding to the hundredths, that comes out to be zero miles. For the 315.3600 seconds following, we need to have run .01 miles. For the next 315.3600 seconds, we need to have run .02 miles.

Here’s some JavaScript to demonstrate. First some core functions, which are the backbone of my distance web app:

const amountOfYearExpired = (time = new Date().getTime()) => {
  const startOfYear = new Date(`Jan 1, ${new Date(time).getFullYear()}`).getTime();
  const startOfNextYear = new Date(`Jan 1, ${new Date(time).getFullYear() + 1}`).getTime();
  const percentagePassed = (time - startOfYear) / (startOfNextYear - startOfYear);
  return percentagePassed;

const metersToMiles = (meters) => {
  return (meters * 0.000621371);

paceDelta = (currentDistance, goal, timestamp = new Date().getTime()) => {
  if (!goal) {
    return 0;
  const percentagePassed = amountOfYearExpired(timestamp);
  const currentTarget = goal * percentagePassed;
  // if this number is negative then we're behind pace
  const output = metersToMiles(currentDistance) - currentTarget;
  return output;

Next we use them like so to prove the established pattern above.

// Subtract 1 to get the value at the very last millisecond before
// it switches to the second half of the first interval. Multiply
// by 1000 to get the value in milliseconds.
const firstHalfOfFirstInterval = (315.3600 / 2) * 1000 - 1;
const date = new Date(new Date('January 1, 2018').getTime() + firstHalfOfFirstInterval);
const delta = Math.abs(paceDelta(0, 1000, date));

The above delta variable has a value of 0.004999968290208016. Rounded to hundredths, that’s 0. If we don’t subtract 1 from firstHalfOfFirstInterval, we get exactly .005. Rounded to hundredths, that’s .01. It stays at .01 for the next 315.3600 seconds:

Math.abs(paceDelta(0, 1000, new Date(new Date('January 1, 2018').getTime() + ((315.3600 / 2) * 1000 - 1) + 315.3600 * 1000)))
// outputs 0.014999968290208016

And then once we re-supply that one final millisecond, we arrive at .02 when rounding to the hundredths:

Math.abs(paceDelta(0, 1000, new Date(new Date('January 1, 2018').getTime() + ((315.3600 / 2) * 1000) + 315.3600 * 1000)))
// outputs 0.015000000000000001

With this established pattern, all we have to do is multiply 12,243 (122.44 miles times 100, since our interval duration is per one hundredth of a mile increments, and then subtract 1, since the full 12,244 will round up and yield our upper limit) by our interval duration, and then add a half an interval to that (because of the aforementioned rounding pattern), and we’ll have the start time of our range.

12,243.5 * 315.3600 = 3,861,110.16

To get the end time of our range, we add an interval of 315.3600 from the previous formula:

12,244.5 * 315.3600 = 3,861,425.52

Let’s multiply those by 1000 to change them to milliseconds, and make dates out of them. Notice we subtract 1 millisecond from the finish since otherwise it’s technically in the next range:

const jan1InUnix = new Date('January 1, 2018').getTime();
// Wed Feb 14 2018 16:31:50 GMT-0600 (CST)
const start = new Date(jan1InUnix + 3861110160);
// Wed Feb 14 2018 16:37:05 GMT-0600 (CST)
const finish = new Date(jan1InUnix + 3861425519);

We can check these numbers to make sure they’re in the right range and representative of that range’s extreme lower and upper limits, which indeed they are:

// outputs 122.435 which rounds to 122.44, the lower limit
Math.abs(paceDelta(0, 1000, jan1InUnix + 3861110160));
// outputs 122.4449999682902 which also rounds to 122.44, the upper limit
Math.abs(paceDelta(0, 1000, jan1InUnix + 3861425519));

When we look at the activity, the start time was around 3:25 PM CST with a moving time of 53:27 minutes. That puts the finish time in the 4:18 PM CST range. We can know with confidence that somewhere between 4:31:50 PM CST and 4:37:05 PM CST, about 14 to 19 minutes after I completed the run, I modified the headline. That’s an accuracy range of five minutes and fifteen seconds.

How “Complex” and “Confusing” Is the Modern Web Really?  

Frank Chimero:

That breaks my heart, because so much of my start on the web came from being able to see and easily make sense of any site I’d visit. I had view source, but each year that goes by, it becomes less and less helpful as a way to investigate other people’s work. Markup balloons in size and becomes illegible because computers are generating it without an eye for context. Styles become overly verbose and redundant to the point of confusion. Functionality gets obfuscated behind compressed Javascript.

I want to make a few points about this piece overall.

  1. First, HTML tables and spacer gifs are not simpler than Flexbox and CSS Grid. More importantly, tables are not mobile friendly. They should never ever be used in modern web development.
  2. If someone’s building with “overly verbose” stylesheets that override each other as they cascade, they’re doing it wrong. If it’s difficult to understand what’s going on in a DOM because there are too many nested DOM nodes, they’re doing it wrong. If you want an example of a modern web app that avoids these pitfalls, look no further than my distance tracking web app for athletes.
  3. This complaint that, “Functionality gets obfuscated behind compressed Javascript” is a real kicker. In a 2002 web era, what would be different here? Is Frank wishing that the JavaScript simply didn’t exist, and by extension, the functionality? Or is he wishing that it existed on the backend, where you couldn’t view it whatsoever? Or is he wishing that it remained as JavaScript, albeit not obfuscated (thereby murdering load performance)? If the litmus test for whether an app is great or not is by how easy it is to decipher the source code, then all iOS apps are by extension the devil incarnate.
  4. Just because somebody publishes a 90-page ebook doesn’t mean that font faces are as complicated as all that. It just means that they’re trying to sell an $8 ebook and realize it’d be a harder sell if it were only 15 pages. Granted, anything to do with web technology can get complex if you choose for it to. If you’re working for Facebook, maybe you need a 90-page ebook to decide exactly how you want to serve up your fonts. But for the little web design projects that it sounds like Frank is doing, he doesn’t need it.
  5. A web page in 2018 can be as simple as it was in the early 2000s if you choose for it to be. The only thing that’s changed is that in the intervening years we’ve built tools that let you make things more complex if you need for them to be. The early 2000s quite frankly didn’t have the sophisticated web apps that we have today, and arguably they could not because the tools did not exist. It does not take a mandatorily greater amount of work to achieve the same things on the web today than it did 15 years ago. Rather, there is a greater depth of complexity awaiting you if you need it. In other words, we didn’t go from A to B. We went from A to A and optionally B if you need it. That’s progress, not regress. Frank writes of his past days back when the web was ostensibly great, “Perhaps I was fascinated by the potential of bashing together something in my room, hitting a button, then having it be ‘out there.‘” I still do that all the time, in 2018, and it’s still great. Again.

(Via Nick Heer, who is still struggling to remember the syntax of Flexbox, which tells me he’s not writing much Flexbox.)

Track Your Yearly Running Goal With This Simple One-Screen Calculator  

In an effort to cut needless fat out of my software budget, I’m letting my Strava Premium expire this spring. $60 a year for Strava Premium doesn’t sound so bad; $600 over the next decade sounds like a lot.1 Moreover, Garmin Connect shows 100% of the in-depth data that Strava Premium does, and Garmin Connect is free.2 The only thing I knew I’d really miss about Strava Premium is the ability to track how far I’m ahead or behind pace in my yearly goal of running 1,000 miles. So this morning I threw together this mobile-friendly HTML page that computes this for you. It’s localStorage cached, and therefore persistent yet anonymous. I’m 124 miles into my yearly goal, which puts me 26.54 miles ahead of pace as of this sentence.3

Usually the subscribers to a service like Strava Premium do it because they love the service and want to support it. What I’ve learned is that those people will always be around, and that there’s no shame in the other 97% of us using the software for free.

I love Strava greatly; I love reducing rows on my credit card statements even more. 💸

Update: The HTML page is now here. I’m now frying bigger fish at the previous URL. More on that later. Maybe.

  1. And with inflation and whatnot, who thinks Strava Premium or any other premium service for that matter will not be higher in ten years than it is today? ↩︎
  2. You have to own a Garmin device to use Garmin Connect, so it’s free in the same sense that Apple Messages is free. ↩︎
  3. I think Strava recalculates your ahead-or-behind amount every 24 hours or when you upload a new activity, whichever comes sooner. My “app” computes it based on the exact timestamp of page load, accurate to the millisecond. A 1,000-miles-per-year goal means you need to average roughly 2.7 miles every day. If your calculator refreshes its output every 24 hours, when does that refresh occur? At midnight? If so, what timezone? Regardless of when it is, your ahead-or-behind amount stays constant all day long and then jumps backwards 2.7 miles all at once every 24 hours. Nobody wants that. That number should be completely smooth in its regression, and lurch forward when you complete a new run. ↩︎

Let’s Bury the Hustle  

This is one of my all-time favorite pieces by DHH. It’s brutally savage and rings true.1 His 5-step process is an essential guide for how to nurture professional creativity.

  1. My only complaint about DHH’s writing style is his disrespectful and borderline obnoxious crassness. Using four-letter words to get a point across is a lazy copout to serious prose, and it achieves no service to the reader. ↩︎

Fewer Podcasts, More Audio Books

Listening to the same podcast feeds to glean new information is like squeezing the same grapefruits over and over again. Unless they’re usually high quality, well-researched shows, once you’ve heard a half-dozen episodes of a podcast, you’ve heard most of what their hosts have to say. After that, they’re just re-voicing their opinions, offering variations on well-worn themes.1 You’ll continue to get a drip here and a drop there, but what’s the point?

Stop listening to the same people say the same things, and renew your mind with fresh books.2

  1. Podcasts are the Hipsterville version of radio talk shows. They’re more sophisticated but they contain the same inescapable problem of repeating themselves over and over and over and… ↩︎
  2. The best place to do that is Audible. Contrary to misconception, Audible can be a pay-per-month service, but it isn’t a typical one like Zwift or Netflix in which you’re exchanging dollars for zero value during a month that you don’t use the service. Rather, if you don’t use Audible for a given month, your credits that you’ve accrued will rollover to the next month. Moreover, you’re not renting audiobooks from Audible; you’re buying them. You keep them for life; you have perpetual access to them. In short, the value that you get for $14.99 per month is equivalent to adding new books to your bookshelf on a monthly basis, except you’re adding them in an accessible form that guarantees you’ll actually get good out of them. That’s more than I can say for the scores of books, for which I paid good money, that are sitting on my shelves in various stages of neglect. ↩︎

Why We Forget the Books We Read  

Julie Beck, writing for The Atlantic:1

The lesson from his binge-watching study is that if you want to remember the things you watch and read, space them out. I used to get irritated in school when an English-class syllabus would have us read only three chapters a week, but there was a good reason for that. Memories get reinforced the more you recall them, Horvath says. If you read a book all in one stretch—on an airplane, say—you’re just holding the story in your working memory that whole time. “You’re never actually reaccessing it,” he says.

If you really want to recall a book, read it over an extended period of time. A few pages per day is better than fifty pages per weekend. Counterintuitively, the longer it takes you to read a book, the better retention you’ll have of it.

(Via Bob Ryskamp.)

  1. The URL of this Atlantic piece is delightful. ↩︎