<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>tag:www.gesteves.com,2006-06-08:</id>
  <title type="text">Guillermo Esteves</title>
  <updated>2023-07-15T18:31:36+00:00</updated>
  <link rel="alternate" type="text/html" href="https://www.gesteves.com/"/>
  <link rel="self" type="application/atom+xml" href="https://www.gesteves.com/feed.xml"/>
  <rights>© 2006–2026 Guillermo Esteves</rights>
    <entry>
      <id>tag:www.gesteves.com,2023-07-15:/links/2023/07/15/given-to-tri</id>
      <title>Given to Tri</title>
        <link rel="alternate" type="text/html" href="https://www.giventotri.com/"/>
      <published>2023-07-15T12:00:00-06:00</published>
      <updated>2023-07-15T18:31:36+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;It’s been a little over a year since I started doing triathlons, and one of the things I’ve been pleasantly surprised by is how much I enjoy writing about it. Whether it’s writing a &lt;a href=&quot;https://www.giventotri.com/tagged/race-report/&quot;&gt;race report&lt;/a&gt; or about a &lt;a href=&quot;https://www.giventotri.com/2023/02/10/review-technogym-myrun/&quot;&gt;crappy experience with a treadmill&lt;/a&gt;, I’ve found it’s a fun creative outlet.&lt;/p&gt;

&lt;p&gt;However, until now, I’ve been publishing all these posts here, in what’s ostensibly my professional work website, and there have been a few things I’ve wanted to write about and didn’t, simply because I didn’t want to flood it with triathlon content. So I’ve moved all of my triathlon posts into a new website, which I’ve called &lt;cite&gt;&lt;a href=&quot;https://www.giventotri.com/&quot;&gt;Given to Tri&lt;/a&gt;&lt;/cite&gt;, because I can’t stop myself from throwing &lt;a href=&quot;https://youtu.be/2SHHwmcAWZE&quot;&gt;a reference&lt;/a&gt; to my favorite band in every single one of my projects.&lt;/p&gt;

&lt;p&gt;I don’t know if my publishing frequency will increase significantly right away, but at least now I’ll have more freedom to do so. Maybe I’ll start writing more about training, or start a link blog, who knows. In any case, you can subscribe to the &lt;a href=&quot;https://www.giventotri.com/feed.xml&quot;&gt;RSS feed&lt;/a&gt; using your favorite newsreader or follow me on &lt;a href=&quot;https://mastodon.gesteves.com/@g&quot;&gt;Mastodon&lt;/a&gt; to stay up to date when new posts go up.&lt;/p&gt;

      </content>
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-09-16:/links/2022/09/16/gifs-are-now-for-boomers-and-cringe</id>
      <title>GIFs are now &quot;for boomers&quot; and &quot;cringe&quot;</title>
        <link rel="alternate" type="text/html" href="https://www.theguardian.com/technology/2022/sep/16/gifs-are-cringe-and-for-boomers-giphy-claims-in-meta-takeover-filing"/>
      <published>2022-09-16T09:00:00-06:00</published>
      <updated>2022-09-16T15:00:25+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;blockquote&gt;
&lt;p&gt;So now they are basically the cringe reaction image your millennial boss uses in Slack.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;figure&gt;&lt;img src=&quot;https://c.tenor.com/TNcZ5YTizqUAAAAC/sam-you-dont-mean-that.gif&quot; alt=&quot;&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

      </content>
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-09-15:/links/2022/09/15/welcome-to-the-new-verge</id>
      <title>Welcome to the new Verge</title>
        <link rel="alternate" type="text/html" href="https://www.theverge.com/2022/9/13/23349876/the-verge-website-redesign-new-newsfeed-blogs-logo"/>
      <published>2022-09-15T12:00:00-06:00</published>
      <updated>2022-09-15T18:00:03+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;The very last project I was involved in before leaving Vox is finally live. This new version of The Verge looks &lt;em&gt;awesome&lt;/em&gt; and I’m so happy to see it launch.&lt;/p&gt;

      </content>
          <category term="Web Development" />
          <category term="Work" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-08-31:/links/2022/08/31/heroku-discontinues-free-plans</id>
      <title>Heroku discontinues free plans</title>
        <link rel="alternate" type="text/html" href="https://blog.heroku.com/next-chapter"/>
      <published>2022-08-31T09:00:00-06:00</published>
      <updated>2022-08-31T15:09:03+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;blockquote&gt;
&lt;p&gt;In order to focus our resources on delivering mission-critical capabilities for customers, we will be phasing out our free plan for Heroku Dynos, free plan for Heroku Postgres, and free plan for Heroku Data for Redis®, as well as deleting inactive accounts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;End of an era. I wouldn’t be where I am today if I hadn’t had free spaces to experiment and learn web development when I couldn’t afford even the cheapest hosting. And I mean that literally—I got my job at 
Vox, which allowed me to stay in the United States, because I had my personal website and portfolio, including that old &lt;a href=&quot;https://www.gesteves.com/blog/2009/11/29/the-star-wars-opening-crawl-in-html-and-css/&quot;&gt;Star Wars in HTML &amp;amp; CSS&lt;/a&gt; demo, on a free Heroku account. It makes me sad to see one such space going away.&lt;/p&gt;

&lt;p&gt;I’m not sure where I’ll move my Heroku projects (both paid and free), but I’m currently looking at &lt;a href=&quot;https://render.com/&quot;&gt;Render&lt;/a&gt; and &lt;a href=&quot;https://fly.io/&quot;&gt;Fly.io&lt;/a&gt; as alternatives.&lt;/p&gt;

      </content>
          <category term="Web Development" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-08-17:/links/2022/08/17/form-workout-builder</id>
      <title>FORM releases a workout builder</title>
        <link rel="alternate" type="text/html" href="https://www.formswim.com/blogs/all/new-feature-build-your-own-custom-swim-workouts"/>
      <published>2022-08-17T08:00:00-06:00</published>
      <updated>2022-08-17T14:00:41+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;I’ve enjoyed using my FORM goggles for swim training, but one of the challenges has been finding workouts similar to the ones prescribed by my training plan (although FORM’s huge workout library has made that easier). Having a workout builder in the FORM app is a bit of a game changer, can’t wait to try it.&lt;/p&gt;

      </content>
          <category term="Fitness" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-07-29:/links/2022/07/29/useful-praise</id>
      <title>Useful praise</title>
        <link rel="alternate" type="text/html" href="https://aworkinglibrary.com/writing/useful-praise"/>
      <published>2022-07-29T10:00:00-06:00</published>
      <updated>2022-07-29T16:00:34+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;blockquote&gt;
&lt;p&gt;A regular practice of praising your colleagues builds goodwill and trust, helps to dispel imposter syndrome, and supports a team that can capably reflect on what it does well as well as where it goes wrong. Because being great is more than a matter of improving your weaknesses—it’s also about building on your strengths.&lt;/p&gt;
&lt;/blockquote&gt;

      </content>
          <category term="Management" />
          <category term="Work" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-07-27:/links/2022/07/27/the-quiet-glory-of-aging-into-athleticism</id>
      <title>The quiet glory of aging into athleticism</title>
        <link rel="alternate" type="text/html" href="https://annehelen.substack.com/p/the-quiet-glory-of-aging-into-athleticism"/>
      <published>2022-07-27T12:30:00-06:00</published>
      <updated>2022-07-27T20:27:08+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;blockquote&gt;
&lt;p&gt;How is it, at age 41, that I feel like my body can do more — and that I can take more joy in it — than ever before? I’m not faster, but I’m more resilient. I’m not doing as many overall miles, but I feel stronger. I love it more, and more feels possible. Sure, my knees are slightly more creaky, and I have to be keenly attentive to stretching and Theragunning and hydrating in a way I never was before. But exercise just generally no longer feels punitive or disciplinary. Instead, I feel something far more akin to curiosity. If part of me feels weak or tweaky, what’s struggling in other parts of my body and needs strengthening? And if I’m attentive to my body, if I’m legitimately kind to it, can it do more than I thought it could?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This quote really resonates with me. I followed a similar path to the one the author describes: The pandemic hit, I got really into Peloton at the end of 2020, then one thing led to another and now I’m training for a full Ironman triathlon next year. I found something similar along the way, that I’m stronger than I thought I was, and capable of harder things than I thought possible; in fact, &lt;em&gt;I don’t know what I’m capable of&lt;/em&gt;, so I’m trying to find out.&lt;/p&gt;

&lt;p&gt;That feeling extends beyond training, into the rest of my life, which is both powerful and liberating, but it’s also made me consider how other aspects of training, such as “active recovery” and “rest”, apply to, say, my work life. I can’t train at 100% intensity 100% of the time and expect to perform well, so why should I treat work that way?&lt;/p&gt;

      </content>
          <category term="Fitness" />
          <category term="Triathlon" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-07-27:/links/2022/07/27/a-four-day-week-at-five-days-pay</id>
      <title>A four-day week at five days&#39; pay</title>
        <link rel="alternate" type="text/html" href="https://prospect.org/labor/four-day-week-at-five-days-pay/"/>
      <published>2022-07-27T10:05:00-06:00</published>
      <updated>2022-07-27T16:09:50+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;blockquote&gt;
&lt;p&gt;While there is no data for the official U.S. trial yet, Schor did have some results from three months of the February 1st trial. Workers, she said, are experiencing “less burnout, less stress, better physical health, better mental health, people sleeping more, people having higher life satisfaction.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;small&gt;(Via &lt;a href=&quot;https://twitter.com/aworkinglibrary/status/1552319631343452160&quot;&gt;Mandy Brown&lt;/a&gt;)&lt;/small&gt;&lt;/p&gt;

      </content>
          <category term="Work" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-07-26:/links/2022/07/26/the-best-way-to-reheat-pizza</id>
      <title>The best way to reheat pizza</title>
        <link rel="alternate" type="text/html" href="https://www.popsci.com/story/diy/best-reheat-pizza/"/>
      <published>2022-07-26T12:30:00-06:00</published>
      <updated>2022-07-26T18:30:42+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;I’m a big fan of the reddit method described here, but I’m intrigued by the air fryer method, since my toaster over has a convection setting. I’ll have to try that next time I order from &lt;a href=&quot;https://pinkygs.com/&quot;&gt;Pinky G’s&lt;/a&gt;.&lt;/p&gt;

      </content>
          <category term="Food" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-07-25:/links/2022/07/25/the-apple-store-time-machine</id>
      <title>The Apple Store Time Machine</title>
        <link rel="alternate" type="text/html" href="https://departmentmap.store/timemachine"/>
      <published>2022-07-25T13:00:00-06:00</published>
      <updated>2022-07-25T20:44:08+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;Four different Apple Stores throughout the years, perfectly recreated in Unity, down to the smallest details. It faithfully replicates the sensation of wandering around an Apple Store, trying it figure out how the hell to check out.&lt;/p&gt;

      </content>
          <category term="Apple" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-07-22:/links/2022/07/22/telemetry-overlay</id>
      <title>Telemetry Overlay</title>
        <link rel="alternate" type="text/html" href="https://goprotelemetryextractor.com/telemetry-overlay-gps-video-sensors"/>
      <published>2022-07-22T13:03:00-06:00</published>
      <updated>2022-07-22T19:09:14+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;I recently bought a GoPro and I’ve been trying to figure out how to make those cool cycling videos where the data from the bike computer is overlaid on top. I tried using Garmin VIRB after seeing it recommended on various cycling forums, but I found it incredibly frustrating to use. Then I found this &lt;a href=&quot;https://goprotelemetryextractor.com/telemetry-overlay-gps-video-sensors&quot;&gt;Telemetry Overlay&lt;/a&gt; app, which is steeply priced at $150, but makes it a snap to overlay data from a FIT file on a GoPro video, with tons of customization options. Highly recommended despite the price; here’s a &lt;a href=&quot;https://www.youtube.com/watch?v=tPT6hadnChA&quot;&gt;test video&lt;/a&gt; I put together with it in just a few minutes.&lt;/p&gt;

      </content>
          <category term="Cycling" />
          <category term="Software" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-07-20:/links/2022/07/20/how-independent-writers-are-turning-to-ai</id>
      <title>How independent writers are turning to AI</title>
        <link rel="alternate" type="text/html" href="https://www.theverge.com/c/23194235/ai-fiction-writing-amazon-kindle-sudowrite-jasper"/>
      <published>2022-07-20T10:39:00-06:00</published>
      <updated>2022-07-20T19:01:52+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;Great story from The Verge, but the presentation is just mind-blowing. Incredible use the storytelling kit my former team at Vox owns and maintains.&lt;/p&gt;

      </content>
          <category term="Internet" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-07-20:/links/2022/07/20/using-dall-e-2-to-replicate-the-styles-of-well-known-portrait-photographers</id>
      <title>Using DALL·E 2 to replicate the styles of well-known portrait photographers</title>
        <link rel="alternate" type="text/html" href="https://twitter.com/triplux/status/1542529379485396995"/>
      <published>2022-07-20T08:00:00-06:00</published>
      <updated>2022-07-20T19:02:43+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;This is wild.&lt;/p&gt;

      </content>
          <category term="Art" />
          <category term="Internet" />
          <category term="Photography" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-07-19:/links/2022/07/19/instagram-is-dead</id>
      <title>Instagram is dead</title>
        <link rel="alternate" type="text/html" href="https://om.co/2022/07/18/instagram-is-dead/"/>
      <published>2022-07-19T20:55:00-06:00</published>
      <updated>2022-07-27T16:04:05+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;Om Malik:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Instagram’s transformation into QVC is now complete and absolute. Instagram is dead — or at least the Instagram I knew and loved is dead. It is no longer part of my photographic journey.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is why I’m happy to have &lt;a href=&quot;https://www.allencompassingtrip.com/&quot;&gt;my own photography website&lt;/a&gt;. Social networks come and go, but at least there I’ll always be in control of how my photography is presented. Perhaps it’s time to bring &lt;a href=&quot;https://indieweb.org/POSSE&quot;&gt;POSSE&lt;/a&gt; back.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;(Via &lt;a href=&quot;https://www.susanjeanrobertson.com/links/&quot;&gt;Susan Robertson&lt;/a&gt;)&lt;/small&gt;&lt;/p&gt;

      </content>
          <category term="Internet" />
          <category term="Photography" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-07-12:/links/2022/07/12/first-images-from-the-james-webb-space-telescope</id>
      <title>First images from the James Webb Space Telescope</title>
        <link rel="alternate" type="text/html" href="https://www.nasa.gov/webbfirstimages"/>
      <published>2022-07-12T09:30:00-06:00</published>
      <updated>2022-07-23T15:11:21+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;blockquote&gt;
&lt;p&gt;The dawn of a new era in astronomy has begun as the world gets its first look at the full capabilities of NASA’s James Webb Space Telescope, a partnership with ESA (European Space Agency) and CSA (Canadian Space Agency). The telescope’s first full-color images and spectroscopic data were released during a televised broadcast at 10:30 a.m. EDT (14:30 UTC) on Tuesday, July 12, 2022, from NASA’s Goddard Space Flight Center in Greenbelt, Maryland. These listed targets below represent the first wave of full-color scientific images and spectra the observatory has gathered, and the official beginning of Webb’s general science operations. They were selected by an international committee of representatives from NASA, ESA, CSA, and the Space Telescope Science Institute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Absolutely breathtaking. The level of detail in the images captured by the JWSS is nothing short of astonishing, especially when &lt;a href=&quot;https://www.webbcompare.com/index.html&quot;&gt;compared to Hubble&lt;/a&gt;.&lt;/p&gt;

      </content>
          <category term="Photography" />
          <category term="Science" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-06-15:/links/2022/06/15/internet-explorer-11-has-retired-and-is-officially-out-of-support</id>
      <title>Internet Explorer 11 has retired and is officially out of support</title>
        <link rel="alternate" type="text/html" href="https://blogs.windows.com/windowsexperience/2022/06/15/internet-explorer-11-has-retired-and-is-officially-out-of-support-what-you-need-to-know/"/>
      <published>2022-06-15T07:05:00-06:00</published>
      <updated>2022-07-23T14:38:11+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;blockquote&gt;
&lt;p&gt;After 25+ years of helping people use and experience the web, Internet Explorer (IE) is officially retired and out of support as of today, June 15, 2022. To many millions of you, thank you for using Internet Explorer as your gateway to the internet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;End of an era.&lt;/p&gt;

      </content>
          <category term="Web Development" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-04-17:/blog/2022/04/17/slack-heroku-cloudfront-lambda-at-edge-oh-my</id>
      <title>How to run Slack apps on free Heroku dynos seamlessly using CloudFront &amp; Lambda@Edge</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2022/04/17/slack-heroku-cloudfront-lambda-at-edge-oh-my/"/>
      <published>2022-04-17T00:00:00-06:00</published>
      <updated>2023-05-02T19:57:32+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>How I use CloudFront and Lambda@Edge functions to work around free Heroku dyno limitations.</summary>
      <content type="html">
        &lt;p&gt;I’ve spent the past few weeks rewriting a few of my old Slack bots, mainly because I wrote most of them years ago as custom integrations and wanted to learn how to build &lt;a href=&quot;https://api.slack.com/&quot;&gt;modern Slack apps&lt;/a&gt; (and also because building bots is just fun). In case you’re curious, the three apps I ended up building were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://github.com/gesteves/trebekbot&quot;&gt;Trebekbot&lt;/a&gt;, which sets up a perpetual game of Jeopardy! in your Slack channels.&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://github.com/gesteves/weatherbot&quot;&gt;Weatherbot&lt;/a&gt;, which posts weather forecasts in Slack.&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://github.com/gesteves/wordlebot&quot;&gt;Wordlebot&lt;/a&gt;, which shows stats about Wordle games in that Slack channel everyone seems to have where people post their scores.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(Update: Now that Heroku has &lt;a href=&quot;https://blog.heroku.com/next-chapter&quot;&gt;discontinued its free plans&lt;/a&gt;, I’ve taken down these bots. I’m leaving this post up for future reference, anyway.)&lt;/p&gt;

&lt;h3 id=&quot;how-slack-apps-work&quot;&gt;How Slack apps work&lt;/h3&gt;

&lt;p&gt;The basic flow of these Slack apps is largely the same:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Someone performs an action in Slack, like mentioning the app (for example, &lt;code&gt;@Trebekbot&lt;/code&gt;) or posting a slash command (like Weatherbot’s &lt;code&gt;/weather&lt;/code&gt; command).&lt;/li&gt;
&lt;li&gt;Slack makes a POST request to an endpoint on the app server with a payload describing the event. In my apps’ case, &lt;code&gt;/slack/events&lt;/code&gt; for events like app mentions and &lt;code&gt;/slack/slash&lt;/code&gt; for slash commands.&lt;/li&gt;
&lt;li&gt;The app enqueues some background job to do whatever the user requested, then immediately acknowledges response of Slack’s request with an HTTP 200 status. For events, the response body can be empty; for slash commands, you can return &lt;code&gt;{&quot;response_type&quot;:&quot;in_channel&quot;}&lt;/code&gt; in the body if you want the user’s slash command to be visible in the channel.&lt;/li&gt;
&lt;li&gt;Later on, the background job does whatever it needs to do, including, for example, posting a message in Slack with the results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Importantly, Slack requires that 200 status to be returned &lt;em&gt;within 3 seconds&lt;/em&gt;, or the event delivery is considered a failure. For slash commands, &lt;a href=&quot;https://api.slack.com/interactivity/slash-commands#responding_basic_receipt&quot;&gt;this results&lt;/a&gt; in a “timeout was reached” error being shown to the user; for other events, Slack &lt;a href=&quot;https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events&quot;&gt;retries up to three times&lt;/a&gt;, backing off exponentially. Slack strongly encourages the use of queues to handle events for this reason.&lt;/p&gt;

&lt;h3 id=&quot;my-problem&quot;&gt;My problem&lt;/h3&gt;

&lt;p&gt;That is all fine, except that I’ve been hosting all these apps on Heroku free dynos, because it’s easy to set up, and I’m lazy, and cheap. However, &lt;a href=&quot;https://devcenter.heroku.com/articles/free-dyno-hours#dyno-sleeping&quot;&gt;free dynos shut down after about 30 minutes of inactivity&lt;/a&gt; and wake up when they receive a new request, but unfortunately take a bit longer than 3 seconds to do so. That means that if someone uses one of these apps while the dynos are asleep, one of these things might happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If they post a slash command, the request will time out, the user will see that “timeout was reached” error in Slack, but then the dyno will finish waking up, enqueue the job, and successfully post the response. Not the end of the world, but I’d rather not show an error message to users, especially if the command is successful in the end.&lt;/li&gt;
&lt;li&gt;If it’s some other event, such as an app mention, the request will time out, and Slack will keep retrying until the dyno is awake, which might result in multiple background jobs enqueued, and multiple responses from the app in Slack. Again, not the end of the world, but kind of annoying. (If you’ve invoked &lt;code&gt;@Trebekbot&lt;/code&gt; and it responded with two or three games at once, that’s why.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The solution is simple: just upgrade the dynos to paid ones so they never go to sleep, duh. But again, I’m cheap, and frankly none of these apps see enough use to justify having dynos running 24/7. What to do?&lt;/p&gt;

&lt;h3 id=&quot;enter-cloudfront-and-lambda-edge&quot;&gt;Enter CloudFront and Lambda@Edge&lt;/h3&gt;

&lt;p&gt;Since I already had these apps behind CloudFront, I started wondering if I could somehow make &lt;em&gt;CloudFront&lt;/em&gt;, and not Heroku, return the 200 status Slack expects, so it doesn’t have to wait for the dyno to be up. Turns out, the answer is yes, with some tweaking of CloudFront’s configuration and a short Lambda@Edge function.&lt;/p&gt;

&lt;p&gt;First, I set up two origins in my CloudFront distribution, both pointing to Heroku, but one using the default origin response timeout of 30 seconds and the other using the minimum timeout of 1 second.&lt;/p&gt;

&lt;p&gt;Then, I set up two behaviors in the distribution: The Slack endpoints (&lt;code&gt;/slack/*&lt;/code&gt;) use the origin with the short timeout, and everything else uses the default one. That way, if someone hits, say, the app’s website while the dyno is asleep, it’ll render after a few seconds, when it wakes up. But if Slack posts to the &lt;code&gt;/slack/*&lt;/code&gt; endpoints while the dyno is asleep, CloudFront will &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html#DownloadDistValuesOriginResponseTimeout&quot;&gt;respond with a 504 status&lt;/a&gt; (Gateway Timeout) after just 1 second. If the dyno is awake, it’ll respond to the request as normal, before the 1 second timeout.&lt;/p&gt;

&lt;p&gt;Finally, in the &lt;code&gt;/slack/*&lt;/code&gt; behavior, I set up a Lambda@Edge function with an &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html&quot;&gt;origin response trigger&lt;/a&gt;, which checks if the response is a 504 status, and if so, replaces the response with a 200 status with the appropriate body. Here’s the entire function:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;js&quot;&gt;exports.handler = (event, context, callback) =&amp;gt; {
  let response = event.Records[0].cf.response;
  const request = event.Records[0].cf.request;

  if (response.status === &#39;504&#39;) {
    // If the request is due to a slash command,
    // return this JSON response so the command
    // is visible in channel.
    if (request.uri === &#39;/slack/slash&#39;) {
      response = {
        status: &#39;200&#39;,
        headers: {
          &#39;content-type&#39;: [{
            key: &#39;Content-Type&#39;,
            value: &#39;application/json&#39;
          }]
        },
        body: JSON.stringify({ response_type: &#39;in_channel&#39; })
      };
    } else {
      // Otherwise, just return an empty body.
      response = {
        status: &#39;200&#39;,
        body: &#39;&#39;
      };
    }
  }
  callback(null, response); 
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;What ends up happening is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A user performs an action in Slack, such as posting a slash command or mentioning the app.&lt;/li&gt;
&lt;li&gt;Slack makes a POST request to the corresponding &lt;code&gt;/slack/*&lt;/code&gt; endpoint, which sits behind CloudFront.&lt;/li&gt;
&lt;li&gt;CloudFront receives the request from Slack and makes the request to the origin, Heroku.&lt;/li&gt;
&lt;li&gt;If the Heroku dyno is awake, it receives the request, enqueues the background jobs, and returns a 200 response before CloudFront’s 1 second timeout is up. CloudFront returns the 200 to Slack, and Slack is happy.&lt;/li&gt;
&lt;li&gt;If the Heroku dyno is asleep, it begins waking up. However, after 1 second, CloudFront gives up and returns a 504 status. Meanwhile, the dyno finishes waking up and enqueues the background jobs.&lt;/li&gt;
&lt;li&gt;The Lambda function executes, and since the response status from the origin was 504, it replaces the response with a 200, and returns it to Slack (with the appropriate body depending on the endpoint hit). Slack is happy!&lt;/li&gt;
&lt;li&gt;A few seconds later, the background job runs, does whatever the user requested, and posts the result in Slack.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And with that, CloudFront is in charge of acknowledging receipt of Slack events when the Heroku dyno is asleep, users of these Slack apps don’t get any error messages or odd behavior in those cases, and more importantly, it’s saving me $7/month per dyno (and each of these apps has two!)&lt;/p&gt;

      </content>
          <category term="Web Development" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-04-15:/links/2022/04/15/weatherbot-for-slack</id>
      <title>Weatherbot for Slack</title>
        <link rel="alternate" type="text/html" href="https://weatherbot.gesteves.com/"/>
      <published>2022-04-15T11:05:00-06:00</published>
      <updated>2022-07-23T14:41:26+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;A weather app for Slack I just built, powered by the Dark Sky API. I spend so much time in Slack, being able to check the weather forecast from there seems like a perfectly natural thing to do.&lt;/p&gt;

      </content>
          <category term="Software" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-04-04:/links/2022/04/04/trebekbot-for-slack</id>
      <title>Trebekbot for Slack</title>
        <link rel="alternate" type="text/html" href="https://www.trebekbot.com/"/>
      <published>2022-04-04T10:00:00-06:00</published>
      <updated>2022-07-23T14:44:54+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;A fun little time-waster I just built: It’s a &lt;em&gt;Jeopardy!&lt;/em&gt; app, powered by the &lt;a href=&quot;http://jservice.io/&quot;&gt;jService&lt;/a&gt; API, which sets up a perpetual game of &lt;em&gt;Jeopardy!&lt;/em&gt; in your Slack channels. I strongly suggest you set this up in a separate channel if you decide to install it, it’s very addictive.&lt;/p&gt;

      </content>
          <category term="Software" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2022-01-07:/links/2022/01/07/hunters-kill-20-yellowstone-wolves-that-roamed-out-of-park</id>
      <title>Hunters kill 20 Yellowstone wolves that roamed out of park</title>
        <link rel="alternate" type="text/html" href="https://buckrail.com/hunters-kill-20-yellowstone-wolves-that-roamed-out-of-park/"/>
      <published>2022-01-07T10:00:00-06:00</published>
      <updated>2022-07-23T14:59:58+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;blockquote&gt;
&lt;p&gt;Park officials said in a statement to AP that the deaths mark “a significant setback for the species’ long-term viability and for wolf research.”&lt;/p&gt;

&lt;p&gt;One pack — the Phantom Lake Pack — is now considered “eliminated” after most or all of its members were killed over a two-month span beginning in October, according to the park.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Absolutely sickening. We need to have wolves protected under the Endangered Species Act immediately. Please, &lt;a href=&quot;https://www.relistwolves.org/domore&quot;&gt;take action&lt;/a&gt;.&lt;/p&gt;

      </content>
          <category term="National Parks" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2021-10-30:/blog/2021/10/30/how-i-back-up-my-lightroom-catalogs</id>
      <title>How I back up my Lightroom catalogs</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2021/10/30/how-i-back-up-my-lightroom-catalogs/"/>
      <published>2021-10-30T00:00:00-06:00</published>
      <updated>2022-07-09T16:28:29+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>A quick writeup on how I keep my Lightroom catalogs backed up.</summary>
      <content type="html">
        &lt;p&gt;In general, I keep all my photos in a single Lightroom catalog, organized by date. This catalog, and the photos in it, reside in my Dropbox folder, and is backed up by Time Machine frequently, which gives me a couple levels of redundancy if something were to happen to my laptop. I keep the final, full-size, edited JPGs in iCloud, Flickr, and &lt;a href=&quot;https://www.allencompassingtrip.com&quot;&gt;my own website&lt;/a&gt;, so I have ways to retrieve them if I have to.&lt;/p&gt;

&lt;p&gt;However, keeping this one catalog around means it can get pretty huge over time, which is a problem because I don’t have unlimited space in either my Dropbox account or my laptop’s hard drive. I almost never revisit old photos, so most of the raw files in the catalog are just taking space for no real reason, except in the rare case I need to submit them as part of some photography competition. This means I’m happy to move my raw files out of my laptop to save space, as long as I still have access to them when needed; to do this, I follow this process more or less once a year, and every time I have to re-remember how to do it, so I might as well write it down here in case it’s useful to others.&lt;/p&gt;

&lt;h3 id=&quot;the-tl-dr&quot;&gt;The TL;DR&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Once a year, export the previous year’s photos as a new Lightroom catalog&lt;/li&gt;
&lt;li&gt;Copy that new catalog into &lt;em&gt;at least&lt;/em&gt; two external drives&lt;/li&gt;
&lt;li&gt;Compress the new catalog into a DMG image&lt;/li&gt;
&lt;li&gt;Upload the catalog’s DMG image to &lt;a href=&quot;https://aws.amazon.com/s3/glacier/&quot;&gt;Amazon Glacier&lt;/a&gt; for long-term storage, using &lt;a href=&quot;https://www.freezeapp.net&quot;&gt;Freeze&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Once the new catalog and its photos are backed up, delete them from the hard drive to reclaim that space&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;step-1-exporting-the-past-years-photos-as-a-new-lightroom-catalog&quot;&gt;Step 1: Exporting the past year’s photos as a new Lightroom catalog&lt;/h3&gt;

&lt;p&gt;Once a year, usually at the beginning of the year, I go into Lightroom, select all the photos from the previous year by right-clicking on the year’s folder, and export them as a new catalog, ensuring “export negative files” is selected.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/73Sm44FNJnSJpxirqWtVz7/f8fe7b119501c5f377e0e774573c92c3/export-as-catalog.png&quot; alt=&#39;A context menu in Lightroom, with the &quot;export this folder as a catalog&quot; option selected.&#39;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;This creates a new catalog, including all the raw files for all the photos from that year, in a new folder elsewhere on my computer. From there, I copy them to &lt;em&gt;at least two&lt;/em&gt; external hard drives, so I have some redundancy if something were to happen to them (ideally one of them should be kept offsite). If I ever need to look at these catalogs, for example to grab the raw files as I mentioned above, I can simply plug in the drive, do what I need to do, then put it away again.&lt;/p&gt;

&lt;h3 id=&quot;step-2-uploading-the-new-catalog-to-amazon-glacier&quot;&gt;Step 2: Uploading the new catalog to Amazon Glacier&lt;/h3&gt;

&lt;p&gt;Next, I upload these catalogs to Glacier for long-term storage. This is optional, but I like the peace of mind of having this as a last resort if I were to lose all my external drives somehow.&lt;/p&gt;

&lt;p&gt;The first step is to create a DMG image of the folder containing the new catalog and all its photos, by opening Disk Utility, selecting &lt;code&gt;File &amp;gt; New Image &amp;gt; Image from Folder&lt;/code&gt;, and selecting the appropriate folder. I leave the default checkboxes checked; I don’t think compressing the image does much but I leave that on, and I don’t encrypt the image, but you may do so if you want. This will take a while, and at the end I have a &lt;code&gt;.dmg&lt;/code&gt; file somewhere on my system.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/2D5I8S66dSz7w5SWd4LcUc/ca63f87eb3d12861079776a1e18019c0/disk-utility.png&quot; alt=&quot;The Disk Utility app in macOS, with a dialog open to save a new image.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;To upload these files to Glacier, I use an app called &lt;a href=&quot;https://www.freezeapp.net&quot;&gt;Freeze&lt;/a&gt;, which makes the process a snap. It requires setting up an AWS user for it with the appropriate permissions to access Glacier (which I will leave as an exercise to the reader). With that user’s access key and secret set up in Freeze, I’ve created a new vault in Glacier to store my catalogs, and then it’s a simple process of dragging the DMG files into the vault and waiting for the uploads to complete.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/4rtLN83wmiRtpc5v1RdIWu/503a44facd60cd05c42d4d36609bd9d0/freeze.png&quot; alt=&quot;The Freeze app for macOS, showing a couple of uploads in progress.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;h3 id=&quot;step-3-reclaim-hard-drive-space&quot;&gt;Step 3: Reclaim hard drive space&lt;/h3&gt;

&lt;p&gt;Once the catalogs and their photos have been copied to the external drives and uploaded to Glacier, I delete those photos from the main catalog and remove them from my computer to clear up that space. And that’s it!&lt;/p&gt;

&lt;h3 id=&quot;caveats&quot;&gt;Caveats&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;While I rarely have a need for these older catalogs, I don’t want to risk losing them entirely if an external drive dies, which is why I rely on external drives &lt;em&gt;and&lt;/em&gt; Glacier.&lt;/li&gt;
&lt;li&gt;However, retrieving files from Glacier, even just to list the contents of the vault, takes &lt;em&gt;hours&lt;/em&gt;. I’d rather not have to use it, which is why I emphasize copying the catalogs to multiple external drives. They’re cheap these days, anyway; I’ve been using these &lt;a href=&quot;https://amzn.to/3pVTSwi&quot;&gt;Seagate 2TB external drives&lt;/a&gt; for the past couple of years, with zero issues so far.&lt;/li&gt;
&lt;li&gt;One problem with this strategy is that the process of exporting the previous year’s photos as a new catalog effectively copies them, so I need to have enough hard drive space to do that. I’ve definitely been in a situation where if I put it off too long, I won’t have enough space to do it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, this strategy has worked well for me so far, and I feel it strikes a good balance between having access to old photos, keeping them safe, and preserving space on my laptop’s hard drive.&lt;/p&gt;

      </content>
          <category term="Photography" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2021-05-18:/links/2021/05/18/local-knowledge-guillermo-esteves</id>
      <title>Local Knowledge: Guillermo Esteves</title>
        <link rel="alternate" type="text/html" href="https://jacksonholemagazine.com/local-knowledge-guillermo-esteves/"/>
      <published>2021-05-18T17:30:00-06:00</published>
      <updated>2022-07-20T19:02:23+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;I talked a little bit about my photography to Jackson Hole Magazine.&lt;/p&gt;

      </content>
          <category term="Personal" />
          <category term="Photography" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2020-12-23:/links/2020/12/23/pandemic-pets-bring-comfort-companionship</id>
      <title>Pandemic pets bring comfort, companionship</title>
        <link rel="alternate" type="text/html" href="https://www.jhnewsandguide.com/valley/feature/pandemic-pets-bring-comfort-companionship/article_ede359c7-6b2e-5e63-bd24-00aa5e5d45b1.html"/>
      <published>2020-12-23T07:10:00-06:00</published>
      <updated>2022-07-23T14:59:36+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;“My cats are in the local paper” is not a sentence I ever expected to say, but here we are.&lt;/p&gt;

      </content>
          <category term="Personal" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2020-12-01:/links/2020/12/01/londons-natural-history-museums-wildlife-photographer-of-the-year-peoples</id>
      <title>One of my photos is up for Wildlife Photographer of the Year People&#39;s Choice Awards</title>
        <link rel="alternate" type="text/html" href="https://www.nhm.ac.uk/wpy/peoples-choice"/>
      <published>2020-12-01T08:35:00-06:00</published>
      <updated>2022-07-26T16:17:19+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
      <content type="html">
        &lt;p&gt;I’m extremely honored that &lt;a href=&quot;https://www.nhm.ac.uk/wpy/gallery/2020-close-encounter&quot;&gt;one of my photographs&lt;/a&gt; has been shortlisted in London’s Natural History Museum’s Wildlife Photographer of the Year People’s Choice Awards, which is now open for votes.&lt;/p&gt;

      </content>
          <category term="Personal" />
          <category term="Photography" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2016-08-10:/blog/2016/08/10/making-intrinsic-ratio-elements-with-a-max-height</id>
      <title>Making intrinsic ratio elements with a max height</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2016/08/10/making-intrinsic-ratio-elements-with-a-max-height/"/>
      <published>2016-08-10T00:00:00-06:00</published>
      <updated>2022-07-09T15:28:47+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>A little CSS technique I wrote for making elements with a fixed ratio that can also be constrained to a maximum height.</summary>
      <content type="html">
        &lt;p&gt;Here’s something that took me longer than I care to admit to figure out how to do. I have &lt;a href=&quot;https://www.allencompassingtrip.com/&quot;&gt;a photoblog&lt;/a&gt; where I post the photos I make; I like to display them at high resolution, but when they’re in a portrait orientation (&lt;a href=&quot;https://www.allencompassingtrip.com/2015/1/10/1026/lincoln-sky&quot;&gt;like this one&lt;/a&gt;), you usually can’t see the whole photo, so I limit them to the height of the viewport and give the visitor the option to “zoom in” by tapping on it. At the same time, I’d like to reserve the space for the photo so it doesn’t push the rest of the content down when it loads. To do this, I think most people are familiar with Thierry Koblentz’s &lt;a href=&quot;http://alistapart.com/article/creating-intrinsic-ratios-for-video&quot;&gt;intrinsic ratio&lt;/a&gt; technique, published in &lt;a href=&quot;http://alistapart.com/&quot;&gt;A List Apart&lt;/a&gt; way back in 2009: in short, you give the element a &lt;code&gt;padding-top&lt;/code&gt; (or &lt;code&gt;padding-bottom&lt;/code&gt;, if you need to support ancient versions of IE) that’s the height to width ratio you want as a percentage, then absolutely position the element inside. A 2:3 portrait photo’s height is 150% of its width (3/2 * 100%), so you give the container a &lt;code&gt;padding-top: 150%&lt;/code&gt; and you get:&lt;/p&gt;

&lt;p data-height=&quot;265&quot; data-theme-id=&quot;0&quot; data-slug-hash=&quot;AXYLLV&quot; data-default-tab=&quot;result&quot; data-user=&quot;gesteves&quot; data-embed-version=&quot;2&quot; class=&quot;codepen&quot;&gt;See the Pen &lt;a href=&quot;http://codepen.io/gesteves/pen/AXYLLV/&quot;&gt;Intrinsic ratio element&lt;/a&gt; by Guillermo Esteves (&lt;a href=&quot;http://codepen.io/gesteves&quot;&gt;@gesteves&lt;/a&gt;) on &lt;a href=&quot;http://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;

&lt;script async src=&quot;//assets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;Well, now the space for the photo is reserved before it loads, but you can’t see the whole thing. This is the part that took me a while to figure out: I want to limit the height of the photo to the height of the viewport, but since the height of its container is set by the padding, doing something like &lt;code&gt;max-height: 100vh&lt;/code&gt; won’t work.&lt;/p&gt;

&lt;p&gt;Taking a step back, the reason why the &lt;code&gt;padding-top&lt;/code&gt; trick works in the first place is because its percentage value is interpreted as a percentage of the width of its containing element. Therefore, if I want to limit the intrinsic ratio element’s height, I’d need to limit its container’s width, based on the width to height ratio of the image, and have said width be dependent on the viewport’s height. A 2:3 element’s width is 66.67% of its height (2/3 * 100%), but since I want it to depend on the viewport height, then the width of the wrapper element should be &lt;code&gt;66.67vh&lt;/code&gt; (with a &lt;code&gt;max-width: 100%&lt;/code&gt; to keep the width in check). Now I get:&lt;/p&gt;

&lt;p data-height=&quot;265&quot; data-theme-id=&quot;0&quot; data-slug-hash=&quot;rLorrG&quot; data-default-tab=&quot;result&quot; data-user=&quot;gesteves&quot; data-embed-version=&quot;2&quot; class=&quot;codepen&quot;&gt;See the Pen &lt;a href=&quot;http://codepen.io/gesteves/pen/rLorrG/&quot;&gt;Intrinsic ratio element with max height&lt;/a&gt; by Guillermo Esteves (&lt;a href=&quot;http://codepen.io/gesteves&quot;&gt;@gesteves&lt;/a&gt;) on &lt;a href=&quot;http://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;

&lt;script async src=&quot;//assets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;Voilà, the element has an intrinsic ratio, it’s as tall as the viewport, and if I want the photo to embiggen I can use a little bit of JS to increase the width of the wrapper element, which in turn would make the intrinsic ratio element (and the photo inside) taller.&lt;/p&gt;

      </content>
          <category term="CSS" />
          <category term="Web Development" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2015-02-17:/blog/2015/02/17/css-ios-transparency-with-webkit-backdrop-filter</id>
      <title>Building iOS-like transparency effects in CSS with backdrop-filter</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2015/02/17/css-ios-transparency-with-webkit-backdrop-filter/"/>
      <published>2015-02-17T00:00:00-06:00</published>
      <updated>2022-07-09T16:28:43+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>Here’s how you can use the new -webkit-backdrop-filter property to build cool iOS-like transparency effects with CSS.</summary>
      <content type="html">
        &lt;p&gt;Today, thanks to a &lt;a href=&quot;https://vine.co/v/OxmjlxdxKxl&quot;&gt;Vine&lt;/a&gt; video &lt;a href=&quot;https://twitter.com/jasonsantamaria&quot;&gt;Jason&lt;/a&gt; shared in our front-end Slack channel, I learned about the &lt;code&gt;-webkit-backdrop-filter&lt;/code&gt; property, which landed very recently in the &lt;a href=&quot;http://nightly.webkit.org/&quot;&gt;WebKit Nightlies&lt;/a&gt;. Like the existing &lt;code&gt;-webkit-filter&lt;/code&gt; property, it allows you to apply &lt;code&gt;effects&lt;/code&gt; such as &lt;code&gt;blur&lt;/code&gt;, &lt;code&gt;grayscale&lt;/code&gt;, &lt;code&gt;hue-rotate&lt;/code&gt;, and others, only instead of applying them to the element itself, they’re applied to whatever is &lt;em&gt;behind&lt;/em&gt; the element. This lets you to do some very iOS-like transparency effects, like what I did to The Verge’s nav dropdown while experimenting with this:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/qjKrDcf1pw10ygC8C3gMP/377b88209b0761c5a41baf33ca825532/Screen_Shot_2015-02-17_at_12.14.01_PM.0.png&quot; alt=&quot;A screenshot of the front page of The Verge, showing a transparency effect on the main navigation.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;In this example, I gave the dropdown background a semi-transparent color, and added a simple &lt;code&gt;-webkit-backdrop-filter: blur(10px);&lt;/code&gt; to it. Instead of blurring the dropdown itself, it blurs whatever the dropdown covers when it’s open, giving it a little more depth by letting the blurred, colorful hero images show through.&lt;/p&gt;

&lt;p&gt;You can see the effect in action in the following &lt;a href=&quot;http://codepen.io/gesteves/pen/PwRPZa?editors=110&quot;&gt;pen&lt;/a&gt;, although you’ll need to download and install the &lt;a href=&quot;http://nightly.webkit.org/&quot;&gt;WebKit Nightly&lt;/a&gt; build to check it out.&lt;/p&gt;

&lt;p data-height=&quot;432&quot; data-theme-id=&quot;0&quot; data-slug-hash=&quot;PwRPZa&quot; data-default-tab=&quot;result&quot; data-user=&quot;gesteves&quot; class=&quot;codepen&quot;&gt;See the Pen &lt;a href=&quot;http://codepen.io/gesteves/pen/PwRPZa/&quot;&gt;PwRPZa&lt;/a&gt; by Guillermo Esteves (&lt;a href=&quot;http://codepen.io/gesteves&quot;&gt;@gesteves&lt;/a&gt;) on &lt;a href=&quot;http://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;

&lt;script async src=&quot;//assets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;Evidently, as this just landed very recently in the nightlies, it’ll be a while before this has enough browser support to let us use it in production, but it’s cool nonetheless.&lt;/p&gt;

      </content>
          <category term="CSS" />
          <category term="Web Development" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2011-09-25:/blog/2011/09/25/i-love-chromes-automatic-updates</id>
      <title>I love Chrome’s automatic updates</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2011/09/25/i-love-chromes-automatic-updates/"/>
      <published>2011-09-25T00:00:00-06:00</published>
      <updated>2022-07-09T16:21:53+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>Older versions of Chrome virtually disappear as soon as a new version is released, which is nice.</summary>
      <content type="html">
        &lt;p&gt;Last night I signed up for &lt;a href=&quot;http://getclicky.com/239148&quot;&gt;Clicky Web Analytics&lt;/a&gt;, and looking around their site I saw that they offer &lt;a href=&quot;http://getclicky.com/marketshare/&quot;&gt;market share&lt;/a&gt; stats for the major browsers (&lt;abbr&gt;IE&lt;/abbr&gt;, Chrome, Safari, Firefox, and Opera,) both in general, and split by browser version. Looking at Chrome’s stats, I noticed something interesting in the graph:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/158KWM4kzuNqj34emDgiEA/31b19839233334eea5a92d6fee103175/chrome-market-share.png&quot; alt=&quot;A graph of Chrome&#39;s market share.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Thanks to Chrome’s silent automatic updates, as soon as a new version is released, the previous one virtually disappears in a matter of days! I’m sure there are valid arguments against updating software automatically and silently, for example at organizations that need to control what software their employees use, or that need to test existing applications in new browser versions before deploying them; but from a developer’s point of view I think it’s awesome, because for all intents and purposes there’s only one version of Chrome – the current one. Since older versions aren’t a big concern, testing in Chrome becomes simpler and easier: there’s no need to hunt down and keep multiple versions for testing.&lt;/p&gt;

&lt;p&gt;Compare to Internet Explorer, where the four most recent versions coexist, so if it represents a major portion of your visits (and it probably does,) then you’ll have to support at least two of them: Internet Explorer 8, for the large number of people still running Windows XP; and 9, for those running Windows Vista and 7. Unfortunately, unless dropping XP and Vista is an option, you’ll probably have to keep supporting them even after Internet Explorer 10 comes out, since &lt;a href=&quot;http://www.pcmag.com/article2/0,2817,2383640,00.asp#fbid=9CphUBgOJbN&quot;&gt;it won’t support Windows Vista&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/AcypOV7SphZb6XKtT678Q/4a99713a16704c1bf4fd8f8fbe77423c/internet-explorer-market-share.png&quot; alt=&quot;A graph of Internet Explorer&#39;s market share.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Safari’s market share behaves a bit like &lt;abbr&gt;IE&lt;/abbr&gt;‘s, inasmuch as it doesn’t automatically update and the newest version coexists with the older one, but remarkably Safari 5.1 has already overtaken the previous version, just a month after its release with the launch of Lion. Still, until 5.0 is gone, testing in it might be problematic unless you have an older Mac nearby, or a Snow Leopard Server disc you can install in VMware Fusion or Parallels.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/5iJv6rbYTSoVmNl1Mc0sDx/aa0169a14c56084cc2eb66a67e1aa0d3/safari-market-share.png&quot; alt=&quot;A graph of Safari&#39;s market share.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Firefox, meanwhile, behaves in a combination of both ways. After Firefox 4, which introduced automatic updates, it behaves like Chrome, with the previous version dropping off after a new release; but with a good number of users still on version 3.6, which didn’t have automatic updates.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/3NIT4YHmO4Qy4bGyn9C3Za/099fb44aa84e38ea0e603c2428368f15/firefox-market-share.png&quot; alt=&quot;A graph of Firefox&#39;s market share.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;aside&gt;&lt;p&gt;If Firefox 3.6 is a concern for you, and you need or want to test in it, you can &lt;a href=&quot;http://www.mozilla.org/en-US/firefox/all-older.html&quot;&gt;download it here&lt;/a&gt;.&lt;/p&gt;&lt;/aside&gt;

&lt;p&gt;And Opera… oh, who cares, I’m pretty sure Opera’s market share is composed entirely of developers testing their sites in Opera.&lt;/p&gt;

&lt;p&gt;Anyway, knowing that I can stop worrying about testing in older versions of Chrome (and to a much lesser degree, Firefox and Safari) personally makes my job much easier, but as usual, your mileage may vary. Let your own browser stats be your guide.&lt;/p&gt;

      </content>
          <category term="Web Development" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2011-09-22:/blog/2011/09/22/better-infinite-scrolling-with-the-html5-history-api</id>
      <title>Better infinite scrolling with the HTML5 History API</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2011/09/22/better-infinite-scrolling-with-the-html5-history-api/"/>
      <published>2011-09-22T00:00:00-06:00</published>
      <updated>2022-07-11T02:58:08+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>A technique to improve infinite or endless scrolling using the HTML5 History API.</summary>
      <content type="html">
        &lt;p&gt;Now that &lt;a href=&quot;http://piictu.com&quot;&gt;Piictu&lt;/a&gt; &lt;a href=&quot;http://techcrunch.com/2011/09/22/piictu-launches-grabs-seed-funding-to-grow-its-game-ified-photo-sharing-app/&quot;&gt;finally launched&lt;/a&gt; and is out of beta, I want to write a bit about one of my favorite things I worked on as the front-end web developer there, which is our implementation of an infinite scrolling page improved by the use of the &lt;abbr&gt;HTML5&lt;/abbr&gt; History &lt;abbr&gt;API&lt;/abbr&gt;, the problem it tried to solve, and the solution we arrived at.&lt;/p&gt;

&lt;h3 id=&quot;whats-piictu&quot;&gt;What’s Piictu?&lt;/h3&gt;

&lt;p&gt;A bit of background first. In case you haven’t tried it (and you totally should,) &lt;a href=&quot;http://piictu.com&quot;&gt;Piictu&lt;/a&gt; is an iPhone social photo app that revolves around the concept of “photo streams”, or threads of photos by different users on the same subject. For example, you can take a photo of a sandwich, start a stream titled “eating a sandwich”, and watch as your friends and followers reply with their own photos of their own sandwiches, or whatever they’re having for lunch. &lt;a href=&quot;http://itunes.apple.com/us/app/piictu/id439888569?mt=8&amp;amp;ls=1&quot;&gt;Check it out&lt;/a&gt;, there are some incredibly creative games and memes going on over there. It’s a lot of fun.&lt;/p&gt;

&lt;p&gt;Since these streams could conceivably have hundreds of photos, and we wanted an uninterrupted photo-viewing experience, we immediately decided to implement each photo stream as an infinitely-scrolling page, instead of using regular pagination. However, this concept of streams of thematically-related photos defined one of the main requirements for the design: we never wanted to take a photo out of its context, which meant that when people shared them, we couldn’t have traditional permalinks with just the one photo. The challenge was to figure out the best way to allow a user to share any photo without taking it out of the context of its stream.&lt;/p&gt;

&lt;h3 id=&quot;the-problem-with-infinite-scroll&quot;&gt;The problem with infinite scroll&lt;/h3&gt;

&lt;p&gt;I’m not a big fan of many sites’ implementations of infinite/endless scroll, and given a choice, I turn it off. Most times it just drives me nuts. For example, in most sites that use it, if my Internet connection goes out or there’s a server error or my browser crashes, I’m forced to start back at the top, which I find infuriating if I’m really deep down the page. Another problem is that I usually can’t bookmark my position, so if I leave and come back later, I’ll have to start over. So, in addition to the photo-sharing-on-an-infinite-page problem, I also wanted to tackle these issues, for a better user experience.&lt;/p&gt;

&lt;h3 id=&quot;the-old-ajax-way&quot;&gt;The old Ajax way&lt;/h3&gt;

&lt;p&gt;My first idea when tackling this problem was a traditional solution using Ajax and fragment identifiers, so we could start the stream of photos at an arbitrary point defined by storing the ID of the desired photo in a &lt;abbr&gt;URL&lt;/abbr&gt; hash (e.g. &lt;code&gt;/stream/123/#/photo/456&lt;/code&gt;.) Since anything after the hash (#) character, or &lt;a href=&quot;http://en.wikipedia.org/wiki/Fragment_identifier&quot;&gt;fragment identifier&lt;/a&gt;, in a &lt;abbr&gt;URL&lt;/abbr&gt; isn’t sent to the server, this would require passing the photo ID to the server using Ajax, and loading the correct photos in the sequence with JavaScript. To make sharing easier, I wanted the hash fragment to be updated with the ID of the photo currently in the viewport as the user scrolls up and down, so they could share it by simply copying and pasting the &lt;abbr&gt;URL&lt;/abbr&gt;.&lt;/p&gt;

&lt;p&gt;However, I had a few issues with this approach. The first obvious one was that it doesn’t degrade gracefully. If the visitor doesn’t have JavaScript enabled or an error prevents the JavaScript from loading, then the user will get a nice empty page – probably not the best experience. It also prevents the page from being crawled, not just by Google, but also by Facebook. When sharing or liking a page, Facebook determines what title, description, and thumbnail to display in the News Feed by crawling the page and looking for &lt;a href=&quot;https://developers.facebook.com/docs/opengraph/&quot;&gt;Open Graph&lt;/a&gt; tags, falling back to things like the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag, description meta tags, and other images on the page. On a traditional permalink page like &lt;a href=&quot;http://instagr.am/p/C5f6F/&quot;&gt;Instagram&lt;/a&gt;’s, it’s easy, just set the Open Graph tags with the metadata of the one photo in the page. But on a page with a multitude of Ajax-loaded photos, without the server knowing which photo is being requested (remember, the server never gets the hash fragment,) how do you set these tags? If Facebook can’t see that information for the photo being shared, it wouldn’t know what to display in the News Feed, undermining what we set out to accomplish in the first place, which was to make it easier for users to share the photos. As an up-and-coming startup looking to get traffic and exposure, this was a real deal-breaker, so I quickly scrapped this solution.&lt;/p&gt;

&lt;aside&gt;&lt;p&gt;For further reading about why this approach is not a very good idea, I recommend Tim Bray’s &lt;a href=&quot;http://www.tbray.org/ongoing/When/201x/2011/02/09/Hash-Blecch&quot;&gt;Broken Links&lt;/a&gt;, Jeremy Keith’s &lt;a href=&quot;http://adactio.com/journal/4346/&quot;&gt;Going Postel&lt;/a&gt;, and Mike Davies’s &lt;a href=&quot;http://isolani.co.uk/blog/javascript/BreakingTheWebWithHashBangs&quot;&gt;Breaking the Web with Hashbangs&lt;/a&gt;.&lt;/p&gt;&lt;/aside&gt;

&lt;h3 id=&quot;a-better-solution-using-the-html5-history-api&quot;&gt;A better solution using the &lt;abbr&gt;HTML5&lt;/abbr&gt; History &lt;abbr&gt;API&lt;/abbr&gt;
&lt;/h3&gt;

&lt;p&gt;Instead, I decided to use the &lt;a href=&quot;http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html&quot;&gt;&lt;abbr&gt;HTML5&lt;/abbr&gt; History &lt;abbr&gt;API&lt;/abbr&gt;&lt;/a&gt;. Instead of getting the ID of the photo currently in the viewport and using it to change the fragment identifier, I update the &lt;abbr&gt;URL&lt;/abbr&gt; in the address bar by calling the &lt;code&gt;replaceState()&lt;/code&gt; method. The basic idea is this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wait for the &lt;code&gt;scroll&lt;/code&gt; event to fire. (Note that since the &lt;code&gt;scroll&lt;/code&gt; event can fire &lt;em&gt;a lot&lt;/em&gt;, for performance reasons it’s best to run any code attached to this event after a small delay, using a &lt;code&gt;setInterval&lt;/code&gt;, as per &lt;a href=&quot;http://ejohn.org/blog/learning-from-twitter/&quot;&gt;John Resig’s recommendation&lt;/a&gt;.)&lt;/li&gt;
&lt;li&gt;When the page has scrolled, get the ID of the top-most photo in the viewport. For this I used the &lt;a href=&quot;http://www.appelsiini.net/projects/viewport&quot;&gt;Viewport Selectors&lt;/a&gt; jQuery plugin, which adds a handy &lt;code&gt;:in-viewport&lt;/code&gt; selector. I also embedded the ID of each photo as a &lt;code&gt;data-photo-id&lt;/code&gt; attribute in their markup, to make it easy to get with JavaScript.&lt;/li&gt;
&lt;li&gt;If the browser supports the History &lt;abbr&gt;API&lt;/abbr&gt;, use &lt;code&gt;replaceState()&lt;/code&gt; to add the photo ID to the base &lt;abbr&gt;URL&lt;/abbr&gt; of the stream page, or remove it, if it’s the first photo in the stream (i.e. if we scroll back to the top.) The reason I chose to use &lt;code&gt;replaceState()&lt;/code&gt; (which updates the current browser history entry) instead of &lt;code&gt;pushState()&lt;/code&gt; (which adds a new history entry) was because I didn’t want to have to click “back” a bunch of times and go back through every photo just to get to the previous page.&lt;/li&gt;
&lt;/ol&gt;

&lt;aside&gt;&lt;p&gt;For more information about the &lt;abbr&gt;HTML5&lt;/abbr&gt; History &lt;abbr&gt;API&lt;/abbr&gt;, read &lt;a href=&quot;http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html&quot;&gt;Session History and Navigation&lt;/a&gt; in the &lt;abbr&gt;HTML5&lt;/abbr&gt; spec, &lt;a href=&quot;https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history&quot;&gt;Manipulating the Browser History&lt;/a&gt; at Mozilla Developer Center, and &lt;a href=&quot;http://diveintohtml5.org/history.html&quot;&gt;Manipulating History for Fun &amp;amp; Profit&lt;/a&gt; in Mark Pilgrim’s &lt;cite&gt;Dive into &lt;abbr&gt;HTML5&lt;/abbr&gt;&lt;/cite&gt;.&lt;/p&gt;&lt;/aside&gt;

&lt;p&gt;An abridged version of the JavaScript code used in Piictu looks somewhat like this (I removed some functionality that wasn’t really necessary for the History &lt;abbr&gt;API&lt;/abbr&gt; explanation from the code, such as the actual infinite scroll implementation. I hope it’s clear enough): &lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;js&quot;&gt;$(window).scroll(function(){
    did_scroll = true;
});

// Every 250ms, check if the page scrolled
setInterval(function(){
    if (did_scroll) {
        updatePhotoPath();
        loadMorePhotos(); // Infinite scroll, etc.
        did_scroll = false;
    }
}, 250);

// Update the URL
function updatePhotoPath() {
    var new_url;
    var in_viewport = $(&#39;div.piic:in-viewport&#39;).first();
    if (history.replaceState) {
        if (in_viewport.hasClass(&#39;original&#39;)) {
            new_url = base_url; // The original URL of the stream page
        } else {
            new_url = base_url + &quot;/photo/&quot; + in_viewport.data(&#39;photoId&#39;);
        }
    history.replaceState(&#39;&#39;, &#39;&#39;, new_url);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can see this in action by going to any stream on Piictu, such as this &lt;a href=&quot;http://piictu.com/streams/4df4fcc02d26880001000353&quot;&gt;Hipstamatic&lt;/a&gt; stream I started a few months ago [&lt;em&gt;NB: Piictu has been defunct for many years now, so evidently all these links are busted, but you can see the same effect on &lt;a href=&quot;https://www.allencompassingtrip.com&quot;&gt;my photoblog&lt;/a&gt;, which uses a similar implementation.&lt;/em&gt;] As you scroll up and down, you’ll notice that the ID of the photo in the viewport is appended to the &lt;abbr&gt;URL&lt;/abbr&gt; of the stream page, and when you return to the top, it’s restored to the original &lt;abbr&gt;URL&lt;/abbr&gt; (the &lt;code&gt;base_url&lt;/code&gt; variable in the source code, which is also saved in a &lt;code&gt;data-*&lt;/code&gt; attribute in the markup for easy retrieval.)&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/7h86rOXrSgUPkXGgZJd88W/849b18ad8650d4718c3e3ae0b4f5c98d/piictu-stream.jpg&quot; alt=&quot;A screenshot of Piictu in Safari, showing the URL rewriting in action.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;So what happens on the server when we request a stream? If we request a plain stream &lt;abbr&gt;URL&lt;/abbr&gt;, such as &lt;code&gt;/streams/123&lt;/code&gt;, the server returns the first few photos normally, starting with the first photo in the stream. If we request a &lt;abbr&gt;URL&lt;/abbr&gt; that contains a photo ID, like &lt;code&gt;/streams/123/photo/345&lt;/code&gt;, the server again returns a few photos, but this time starting at the photo with the specified ID, with an option to load the photos above it, or scroll down to load more photos below. No need to use JavaScript to figure out which photos to show, it’s all returned directly from the server. Also, the metadata of the photo being requested is also returned as Open Graph tags in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the page, so when you post &lt;code&gt;/streams/123/photo/345&lt;/code&gt; on Facebook or Google+, they’ll show the correct thumbnail and caption for that photo. It solves our goal for the photos, which was to help users to easily share them: regardless of whether they use the sharing buttons next to each photo, or simply grab the &lt;abbr&gt;URL&lt;/abbr&gt; from the address bar and paste it in an instant message or their favorite social network, &lt;em&gt;it’ll just work&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It also alleviates some of my pet peeves with infinite scrolling. Since the &lt;abbr&gt;URL&lt;/abbr&gt; updates automatically as you move up and down the page, you can easily bookmark your position, which is particularly handy on very long streams; and if for whatever reason you’re forced to reload the page or your browser crashes, you’ll start where you left off, avoiding the frustration of having to start over (assuming your browser reopens your tabs after a crash.)&lt;/p&gt;

&lt;p&gt;Finally, it degrades somewhat gracefully, as it’ll show the appropriate photos even if JavaScript is disabled, since JavaScript isn’t necessary to figure out which photos to load. (I say “somewhat” because it doesn’t yet offer regular pagination as a fallback, but it’s on the to-do list.)&lt;/p&gt;

&lt;h3 id=&quot;what-about-internet-explorer&quot;&gt;What about Internet Explorer?&lt;/h3&gt;

&lt;p&gt;As always, the biggest issue with using any modern technology is Internet Explorer, since in this case it doesn’t support the History &lt;abbr&gt;API&lt;/abbr&gt; in versions 9 and below. I briefly worked on a workaround for &lt;abbr&gt;IE&lt;/abbr&gt;, using the ol’ hash fragments as a fallback. In the end we simply decided not to support &lt;abbr&gt;IE&lt;/abbr&gt;, mainly because between January and May, Internet Explorer accounted for only 2.42% of the visits to our signup and teaser page, so the added effort and maintenance it would require seemed counterproductive. In addition, our implementation degrades gracefully in &lt;abbr&gt;IE&lt;/abbr&gt;. The &lt;abbr&gt;URL&lt;/abbr&gt; may not change as the user scrolls, but everything else works properly and sharing photos is still possible, using the Twitter and Facebook buttons. In other words, it simply behaves as a traditional implementation of infinite scroll. Finally, it’s a temporary situation, as &lt;a href=&quot;http://msdn.microsoft.com/en-us/ie/hh272905#_HTML5History&quot;&gt;Internet Explorer 10 &lt;em&gt;will&lt;/em&gt; support the History &lt;abbr&gt;API&lt;/abbr&gt;&lt;/a&gt;, and it shouldn’t require any further work. I tested it in the &lt;a href=&quot;http://msdn.microsoft.com/en-us/windows/apps/br229516&quot;&gt;Windows 8 Developer Preview&lt;/a&gt;, which includes a preview version of Internet Explorer 10, and it worked perfectly.&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;I really believe that using the &lt;abbr&gt;HTML5&lt;/abbr&gt; History &lt;abbr&gt;API&lt;/abbr&gt; to augment infinite scrolling offers a superior user experience by alleviating some of the annoyances caused by traditional approaches, such as the lack of bookmarking and sharing. I expect this technique will be used more once Internet Explorer supports the History &lt;abbr&gt;API&lt;/abbr&gt;, but if you’re willing to live without &lt;abbr&gt;IE&lt;/abbr&gt; support for a bit (or use one of the many &lt;a href=&quot;https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills&quot;&gt;polyfills&lt;/a&gt; available,) it’s definitely worth giving it a try now.&lt;/p&gt;

&lt;p&gt;Let me know what you think about this; I look forward to your comments and questions, even though I still haven’t gotten around to adding comments to this blog. In the meantime, feel free to &lt;a href=&quot;https://twitter.com/intent/tweet?text=%40gesteves%20&quot;&gt;tweet @gesteves&lt;/a&gt; or &lt;a href=&quot;mailto:contact@gesteves.com&quot;&gt;send me an email&lt;/a&gt;.&lt;/p&gt;

      </content>
          <category term="JavaScript" />
          <category term="Web Development" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2011-09-19:/blog/2011/09/19/hello-world</id>
      <title>Hello, World</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2011/09/19/hello-world/"/>
      <published>2011-09-19T00:00:00-06:00</published>
      <updated>2022-07-09T16:12:51+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>After almost four years on Tumblr, I’ve decided it’s time to switch blog platforms.</summary>
      <content type="html">
        &lt;p&gt;After almost four years on &lt;a href=&quot;http://www.tumblr.com&quot;&gt;Tumblr&lt;/a&gt;, I’ve decided it’s time to switch blog platforms. My blog now runs on &lt;a href=&quot;http://octopress.org&quot;&gt;Octopress&lt;/a&gt; and &lt;a href=&quot;http://www.heroku.com&quot;&gt;Heroku&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The reason I’m switching, and the reason I was using Tumblr in the first place, are a bit of a long story. I used to have a real blog, one I built myself in 2005 or so, when I was teaching myself Rails, and which I loved and updated frequently. However, a couple of years later, thanks to Venezuela’s foreign currency restrictions which forbid us from spending more than US$400 a year in electronic payments, having a self-hosted blog running on paid hosting – even cheap, shared hosting – became untenable. Back then I was spending $9 a month at &lt;a href=&quot;http://railsplayground.com/&quot;&gt;Rails Playground&lt;/a&gt; to host my blog, which may not sound like a lot, but it added up to $108 a year – over a quarter of what the venezuelan government allows me to spend on the Internet in a year. So, in 2008, when Tumblr began to take off in popularity, I decided to cancel my hosting plan, scrap my blog, write a small script to import all my content, and switch to Tumblr.&lt;/p&gt;

&lt;p&gt;It had the advantage of not having to worry about servers or hosting costs while being flexible enough to allow me to tinker with the code and design to my heart’s content, plus an amazing community that led me to meet some of the best friends I’ve ever had. However, in the past few months I’ve become quite dissatisfied with the service and the constant outages and downtime, like &lt;a href=&quot;http://staff.tumblr.com/post/10264121525/outage&quot;&gt;this recent&lt;/a&gt;, ongoing issue. I’m also a bit uneasy with the content I post over there, because it’s a strange mix of work/professional stuff and personal posts, reblogs, memes, and inside jokes that probably aren’t interesting to anyone except my close circle of friends. So I thought it would be better to have a place that’s just for &lt;a href=&quot;http://gestev.es/AGnN&quot;&gt;serious business&lt;/a&gt;, and leave Tumblr for personal posts, socializing with my friends, and sharing photos of cats. &lt;a href=&quot;http://tumblr.gesteves.com&quot;&gt;All-Encompassing Trip&lt;/a&gt; will keep going at its new address, but this will be my primary blog for now.&lt;/p&gt;

&lt;p&gt;As for my choice of platform, I chose &lt;a href=&quot;http://octopress.org&quot;&gt;Octopress&lt;/a&gt; after reading &lt;a href=&quot;http://mattgemmell.com&quot;&gt;Matt Gemmell&lt;/a&gt; &lt;a href=&quot;http://mattgemmell.com/2011/09/12/blogging-with-octopress/&quot;&gt;rave about it&lt;/a&gt;. I’ve always liked the idea of having a “&lt;a href=&quot;http://inessential.com/2011/03/16/a_plea_for_baked_weblogs&quot;&gt;baked&lt;/a&gt;” blog (i.e. one that’s entirely static &lt;abbr&gt;HTML&lt;/abbr&gt;), and I’ve experimented in the past with things like &lt;a href=&quot;http://nanoc.stoneship.org/&quot;&gt;nanoc&lt;/a&gt;, but Octopress makes it dead simple to set up, generate, and deploy a static &lt;abbr&gt;HTML&lt;/abbr&gt; blog. If you’re considering starting a blog, and feel comfortable working in the Terminal, I can’t recommend Octopress enough.&lt;/p&gt;

&lt;p&gt;There are plenty of advantages to the “baked” approach. Since there are no slow and expensive database calls, it’s blazing fast, lighter and more responsive, and it won’t fall down the first traffic spike it gets – not that I’m expecting to get &lt;a href=&quot;http://daringfireball.net&quot;&gt;fireballed&lt;/a&gt; or anything, but for me it also means that it’s lightweight enough that I can probably get away with running it with a single Heroku dyno for the foreseeable future – which makes it free. There’s also a security argument to be made, since there’s no admin interface to hack, or any chance of &lt;abbr&gt;SQL&lt;/abbr&gt; injection. I also like that it makes backups really simple: the posts live on my computer, so they get backed up to Time Machine and SuperDuper as part of my regular backup process; my Sites folder is symlinked in my Dropbox folder, so there’s a backup there too; and since the posts are also under source control, everything gets committed to my Github repo as well. Finally, migration is trivial, because it’s just a bunch of static &lt;abbr&gt;HTML&lt;/abbr&gt; files. Just put them on a new server and it’ll work. Octopress can even automate the process of copying the files to the server with rsync after writing a post.&lt;/p&gt;

&lt;p&gt;A few other things of note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The theme is mostly built from scratch, based on the design of &lt;a href=&quot;http://www.gesteves.com&quot;&gt;my website&lt;/a&gt;. Unfortunately, I mostly destroyed all the sensible patterns and defaults &lt;a href=&quot;https://twitter.com/#!/imathis&quot;&gt;Brandon&lt;/a&gt; (the creator of Octopress) created for theming it, so I think I’ll have to do some work rebuilding them to keep the code more organized. I did keep his awesome port of &lt;a href=&quot;http://ethanschoonover.com/solarized&quot;&gt;Solarized&lt;/a&gt; syntax highlighting, though.&lt;/li&gt;
&lt;li&gt;I modified it for deployment to Heroku. This included, on Brandon’s instructions, removing the &lt;code&gt;public&lt;/code&gt; folder from &lt;code&gt;.gitignore&lt;/code&gt;, but I also added everything &lt;em&gt;but&lt;/em&gt; the &lt;code&gt;public&lt;/code&gt; folder and the config files to &lt;code&gt;.slugignore&lt;/code&gt;, to keep the slug size as small as possible (it clocks in at 460 KB, vs. 4.4 MB for &lt;a href=&quot;http://www.gesteves.com&quot;&gt;my website&lt;/a&gt;’s slug); and I added a rake task for Heroku deployment, which is mostly a copy of the default Github one, but pushing to Heroku &lt;code&gt;master&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;I wanted to use &lt;a href=&quot;http://www.iawriter.com/&quot;&gt;iA Writer&lt;/a&gt; as my blogging software, so I modified the &lt;code&gt;new_post&lt;/code&gt; rake task so that it calls &lt;code&gt;open #{filename}&lt;/code&gt; at the end, which opens the newly created post in the default editor for Markdown files, which I had previously set to Writer.&lt;/li&gt;
&lt;li&gt;I also symlinked the &lt;code&gt;_posts&lt;/code&gt; folder to the &lt;a href=&quot;http://www.iawriter.com/&quot;&gt;Writer&lt;/a&gt; and &lt;a href=&quot;http://www.secondgearsoftware.com/elements/&quot;&gt;Elements&lt;/a&gt; folders in my Dropbox, so I can theoretically write posts from my iPad and iPhone, although I’d have to &lt;abbr&gt;SSH&lt;/abbr&gt; into my computer to actually publish them.&lt;/li&gt;
&lt;li&gt;I’m trying to simplify the process of creating new posts and deploying to Heroku, and integrate it better to the OS. I’m currently trying to figure out how to add the rake tasks to OS X’s Services menu, to make it easier to publish posts after writing them. I also replaced some of the &lt;code&gt;puts&lt;/code&gt; calls in the Rakefile with calls to &lt;code&gt;growlnotify&lt;/code&gt;, to get nice Growl notifications on successful deploys and whatnot.&lt;/li&gt;
&lt;li&gt;I still haven’t decided whether or not I want comments here, or if I want to use &lt;a href=&quot;http://disqus.com/&quot;&gt;Disqus&lt;/a&gt; or &lt;a href=&quot;https://developers.facebook.com/docs/reference/plugins/comments/&quot;&gt;Facebook Comments&lt;/a&gt;. In the meantime, you can &lt;a href=&quot;https://twitter.com/intent/tweet?text=%40gesteves%20&quot;&gt;tweet @gesteves&lt;/a&gt; with any comments.&lt;/li&gt;
&lt;li&gt;I didn’t import any of the old content from Tumblr; I couldn’t figure out a good way to do it without breaking a ton of links, since Tumblr’s permalink format doesn’t match Octopress’s. I thought about modifing Octopress’s permalinks and work something out using my local backup of Tumblr, but instead I opted for a clean break and a fresh start, using a bit of &lt;a href=&quot;http://www.sinatrarb.com/&quot;&gt;Sinatra&lt;/a&gt; code to 301-redirect any traffic looking for a Tumblr permalink to my Tumblr’s &lt;a href=&quot;http://tumblr.gesteves.com&quot;&gt;new domain&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anyway, I’m not entirely sure how much or how often I’ll write here – Twitter &amp;amp; Tumblr seem to have atrophied my ability to write more than 140 characters at a time, and writing this post took longer than I care to admit – but I do hope to at least comment on interesting web design &amp;amp; development resources I find, in the style of &lt;a href=&quot;http://labnotes.org/&quot;&gt;Assaf Arkin&lt;/a&gt;’s “Rounded Corners” series – which I love – and maybe get back in the habit of writing well enough to, you know, express, like, opinions and stuff. Wish me luck, and thanks for reading.&lt;/p&gt;

      </content>
          <category term="Blogging" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2010-04-28:/blog/2010/04/28/styling-placeholder-text-in-a-form-input</id>
      <title>Styling placeholder text in a form input</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2010/04/28/styling-placeholder-text-in-a-form-input/"/>
      <published>2010-04-28T03:27:00-06:00</published>
      <updated>2022-07-16T03:43:40+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>Just a quick CSS tip for styling placeholder text on inputs.</summary>
      <content type="html">
        &lt;p&gt;If you’re using the &lt;a href=&quot;http://www.w3.org/TR/html5/forms.html#the-placeholder-attribute&quot;&gt;placeholder attribute&lt;/a&gt; in a form input element, and want to change how the placeholder text looks using CSS, use the &lt;code&gt;-webkit-input-placeholder&lt;/code&gt; pseudo-element, like so:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;::-webkit-input-placeholder {
    …
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The placeholder attribute works in the latest &lt;a href=&quot;http://ftp.mozilla.org/pub/firefox/nightly/latest-trunk/&quot;&gt;Firefox nightly&lt;/a&gt;, but if there’s a &lt;code&gt;-moz&lt;/code&gt; extension to style it, I sure as hell can’t find it.&lt;/p&gt;

      </content>
          <category term="CSS" />
          <category term="Web Development" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2010-03-26:/blog/2010/03/26/css-glow-effects-with-box-shadow</id>
      <title>CSS glow effects with box-shadow</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2010/03/26/css-glow-effects-with-box-shadow/"/>
      <published>2010-03-26T00:00:00-06:00</published>
      <updated>2022-07-11T02:58:48+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>A really nice touch to add to a form, or any other elements you might want to highlight.</summary>
      <content type="html">
        &lt;p&gt;If you’ve visited Twitter’s login screen lately in Safari, you might have noticed that the inputs have this subtle glow when they’re in focus:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/3q5SCc0cvzHUFVsSG3mBpX/0358d31a08dc92af1ca74d3f0defce6f/1.png&quot; alt=&quot;A screenshot of Twitter&#39;s login form, with the username field glowing in light blue.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;I love the way it looks and I think it’s a really nice touch to add to a form, or any other elements you might want to highlight. And it’s easy, too. Here’s how you do it:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;input {
    outline:none;
    transition: all 0.25s ease-in-out;
    -webkit-transition: all 0.25s ease-in-out;
    -moz-transition: all 0.25s ease-in-out;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;What I’m doing here is using &lt;code&gt;outline:none;&lt;/code&gt; to get rid of the default highlight that Safari shows when an input is in focus, and then I’m setting the &lt;code&gt;transition&lt;/code&gt; property to tell the input to animate any change to its properties. Read the &lt;a href=&quot;http://www.w3.org/TR/css3-transitions/&quot;&gt;CSS Transitions&lt;/a&gt; draft to know more about the options you can use. Here, I set it to animate for 0.25 seconds, easing in &amp;amp; out of the animation. I’m also setting the same property with vendor prefixes for WebKit &amp;amp; Mozilla.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;input:focus {
    box-shadow: 0 0 5px rgba(0, 0, 255, 1);
    -webkit-box-shadow: 0 0 5px rgba(0, 0, 255, 1); 
    -moz-box-shadow: 0 0 5px rgba(0, 0, 255, 1); 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, when the user focuses on the input, I’m telling it to display a 5-pixels-wide box shadow with no offsets. Since the “shadow” is actually bright blue, it ends up looking like a glow. I’m also using RGBA so I can play with the opacity and make it more subtle if I want to. And again, I’m setting the same property with vendor prefixes so it’ll work with WebKit &amp;amp; Firefox. Since I set the &lt;code&gt;transition&lt;/code&gt; property earlier, when the user focuses on the input, the box shadow won’t appear at once, and instead will be nicely animated. (Of course, if you want to apply this effect to other elements, you should target other pseudo-classes, like &lt;code&gt;:hover&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;To make it look a bit nicer, I’m going to give it some rounded corners and a thinner border that will be highlighted when the input has focus:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;input {
    outline:none;
    transition: all 0.25s ease-in-out;
    -webkit-transition: all 0.25s ease-in-out;
    -moz-transition: all 0.25s ease-in-out;
    border-radius:3px;
    -webkit-border-radius:3px;
    -moz-border-radius:3px;
    border:1px solid rgba(0,0,0, 0.2);
}

input:focus {
    box-shadow: 0 0 5px rgba(0, 0, 255, 1);
    -webkit-box-shadow: 0 0 5px rgba(0, 0, 255, 1); 
    -moz-box-shadow: 0 0 5px rgba(0, 0, 255, 1);
    border:1px solid rgba(0,0,255, 0.8); 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here’s the result:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/2dq4fBQjgfg21VNR2xVzcl/33de6f4ccb5fbc9981d482e4426c6251/2.png&quot; alt=&quot;A screenshot of an input field with a blue glow.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

      </content>
          <category term="CSS" />
          <category term="Web Development" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2009-11-29:/blog/2009/11/29/the-star-wars-opening-crawl-in-html-and-css</id>
      <title>The Star Wars opening crawl in HTML &amp; CSS</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2009/11/29/the-star-wars-opening-crawl-in-html-and-css/"/>
      <published>2009-11-29T00:00:00-06:00</published>
      <updated>2022-07-17T00:05:16+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>I’ve rebuilt the opening crawl for Star Wars Episode IV using only HTML and CSS.</summary>
      <content type="html">
        &lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/2LM7eamVF7YJcCjn4l9YOK/e131b4267d3eec11228c95e1e2b06394/star-wars.png&quot; alt=&quot;Screenshot of the Star Wars opening crawl in a Safari window.&quot;&gt;&lt;figcaption&gt;
I’m very proud of this technological terror I’ve constructed.&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;I’m done: the &lt;a href=&quot;https://codepen.io/gesteves/full/YBPpwG&quot;&gt;Star Wars opening crawl&lt;/a&gt;, built using only HTML &amp;amp; CSS. Caveats: It only works in Safari 5 and the &lt;a href=&quot;http://nightly.webkit.org/&quot;&gt;WebKit Nightly&lt;/a&gt;. Nothing else supports the CSS and &lt;a href=&quot;http://webkit.org/blog/386/3d-transforms/&quot;&gt;3D transforms&lt;/a&gt; and &lt;a href=&quot;http://webkit.org/blog/324/css-animation-2/&quot;&gt;animations&lt;/a&gt; I used (yet), but I just wanted to see if it could be done. Here’s how it works:&lt;/p&gt;

&lt;p&gt;The first step is setting up the stage where the opening crawl will render:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;body {
  background: black url(&quot;https://s3.amazonaws.com/csstarwars/background.png&quot;);
}

#stage {
  height: 600px;
  width: 1000px;
  overflow: hidden;
  margin: 0 auto;
  -webkit-perspective-origin: center 300px;
  -webkit-perspective: 800;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Most of it should be fairly self-explanatory: I’m setting a background image (the stars) on the body of the page, and defining a container of fixed dimensions for the crawl.&lt;/p&gt;

&lt;p&gt;The critical properties here are &lt;code&gt;-webkit-perspective-origin&lt;/code&gt; and &lt;code&gt;-webkit-perspective&lt;/code&gt;. The former defines the point of view of the observer, making them watch from the horizontal center of the container and 300px down from the vertical center. The latter defines the distance of the observer along the Z axis. The actual values were a bit of guesswork to match the perspective seen in the movie.&lt;/p&gt;

&lt;p&gt;Next up is the “a long time ago, in a galaxy far, far away” text, which is styled as follows:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;#far-far-away {
  color: rgb(75,213,238);
  font-family: &quot;FranklinGothicBookRegular&quot;, sans-serif;
  font-size: 48px;
  line-height: 1.5;
  position: absolute;
  top: 200px;
  left: 190px;
  opacity: 0;
  -webkit-animation: fade-in-out 6s linear;
  -webkit-animation-iteration-count: 1;
  -webkit-animation-delay: 5s;
}

@-webkit-keyframes fade-in-out {
  0%   { opacity:0; }
  16%  { opacity:1; }
  84%  { opacity:1; }
  100% { opacity:0; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I’m using the &lt;code&gt;@-webkit-keyframes&lt;/code&gt; rule to fine-tune the effect of the text fading in and out, to closely match its appearance in the movie. Specifically, the entire effect lasts 6 seconds, with the text fully visible 16% in (about a second) and starting to fade out again at 84% (or 5 seconds in). The animation starts playing on a 5-second delay, and only happens once.&lt;/p&gt;

&lt;p&gt;Now, this is where the fun begins: Animating the Star Wars logo, followed by the opening crawl. Unfortunately, there’s no “Star Wars” font I could use, so the logo is simply an image, animated like so:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;img {
  opacity: 0;
  position: absolute;
  top: 100px;
  width: 1000px;
  -webkit-transform-origin: center center;
  -webkit-animation: logo 25s linear;
  -webkit-animation-iteration-count: 1;
  -webkit-animation-delay: 12s;
  -webkit-animation-timing-function: ease-in;
}

@-webkit-keyframes logo {
  0%   { -webkit-transform: translateZ(0);         opacity:0;  }
  0.1% { -webkit-transform: translateZ(0);         opacity:1;  }
  50%  { -webkit-transform: translateZ(-50000px);  opacity:1;  }
  60%  { -webkit-transform: translateZ(-60000px);  opacity:0;  }
  100% { -webkit-transform: translateZ(-100000px); opacity:0;  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The logo animation is on a 12-second delay so it starts at the appropriate time, taking 25 seconds in total and playing only once. The keyframes are fine-tuned to replicate the movie, where it appears to fade in and accelerate quickly, and fade out slowly when the logo is way out in the distance (this took a lot of trial and error, as you can imagine).&lt;/p&gt;

&lt;p&gt;As for the crawl itself:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;#crawl {
  color: rgb(252,223,43);
  font-family: &quot;FranklinGothicDemiRegular&quot;, sans-serif;
  text-align: center;
  font-size: 36px;
  opacity: 0;
  -webkit-animation: crawl 120s linear;
  -webkit-animation-iteration-count: 1;
  -webkit-animation-delay: 16s;
  -webkit-transform-style: preserve-3d;
}

#crawl p.title {
  font-family: &quot;FranklinGothicMediumCondRegul&quot;, sans-serif;
  text-transform: uppercase;
  font-size: 96px;
  -webkit-transform: scaleX(0.6);
}

#crawl p {
  white-space: pre;
}

@-webkit-keyframes crawl {
  0%   { -webkit-transform: rotateX(80deg) translateZ(200px) translateY(1100px);  opacity:1; }
  40%  { -webkit-transform: rotateX(80deg) translateZ(200px) translateY(-340px);  opacity:1; }
  80%  { -webkit-transform: rotateX(80deg) translateZ(200px) translateY(-1780px); opacity:0; }
  100% { -webkit-transform: rotateX(80deg) translateZ(200px) translateY(-2500px); opacity:0; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Similar to the previous animation, I’m setting appropriate delay and duration lengths to ensure the crawl appears on the screen and disappears into the distance at the correct time. The key here is the &lt;code&gt;translateY&lt;/code&gt; properties, which, combined with the perspective properties I had previously set on the container, ensure that the crawl’s vanishing point is in the correct place. The &lt;code&gt;-webkit-transform-style&lt;/code&gt; set to &lt;code&gt;preserve-3d&lt;/code&gt; allows me to rotate the crawl correctly in 3D space, and not have it be flattened on its plane.&lt;/p&gt;

&lt;p&gt;And with that, the Star Wars opening crawl starts playing, and should match pretty closely the theatrical version. But there’s one thing missing: John Williams’s legendary score! Hilariously, despite doing everything else in pure HTML and CSS, we’ll need a single line of JS to ensure the music in the &lt;code&gt;audio&lt;/code&gt; element on the page starts playing at the correct time, 12 seconds after the page loads:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;js&quot;&gt;setTimeout(&quot;document.getElementById(&#39;audio&#39;).play()&quot;, 12000);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;(Update: I’ve removed the audio file to prevent any legal complications).&lt;/p&gt;

&lt;p&gt;And that’s it! It’s pretty impressive how much you can accomplish these days with nothing but standards-based HTML and CSS. Here’s hoping these new CSS3 features make it to other browsers soon enough.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;Update: After almost ten years hosting this on my website, I’ve moved it over to &lt;a href=&quot;https://codepen.io/gesteves/full/YBPpwG&quot;&gt;CodePen&lt;/a&gt;, go check it out there.&lt;/p&gt;

      </content>
          <category term="CSS" />
          <category term="Web Development" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2009-08-26:/blog/2009/08/26/fuck-you-twitter-spammers</id>
      <title>Blocking and reporting Twitter spammers with a shell script</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2009/08/26/fuck-you-twitter-spammers/"/>
      <published>2009-08-26T12:58:00-06:00</published>
      <updated>2022-07-16T04:17:17+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>A simple shell script that blocks a Twitter spammer and sends a direct message to @spam to report it.</summary>
      <content type="html">
        &lt;p&gt;A simple shell script that blocks a Twitter spammer and sends a direct message to &lt;a href=&quot;http://twitter.com/spam&quot;&gt;@spam&lt;/a&gt; to report it:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;#!/bin/bash
curl -s -u USERNAME:PASSWORD -d &quot;id=$1&quot; http://twitter.com/blocks/create/$1.xml
curl -s -u USERNAME:PASSWORD -d &quot;text=$1&amp;amp;amp;user=spam&quot; http://twitter.com/direct_messages/new.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Replace &lt;code&gt;USERNAME&lt;/code&gt; and &lt;code&gt;PASSWORD&lt;/code&gt; with your username and password, obviously. It receives a single parameter, the offending spammer’s username, like so:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;$ ./fuckyou.sh agibbs1984a
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;My script-fu is not that strong, so if you can improve this, I’m open to suggestions.&lt;/p&gt;

      </content>
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2009-03-07:/blog/2009/03/07/start-bittorrent-downloads-remotely-with-dropbox</id>
      <title>Start BitTorrent downloads remotely with Dropbox</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2009/03/07/start-bittorrent-downloads-remotely-with-dropbox/"/>
      <published>2009-03-07T00:00:00-06:00</published>
      <updated>2022-07-09T15:58:06+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>Here’s a quick way to start BitTorrent downloads remotely, if you’re using Dropbox and Transmission.</summary>
      <content type="html">
        &lt;p&gt;Here’s a quick way to start BitTorrent downloads remotely, if you’re using &lt;a href=&quot;https://www.getdropbox.com/referrals/NTE4MjI2OQ&quot;&gt;Dropbox&lt;/a&gt;. First, make sure Dropbox is installed in the computer where you regularly download your torrents, probably your main computer at home. For those not using Dropbox, you can get an account &lt;a href=&quot;https://www.getdropbox.com/referrals/NTE4MjI2OQ&quot;&gt;here&lt;/a&gt;. You’ll have to download a small application, which creates a “Dropbox” folder in your “My Documents” folder in Windows or in your “Home” folder in OS X. Dropbox keeps that folder synchronized across all the computers where you have installed it, so that if you move a file into that folder, it will show up on all the other computers. You can also access and upload files via Dropbox’s web interface.&lt;/p&gt;

&lt;p&gt;Now, most BitTorrent clients have an option to automatically add torrents copied on a certain folder. In &lt;a href=&quot;http://www.transmissionbt.com/&quot;&gt;Transmission&lt;/a&gt;, it’s in preferences, in the “transfers” tab. Check the &lt;q&gt;Watch for torrent files in&lt;/q&gt; option. Optionally, check the &lt;q&gt;Trash original torrent files&lt;/q&gt; so your folder doesn’t fill up with old &lt;code&gt;.torrent&lt;/code&gt; files.&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/bQKYNJNkOjCRXwJ2pvZAR/a678b3ea4238790c88b1b7e2a2a521f8/84438959_1.png&quot; alt=&quot;Screenshot of Transmission&#39;s preferences screen.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;In &lt;a href=&quot;http://www.utorrent.com/&quot;&gt;µTorrent&lt;/a&gt;, the option is in the preferences (the cog icon in the toolbar), under “directories.” Check &lt;q&gt;Automatically load &lt;code&gt;.torrents&lt;/code&gt; from&lt;/q&gt; and optionally, &lt;q&gt;Delete loaded &lt;code&gt;.torrents&lt;/code&gt;.&lt;/q&gt;&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/48EEvhIxHi6tCjueOxFJJl/e552d738ffddc8c0109a668819d6fcd9/84438959_2.png&quot; alt=&quot;Screenshot of uTorrent preferences screen.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;What you want to do is set those options to watch your Dropbox folder (or a folder inside your Dropbox, if you want to keep things tidy) for new &lt;code&gt;.torrents&lt;/code&gt;, so they’ll start downloading automatically when a new file appears. &lt;em&gt;Do not&lt;/em&gt;, however, set your client to save the resulting download into your Dropbox folder. Save them elsewhere.&lt;/p&gt;

&lt;p&gt;Now as long as you keep Dropbox and BitTorrent running, you can start torrents remotely. All you have to do is upload &lt;code&gt;.torrent&lt;/code&gt; files to the Dropbox folder from wherever you are, using the web interface, and it should start downloading almost immediately on the other computer. I started doing this at the office, because for some reason I can’t use Transmission’s web UI from there, and it’s worked for me without a hitch; by the time I get home, my downloads are usually ready. Give it a shot, and let me know what you think.&lt;/p&gt;

&lt;h3 id=&quot;bonus-move-your-files-and-start-transmission-with-automator&quot;&gt;Bonus: Move your files and start Transmission with Automator&lt;/h3&gt;

&lt;p&gt;When I first wrote this last year, I forgot two critical issues. First, if I tell my computer to watch my Dropbox for torrent files, it means that when I’m at home I’ll have to move the torrent files there myself, or start the downloads manually, because by default they’re saved to the Downloads folder. That’s annoying. And second, if Transmission is not running, then saving the torrent files to Dropbox is not going to be of much help. Well, you can solve both problems in Mac OS X 10.6 with a couple of Automator folder actions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Open Automator. In the initial “choose a template for your workflow” dialog, select “folder action”.&lt;/li&gt;
&lt;li&gt; In the “Folder Action receives files and folders added to” dropdown at the top of the window, select your Downloads folder, or the folder where you have set your browser to save your downloaded files.&lt;/li&gt;
&lt;li&gt; In the actions library to the left, select “Files &lt;abbr title=&quot;and&quot;&gt;&amp;amp;&lt;/abbr&gt; Folders” and add a “Filter Finder Items” action. Set the conditions to “file extension” “is” “torrent”.&lt;/li&gt;
&lt;li&gt; Add a “Move Finder Items” action. Set it to move the files to the folder in your Dropbox you told your BitTorrent client to watch.&lt;/li&gt;
&lt;li&gt; From “Utilities”, add a “Launch Application” action, and tell it to launch Transmission, or whatever BitTorrent app you use.&lt;/li&gt;
&lt;li&gt; Save your workflow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your workflow should look like this:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/6VJLM7Lsv6zZwir8avDdYW/e67b5bb64875e30f43966a139cdffa8c/84438959_3.png&quot; alt=&quot;Screenshot of an Automator workflow.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;When you download a torrent file locally, it will automatically move it to Dropbox, where Transmission will catch it and start the download. That takes care of the case where you download the torrent locally, but you need a second automator action to start Transmission when you add a torrent to Dropbox remotely:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Create another Automator workflow. Again, make it a Folder Action, but this time tell it to watch the second folder, the one inside Dropbox where you’re moving the torrent files.&lt;/li&gt;
&lt;li&gt; In the actions library to the left, select “Files &lt;abbr title=&quot;and&quot;&gt;&amp;amp;&lt;/abbr&gt; Folders” and add a “Filter Finder Items” action. Set the conditions to “file extension” “is” “torrent”.&lt;/li&gt;
&lt;li&gt; From “Utilities”, add a “Launch Application” action, and tell it to launch Transmission, or whatever BitTorrent app you use.&lt;/li&gt;
&lt;li&gt; Save the workflow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Boom. Done. Now, when a torrent file appears in your Dropbox, either because you uploaded it remotely or because the first Automator folder action moved it from the Downloads folder, your BitTorrent app will automatically launch and start the download.&lt;/p&gt;

      </content>
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2008-06-20:/blog/2008/06/20/fixing-web-sharing-after-a-time-machine-restore</id>
      <title>Fixing Web Sharing after a Time Machine restore</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2008/06/20/fixing-web-sharing-after-a-time-machine-restore/"/>
      <published>2008-06-20T00:00:00-06:00</published>
      <updated>2022-07-11T02:59:27+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>Restoring Mac OS X from a Time Machine backup breaks Web Sharing. Here’s how to fix it.</summary>
      <content type="html">
        &lt;p&gt;Has anyone had any problems lately with Web Sharing not working at all? Say, after restoring from Time Machine? I just noticed it a few days ago, but didn’t pay much attention; after all I just use the Mongrel bundled with Rails. But today I was checking the Console and noticed this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;shell&quot;&gt;Jun 20 12:18:46 Delta org.apache.httpd[11920]: (2)No such file or directory: httpd: could not open error log file /private/var/log/apache2/error_log.
Jun 20 12:18:46 Delta org.apache.httpd[11920]: Unable to open logs
Jun 20 12:18:46 Delta com.apple.launchd[1] (org.apache.httpd[11920]): Exited with exit code: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Crap. Of course. I remembered that a few weeks earlier I had replaced my PowerBook’s aging 80GB hard drive with a brand new 160GB one, and restored my Mac OS X installation with Time Machine. As &lt;a href=&quot;http://duncandavidson.com&quot;&gt;James Duncan Davidson&lt;/a&gt; notes in his &lt;a href=&quot;http://duncandavidson.com/2008/01/restoring-from-time-machine.html&quot;&gt;Restoring from Time Machine&lt;/a&gt; article, Time Machine automatically excludes a bunch of stuff like caches and logs, so Apache’s log directory is not recreated during a restore, and that caused it to crash and burn.&lt;/p&gt;

&lt;p&gt;So, anyway, to fix it, just open up the terminal and type &lt;code&gt;sudo mkdir /private/var/log/apache2&lt;/code&gt;. I hope this helps someone with the same problem.&lt;/p&gt;

      </content>
          <category term="Bugs" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2008-04-08:/blog/2008/04/08/helvetica-neue-light</id>
      <title>Helvetica Neue Light</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2008/04/08/helvetica-neue-light/"/>
      <published>2008-04-08T00:00:00-06:00</published>
      <updated>2022-07-11T03:00:04+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>How to use Helvetica Neue Light correctly in CSS.</summary>
      <content type="html">
        &lt;p&gt;Some of my recent visitors might have noticed that the current version of this site uses &lt;a href=&quot;http://www.linotype.com/12757/neuehelvetica45light-font.html&quot;&gt;Helvetica Neue Light&lt;/a&gt; for almost all the text, a look inspired by the beautiful pages of &lt;a href=&quot;http://www.panic.com/&quot;&gt;Panic&lt;/a&gt;’s products. As reference, here’s a screenshot of part of &lt;a href=&quot;http://www.panic.com/candybar/&quot;&gt;CandyBar&lt;/a&gt;’s website:&lt;/p&gt;

&lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/27FVt63rXWBtGP6iVtjga1/4905b25307889768a4fd1656d76b66cf/36097597_1.png&quot; alt=&quot;Partial screenshot of CandyBar&#39;s website.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Pretty, huh? After snooping around their &lt;abbr title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/abbr&gt; I saw they’re using the following declaration for the body text:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;font-family: &quot;HelveticaNeue-Light&quot;, Helvetica, Arial, sans-serif;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I thought this seemed like a slightly unusual way of declaring the font name. Why not just use “Helvetica Neue Light”? After a quick Google search I found that, as &lt;span class=&quot;vcard&quot;&gt;&lt;a href=&quot;http://pixelmatrixdesign.com/&quot;&gt;Josh Pyles&lt;/a&gt;&lt;/span&gt; and &lt;span class=&quot;vcard&quot;&gt;&lt;a href=&quot;http://stevecochrane.com/v3/&quot;&gt;Steve Cochrane&lt;/a&gt;&lt;/span&gt; &lt;a href=&quot;http://pixelmatrixdesign.com/blog/comments/advanced_web_typography/&quot;&gt;point&lt;/a&gt; &lt;a href=&quot;http://stevecochrane.com/v3/2007/12/13/helvetica-neue-variants-for-use-on-the-web/&quot;&gt;out&lt;/a&gt;, Safari allows you to use a font’s additional weights by referencing their PostScript names — in this case, “HelveticaNeue-Light” — in your &lt;abbr&gt;CSS&lt;/abbr&gt;; whereas you simply declare the font’s full name (“Helvetica Neue Light”) in your stylesheets to use it in Firefox 2 and other Gecko-based browsers like Camino. Thus, the following declaration will give you gorgeous Helvetica Neue Light in almost every Mac browser:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;font-family: &quot;HelveticaNeue-Light&quot;, &quot;Helvetica Neue Light&quot;, sans-serif;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Almost every Mac browser, &lt;em&gt;except&lt;/em&gt; Firefox 3 and recent WebKit &lt;a href=&quot;http://nightly.webkit.org/&quot;&gt;nightly builds&lt;/a&gt;, that is. Instead, you’ll get regular Helvetica Neue.&lt;/p&gt;

&lt;p&gt;So what’s the deal? Why doesn’t this work in the nightlies anymore, when it worked in previous ones and in the shipping version of Safari? I thought it was a bug in nightly r31623, so I &lt;a href=&quot;http://bugs.webkit.org/show_bug.cgi?id=18311&quot;&gt;filed it&lt;/a&gt; and got a response from &lt;span class=&quot;vcard&quot;&gt;&lt;a href=&quot;http://l-c-n.com/phiw/&quot;&gt;Philippe Wittenbergh&lt;/a&gt;&lt;/span&gt;, stating:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I believe the current (@ r31623) is correct. Per &lt;a href=&quot;http://www.w3.org/TR/CSS21/fonts.html#font-family-prop&quot;&gt;&lt;abbr&gt;CSS&lt;/abbr&gt; 2.1:15 Fonts&lt;/a&gt;, the author specifies a font ‘family’ (e.g. Helvetica Neue). If you then want a specific face (&lt;abbr&gt;e.g.&lt;/abbr&gt; ‘Helvetica Neue-Ultra-Light’) within that family you use the &lt;code&gt;font-weight&lt;/code&gt; property, in this case &lt;code&gt;font-weight: 100&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which is absolutely correct: Firefox 3 and the recent WebKit nightlies are simply following the standard to the letter, and calling a font face by its full or PostScript name is non-standard behavior. Shame on me for not knowing the &lt;abbr&gt;CSS&lt;/abbr&gt; spec better. So, the standards-compliant way of getting Helvetica Neue Light is:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;font-family: &quot;Helvetica Neue&quot;, sans-serif;
font-weight: 300;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For backwards compatibility, we can add both the PostScript and full names of the font to the declaration and end up with:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;css&quot;&gt;font-family: &quot;HelveticaNeue-Light&quot;, &quot;Helvetica Neue Light&quot;, &quot;Helvetica Neue&quot;, sans-serif;
font-weight: 300;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To sum up, if you want to use a specific font face, you have to use &lt;code&gt;font-family&lt;/code&gt; along with the &lt;code&gt;font-weight&lt;/code&gt; property, calling both the PostScript and screen names of that face for backwards compatibility. Now go forth and spruce up your websites with some beautiful typography.&lt;/p&gt;

      </content>
          <category term="CSS" />
          <category term="Typography" />
          <category term="Web Development" />
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2008-02-29:/blog/2008/02/29/pownce-bookmarklet-and-activity</id>
      <title>Pownce bookmarklet &amp; activity</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2008/02/29/pownce-bookmarklet-and-activity/"/>
      <published>2008-02-29T01:38:00-06:00</published>
      <updated>2022-07-17T00:30:28+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>A handy bookmarklet and IE8 activity for sharing links in Pownce.</summary>
      <content type="html">
        &lt;p&gt;What’s the first obvious thing to make with the new &lt;a href=&quot;http://pownce.pbwiki.com/API+Documentation2-0#PostaLink&quot;&gt;Pownce &lt;abbr title=&quot;Application Programming Interface&quot;&gt;API&lt;/abbr&gt;&lt;/a&gt; Leah just &lt;a href=&quot;http://blog.pownce.com/2008/02/28/announcing-pownce-api-v20/&quot;&gt;announced&lt;/a&gt;? A bookmarklet
to post links quickly to Pownce. Add this sucker to your bookmarks bar and Pownce away, my friends.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;js&quot;&gt;javascript:var%20a;if%20(window.getSelection){a=window.getSelection();}else%20if%20(document.getSelection){a=document.getSelection();}else%20if(document.selection){a=document.selection.createRange().text;}if%20(a==&#39;&#39;){a=document.title;}location.href=&#39;http://pownce.com/send/link/?url=&#39;+encodeURIComponent(location.href)+&#39;&amp;amp;amp;note_body=&#39;+encodeURIComponent(a)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you’d rather use the activities feature of Internet Explorer 8, feel free to install the &lt;a href=&quot;javascript:window.external.addService(&#39;http://guillermoesteves.com/pownceit-activity.xml&#39;);&quot;&gt;Pownce activity&lt;/a&gt;, which pretty much does the same thing as the bookmarklet. It’s also compatible with Firefox, courtesy of Michael Kaply’s &lt;a href=&quot;http://www.kaply.com/weblog/2008/03/07/microsoft-activities-for-firefox-new-version/&quot;&gt;activities extension&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2006-06-23:/blog/2006/06/23/macintosh-512k</id>
      <title>Macintosh 512K</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2006/06/23/macintosh-512k/"/>
      <published>2006-06-23T00:00:00-06:00</published>
      <updated>2022-07-09T15:49:32+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>I’m the proud owner of a great piece of computer history.</summary>
      <content type="html">
        &lt;figure&gt;&lt;img src=&quot;//images.ctfassets.net/8uiqm8wl5g69/6w6KYBUQi2zpSVyOxqjBDa/a6196bb1ec769186b71ad2e22328024c/174194303_289ea55115_o.jpg&quot; alt=&quot;A Macintosh 512K next to a G4 PowerBook on a wooden desk. A fourth-generation iPod sits on a cradle between them.&quot;&gt;&lt;figcaption&gt; &lt;cite&gt;&lt;/cite&gt;&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Today I was in the car with my dad, and he’s telling me about the garage sale my family’s having at my grandmother’s house, and he mentions that my uncle brought a really, really old computer to the sale, and that he was about to sell it to some guy for about Bs. 40,000 (20 bucks, give or take). I asked him if he knew what kind of computer it was, and he said “an Apple”. “What kind of Apple?”, I ask, and he tells me that it’s one of the first ones. “Why? Do you want it?”, he asks me, and I tell him “sure, why not”. He calls my uncle, and he tells the guy that the computer had already been sold earlier.&lt;/p&gt;

&lt;p&gt;So we drive over to my grandmother’s house to pick up the computer. It’s an &lt;a href=&quot;http://en.wikipedia.org/wiki/Macintosh_512K&quot;&gt;Apple Macintosh 512K&lt;/a&gt;, circa 1984. According to the &lt;a href=&quot;http://en.wikipedia.org&quot;&gt;Wikipedia&lt;/a&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Macintosh_512K&quot;&gt;article&lt;/a&gt;,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Macintosh 512K Personal Computer, the second of a long line of Apple Macintosh computers, was the first update to the original Macintosh 128K. It was virtually identical to the previous Mac, differing primarily in the amount of built-in memory, which quadrupled the original’s. This large increase earned it the nickname Fat Mac. The additional memory was significant because more ambitious users with computer experience stretched the capacity of the original Mac almost immediately, despite the limited number of applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s in pristine state, except for a slight scuff mark at the top of the computer, and the 20+ years worth of grunge. It even works perfectly! I have a whole stack of floppies (copies, unfortunately, instead of the original ones) with apps like MacPaint and MacWrite, and the original System Software, plus the original keyboard and one-button mouse (“hello, computer!”). There was also an Apple printer (I forgot to write down the model) at my grandmother’s house, but it was rather heavy and I don’t really have any place to put it, so I left it there in the meantime, and I told my family not to sell it.&lt;/p&gt;

&lt;p&gt;Now I’m the proud owner of a great piece of computer history.&lt;/p&gt;

&lt;p&gt;Update: I picked up the printer today, it almost broke my back… that thing must weigh at least 30 pounds. It’s an Apple ImageWriter I dot-matrix printer, model A9M0303. That’s all I know about it, so if you have any further info, please contact me. I also spent a few minutes checking out that stack of disks, and I found some good stuff, like two versions of Finder: System version 1.1 with Finder 1.1g; and another System software with a version of Finder number E1-5.3; a few games, including Hangman, Memory, Billiard, IAGO and On-the-Contrary; and A Guided Tour of Macintosh, which, among other things, explains in detail the use of that newfangled device called the “mouse”. I can’t imagine how alien that thing must have looked like 25 years ago.&lt;/p&gt;

&lt;p&gt;Update (08/31/2006): Sweet, one of my &lt;a href=&quot;http://www.flickr.com/photos/gesteves/174194407/&quot;&gt;pictures&lt;/a&gt; of the Macintosh has been featured on &lt;a href=&quot;http://www.tuaw.com/&quot;&gt;TUAW&lt;/a&gt;’s &lt;a href=&quot;http://www.tuaw.com/2006/08/30/rig-of-the-day-retro-simplistic/&quot;&gt;Rig of the Day&lt;/a&gt;. Go &lt;a href=&quot;http://digg.com/apple/A_Macintosh_512k_and_a_PowerBook_G4&quot;&gt;Digg&lt;/a&gt; it!&lt;/p&gt;

      </content>
    </entry>
    <entry>
      <id>tag:www.gesteves.com,2006-06-08:/blog/2006/06/08/d-link-ubuntu-how-to</id>
      <title>How to get a D-Link DWL-G650+ Wi-Fi adapter to work in Ubuntu Linux 6.06</title>
        <link rel="alternate" type="text/html" href="https://www.gesteves.com/blog/2006/06/08/d-link-ubuntu-how-to/"/>
      <published>2006-06-08T00:00:00-06:00</published>
      <updated>2022-07-11T03:00:49+00:00</updated>
      <author>
        <name>Guillermo Esteves</name>
      </author>
        <summary>The D-Link DWL-G650+ doesn’t work in Ubuntu with the default firmware; here are two ways to replace it with the one that works.</summary>
      <content type="html">
        &lt;p&gt;A few days ago I installed the latest version of &lt;a href=&quot;http://www.ubuntu.com/&quot;&gt;Ubuntu Linux&lt;/a&gt; (version 6.06, Dapper Drake) on my old Compaq Presario 1200 laptop. The installation went smoothly, and Ubuntu runs beautifully, considering it’s an old 800MHz Celeron. Except for one small issue: My D-Link DWL-G650+ 802.11g cardbus adapter wouldn’t work. The status LEDs would come on, and the adapter was properly detected by the OS, but I couldn’t manage to get an IP from the router. After looking around in Google for a few minutes, I found &lt;a href=&quot;https://launchpad.net/distros/ubuntu/+source/linux-source-2.6.15/+bug/30766&quot;&gt;this website&lt;/a&gt;, which explained that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As in the summary, acx111-based d-link dwl-g650+ does not work with the default firmware. It works with 1.2.1.34 (tiacx111c16) - this is the firmware recommended (as the better of the only two working) on acx100 development website - see &lt;a href=&quot;http://acx100.sourceforge.net/wiki/Firmware&quot;&gt;http://acx100.sourceforge.net/wiki/Firmware&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;According to the comments section in that page, there are a few ways to fix this, and I’m going to describe two of them. I’m writing this mostly as a reminder for myself since I’ll probably have to do it again next week after I replace the 10GB hard drive in the Compaq with a new 60GB one, but I thought this might be useful to somebody else.&lt;/p&gt;

&lt;h3 id=&quot;solution-1&quot;&gt;Solution 1&lt;/h3&gt;

&lt;p&gt;This first solution involves deleting &lt;code&gt;tiacx111c16&lt;/code&gt; from &lt;code&gt;/lib/firmware/[kernel version]/acx/default&lt;/code&gt;, which links to &lt;code&gt;/lib/firmware/[kernel version]/acx/2.3.1.31/tiacx111c16&lt;/code&gt; (the broken firmware), and replace it with a link to &lt;code&gt;/lib/firmware/[kernel version]/acx/1.2.1.34/tiacx111c16&lt;/code&gt; (the working one). To do this open a terminal window and type:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;shell&quot;&gt;sudo rm /lib/firmware/[kernel version]/acx/default/tiacx111c16
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Replace [kernel version] with your kernel version, obviously. The system will ask you for your password. Enter it. Now type:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;shell&quot;&gt;sudo ln -s /lib/firmware/[kernel version]/acx/1.2.1.34/tiacx111c16 /lib/firmware/[kernel version]/acx/default/tiacx111c16
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Eject the card, reinsert it, and that’s it. It should be working properly now.&lt;/p&gt;

&lt;p&gt;Note: To find out your kernel version, type &lt;code&gt;echo uname -r&lt;/code&gt; at the terminal.&lt;/p&gt;

&lt;h3 id=&quot;solution-2&quot;&gt;Solution 2&lt;/h3&gt;

&lt;p&gt;I think this solution is easier, but you’ll have to reboot your PC. Again, open a terminal, and type:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;shell&quot;&gt;sudo pico /etc/modprobe.d/options
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Your system will ask your password; provide it. Now add the following line to the file you’re editing:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;shell&quot;&gt;options acx firmware_ver=1.2.1.34
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Press Control+x to exit, and press Y to save the changes. Reboot the computer, and you’re done.&lt;/p&gt;

&lt;p&gt;I think that’s it. Feel free to comment if you have any observations or corrections to make.&lt;/p&gt;

      </content>
          <category term="Bugs" />
    </entry>
</feed>
