<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rob OLeary</title>
    <description>The latest articles on DEV Community by Rob OLeary (@robole).</description>
    <link>https://dev.to/robole</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F361098%2Fc72054fa-7e10-4e56-8abe-40906f536dfb.png</url>
      <title>DEV Community: Rob OLeary</title>
      <link>https://dev.to/robole</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/robole"/>
    <language>en</language>
    <item>
      <title>Eleventy - Is it time to upgrade to version 3?</title>
      <dc:creator>Rob OLeary</dc:creator>
      <pubDate>Sun, 08 Sep 2024 10:08:42 +0000</pubDate>
      <link>https://dev.to/robole/eleventy-is-it-time-to-upgrade-to-version-3-4c72</link>
      <guid>https://dev.to/robole/eleventy-is-it-time-to-upgrade-to-version-3-4c72</guid>
      <description>&lt;p&gt;Version three of Eleventy has some exciting new features such as async configuration, &lt;a href="https://www.aleksandrhovhannisyan.com/blog/eleventy-virtual-templates/" rel="noopener noreferrer"&gt;virtual templates&lt;/a&gt;, and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules" rel="noopener noreferrer"&gt;ECMAScript Modules (ESM)&lt;/a&gt; support. I am keen to upgrade, it unlocks some possibilities for features I'd like to work on!&lt;/p&gt;

&lt;p&gt;Version three is still in pre-release. It has reached it first beta (v3.0.0-beta.1). It should be in decent shape, but you are going to be somewhat of a canary tester if you use a pre-release version. You may have a smooth ride or you may hit speedbumps along the way depending on your particular configuration.&lt;/p&gt;

&lt;p&gt;After reading some accounts of other people upgrading (&lt;a href="https://www.raymondcamden.com/2024/08/05/upgraded-to-eleventy-30-beta" rel="noopener noreferrer"&gt;Raymond Camden&lt;/a&gt;, &lt;a href="https://helenchong.dev/blog/posts/2024-07-19-eleventy-3-0-upgrade/" rel="noopener noreferrer"&gt;Helen Chong&lt;/a&gt;, and &lt;a href="https://www.bobmonsour.com/posts/upgrade-and-debug/" rel="noopener noreferrer"&gt;Bob Monsour&lt;/a&gt;), it encouraged me to give it a go! I set myself a time limit of three hours. I will need to shelf it for another time if it takes longer than that.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to upgrade
&lt;/h2&gt;

&lt;p&gt;I made a new branch of my website and followed the instructions in the &lt;a href="https://github.com/11ty/eleventy/releases/tag/v3.0.0-beta.1" rel="noopener noreferrer"&gt;v3.0.0-beta.1 release notes&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I installed Eleventy v3 beta. You should do this first. I use pnpm as my package manager, so I ran &lt;code&gt;pnpm add @11ty/eleventy@beta&lt;/code&gt; to add it as a dependency.&lt;/li&gt;
&lt;li&gt;I took the &lt;em&gt;extra step&lt;/em&gt; of uninstalling the previous version of eleventy because I wanted to ensure my npm scripts would work. I ran &lt;code&gt;pnpm remove @11ty/eleventy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;I installed the &lt;a href="https://github.com/11ty/eleventy-upgrade-help" rel="noopener noreferrer"&gt;upgrade helper plugin&lt;/a&gt; with &lt;code&gt;pnpm add @11ty/eleventy-upgrade-help@3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;I added the plugin to my configuration file (as below). I made sure that it was the last &lt;code&gt;addPlugin()&lt;/code&gt; call.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// eleventy.config.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UpgradeHelper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@11ty/eleventy-upgrade-help&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// If you have other `addPlugin` calls, UpgradeHelper should be listed last.&lt;/span&gt;
  &lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UpgradeHelper&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Version three supports both CommonJS and ESM. I should not need to change anything in theory. Hopefully, I will dodge the breaking changes.&lt;/p&gt;

&lt;p&gt;I ran Eleventy in serve mode. It spat out an error! Let's see if I can fix things up...&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade issues
&lt;/h2&gt;

&lt;p&gt;I encountered three errors and 1 warning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error 1 - Tabs are prohibited in YAML frontmatter
&lt;/h3&gt;

&lt;p&gt;The first issue was a complaint about having tabs in YAML frontmatter. It was pointing at the &lt;code&gt;tag.njk&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[11ty] Problem writing Eleventy templates:
[11ty] 1. Having trouble reading front matter from template ./src/pages/tag.njk (via TemplateContentFrontMatterError)
[11ty] 2. tab characters must not be used in indentation (10:1)
[11ty] 
[11ty]   7 | stylesheet: "/assets/stylesheet ...
[11ty]   8 | eleventyComputed:
[11ty]   9 |   title: Posts tagged with “{{  ...
[11ty]  10 | →permalink: /blog/tag/{{ tag |  ...
[11ty] ------^ (via YAMLException)
[11ty] 
[11ty] Original error stack trace: YAMLException: tab characters must not be used in indentation (10:1)
[11ty] 
[11ty]   7 | stylesheet: "/assets/stylesheet ...
[11ty]   8 | eleventyComputed:
[11ty]   9 |   title: Posts tagged with “{{  ...
[11ty]  10 | →permalink: /blog/tag/{{ tag |  ...
[11ty] ------^
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/nodeca/js-yaml" rel="noopener noreferrer"&gt;&lt;code&gt;js-yaml&lt;/code&gt;&lt;/a&gt; library was upgraded from version three to version four. It is more strict on enforcing formatting. Tabs in YAML frontmatter will now break the build.&lt;/p&gt;

&lt;p&gt;Replacing the tab in the frontmatter with 2 spaces solves this issue. Thankfully it was only one instance in my project! So it was a quick fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error 2 - Failed version check of Sass plugin
&lt;/h3&gt;

&lt;p&gt;The next issue came from the &lt;a href="https://github.com/kentaroi/eleventy-sass" rel="noopener noreferrer"&gt;&lt;code&gt;eleventy-sass&lt;/code&gt;&lt;/a&gt; plugin that I use for adding &lt;a href="https://sass-lang.com/" rel="noopener noreferrer"&gt;Sass&lt;/a&gt; support to Eleventy. The plugin has a version check. It does not support version three yet, so it breaks the build.&lt;/p&gt;

&lt;p&gt;Rather than getting into the weeds, I just removed it as a plugin from my config. This got rid of the error but also removed the styles from my website! Everything looks rather bland now!&lt;/p&gt;

&lt;p&gt;I will circle back later to see if there is a quick solution to this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error 3 - A peciular &lt;code&gt;TemplateContentPrematureUseError&lt;/code&gt; error
&lt;/h3&gt;

&lt;p&gt;The next error I recieved was a &lt;code&gt;TemplateContentPrematureUseError&lt;/code&gt; error as below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[11ty] Unhandled rejection in promise:
[11ty] Tried to use templateContent too early on ./src/posts/new/2024-08-17-code-embeds/index.md (via TemplateContentPrematureUseError)
[11ty] 
[11ty] Original error stack trace: TemplateContentPrematureUseError: Tried to use templateContent too early on ./src/posts/new/2024-08-17-code-embeds/index.md
[11ty]     at Object.get [as templateContent] (file:///home/rob/programming/workspace/websites/roboleary-v0_3/node_modules/.pnpm/@11ty+eleventy@3.0.0-beta.1/node_modules/@11ty/eleventy/src/Template.js:612:14)
[11ty]     at Object.memberLookup (/home/rob/programming/workspace/websites/roboleary-v0_3/node_modules/.pnpm/nunjucks@3.2.4_chokidar@3.6.0/node_modules/nunjucks/src/runtime.js:201:17)
[11ty]     at eval (eval at _compile (/home/rob/programming/workspace/websites/roboleary-v0_3/node_modules/.pnpm/nunjucks@3.2.4_chokidar@3.6.0/node_modules/nunjucks/src/environment.js:527:18), &amp;lt;anonymous&amp;gt;:83:15)
[11ty]     at iterCallback (/home/rob/programming/workspace/websites/roboleary-v0_3/node_modules/.pnpm/nunjucks@3.2.4_chokidar@3.6.0/node_modules/nunjucks/src/runtime.js:236:11)
[11ty]     at next (/home/rob/programming/workspace/websites/roboleary-v0_3/node_modules/.pnpm/nunjucks@3.2.4_chokidar@3.6.0/node_modules/nunjucks/src/lib.js:258:7)
[11ty]     at eval (eval at _compile (/home/rob/programming/workspace/websites/roboleary-v0_3/node_modules/.pnpm/nunjucks@3.2.4_chokidar@3.6.0/node_modules/nunjucks/src/environment.js:527:18), &amp;lt;anonymous&amp;gt;:127:1)
[11ty]     at /home/rob/programming/workspace/websites/roboleary-v0_3/node_modules/.pnpm/eleventy-plugin-killer-filters@0.6.0/node_modules/eleventy-plugin-killer-filters/eleventy.config.js:39:9
[11ty]     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error pointed to a markdown post. There was nothing special about that post. I removed the post to see if that changed things, it didn't! It threw the same error but pointed to another post! That's weird!&lt;/p&gt;

&lt;p&gt;After digging through the issues of the Eleventy repo, I discovered that the error has nothing to do with markdown posts. My situation matched the explanation given in the issue - &lt;a href="https://github.com/11ty/eleventy/issues/3136" rel="noopener noreferrer"&gt;Tried to use templateContent too early in eleventy 3.0.0-alpha.3 on rerender&lt;/a&gt;. The actual cause of the issue is the &lt;code&gt;htmlAbsoluteUrls&lt;/code&gt; filter that is supplied by the official &lt;a href="https://www.11ty.dev/docs/plugins/rss/" rel="noopener noreferrer"&gt;RSS plugin&lt;/a&gt;. I use that filter in a template (&lt;code&gt;feed.njk&lt;/code&gt;) to generate my RSS feed. When I deleted &lt;code&gt;feed.njk&lt;/code&gt;, the error went away!&lt;/p&gt;




&lt;p&gt;With those issues overcome temporarily, my website appeared to running in serve and build modes correctly.&lt;/p&gt;

&lt;p&gt;In order for me to publish my website using version 3 beta, I would need to do the following to have my website in the exact same state as before:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To restore my styles, I would need to update the &lt;code&gt;eleventy-sass&lt;/code&gt; plugin. It appears that the plugin author has made a new major version supporting Eleventy v3. It requires Node.js v22+ and uses an experimental Node feature. I'm not sure I want to go down that road. And I would need to test it to check on the stability. I will need to delve deeper to make a decision.&lt;/li&gt;
&lt;li&gt;Probably the cleanest solution for the RSS feed template (&lt;code&gt;feed.njk&lt;/code&gt;) is to convert it to a virtual template. This is not something I want to rush into. My feed has some conditional logic, so it is not a quick conversion. I'd need to test it properly to ensure that the outputs match.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Revising those 2 facets of my website would definitely push me over my 3 hour limit. I will need to shelf this for now.&lt;/p&gt;

&lt;p&gt;I can use the time left over to do a quick reconnaissance on converting my configuration to ESM. That is something I'd like to get to eventually also.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about converting your eleventy configuration to ESM?
&lt;/h2&gt;

&lt;p&gt;My configuration file is split into approximately 40 files, and I have quite a few data files. To feel out the process, I wanted to work with a small subset of my website. I deleted the majority of the pages, templates and markdown files that were using a lot of filters and shortcodes. I edited my config to use the minimum number of imports and was able to get things running.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;only big issue I found was with importing JSON files&lt;/strong&gt;. You should be able to import a JSON file in Node with the following syntax: &lt;code&gt;import data from './data.json' assert {type: 'json'};&lt;/code&gt;. Importing a JSON file in Node is still an &lt;em&gt;experimental&lt;/em&gt; feature, it can cause issues.&lt;/p&gt;

&lt;p&gt;Eleventy does not handle JSON imports gracefully yet, the problem is outlined in the issue - &lt;a href="https://github.com/11ty/eleventy/issues/3128" rel="noopener noreferrer"&gt;Import attributes not yet supported (Error thrown from acorn dependency)&lt;/a&gt;. In serve and watch mode, JSON imports crash operation. You cannot ignore the issue.&lt;/p&gt;

&lt;p&gt;There is a workaround with &lt;a href="https://nodejs.org/api/module.html#modulecreaterequirefilename" rel="noopener noreferrer"&gt;&lt;code&gt;module.createRequire&lt;/code&gt;&lt;/a&gt;. It will permit importing a JSON file like you would in CommonJS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRequire&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRequire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/data/site.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could convert your JSON files to JavaScript files also if you prefer. I guess that when Node fully supports JSON imports, this issue will be resolved downstream eventually also.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I got most of the way through upgrading to version three of Eleventy. It will entail rewriting a couple parts of my website for me to get over the line. I don't have more time to give to that effort right now. I will favour a refresh of my website before I follow through with the upgrade.&lt;/p&gt;

&lt;p&gt;Thanks to all the folks who have worked hard on this release! 🙌&lt;/p&gt;




&lt;p&gt;Written by &lt;a href="https://www.roboleary.net" rel="noopener noreferrer"&gt;Rob O'Leary&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.roboleary.net/feed.xml" rel="noopener noreferrer"&gt;Subscribe to RSS feed&lt;/a&gt; for the latest articles.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;© Rob OLeary 2024&lt;/small&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>eleventy</category>
    </item>
    <item>
      <title>How to create a slick CSS animation from Alien</title>
      <dc:creator>Rob OLeary</dc:creator>
      <pubDate>Wed, 29 May 2024 11:30:00 +0000</pubDate>
      <link>https://dev.to/robole/how-to-create-a-slick-css-animation-from-alien-4065</link>
      <guid>https://dev.to/robole/how-to-create-a-slick-css-animation-from-alien-4065</guid>
      <description>&lt;p&gt;The title sequence for Alien is iconic. It sets the mood of the movie perfectly. Let's see how we can recreate it as a web animation!&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;You can watch &lt;a href="https://www.youtube.com/watch?v=7BYzzast0jw" rel="noopener noreferrer"&gt;the title sequence on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is &lt;a href="https://codepen.io/robjoeol/pen/ZEZXabR" rel="noopener noreferrer"&gt;the finished animation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/robjoeol/embed/ZEZXabR?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  About the title sequence
&lt;/h2&gt;

&lt;p&gt;The title is unsettling. It opens with a still scene of far away shot of planet and the camera is slowly panning across it. Slowly some disjointed bits of the title fade into view turning from a bluish hue to white. There is exaggerated spacing between the letters, so that when the bits of the letters finally resolve to a word, it still feels odd to recognize it as a word. It is backed by a moody instrumental.&lt;/p&gt;

&lt;p&gt;The typeface is san serif. In &lt;a href="https://www.artofthetitle.com/title/alien" rel="noopener noreferrer"&gt;an interview with The Art of The Title&lt;/a&gt;, the Title Designer Richard Greenberg says the following about the typeface:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It’s probably a slight variation on Futura, but it wasn’t custom. It was incredibly simple, but it struck a chord. Maybe because it was attached to one of the most frightening movies ever made!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The typeface probably is Helvetica Black. I have used a similar font called &lt;a href="https://www.fontspace.com/homepagebaukasten-bold-font-f24942" rel="noopener noreferrer"&gt;HomepageBaukasten Bold&lt;/a&gt; in my implementation.&lt;/p&gt;

&lt;p&gt;You can read &lt;a href="//ttps://www.artofthetitle.com/title/alien"&gt;the full interview with Richard Greenberg&lt;/a&gt; to learn more about the design of the title sequence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The animation
&lt;/h2&gt;

&lt;p&gt;The duration of the title sequence is 2 minutes. The animation has 3 parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Background pan - The background is panning slowly to the right. When it reaches a point far to the right, it fades out. This runs for the duration of the title sequence.&lt;/li&gt;
&lt;li&gt;Credit reveal - Credits of the crew are faded in and out. I only included a credit for for Ridley Scott to fill the void at the beginning. I wanted to keep it as simple as possible. This occurs 5 seconds into the title sequence, and has a duration of 4 seconds.&lt;/li&gt;
&lt;li&gt;Title reveal - The title "ALIEN" is revealed segment by segment of each letter. This beings 12.5 seconds into the title sequence. A new segment is revealed approxmiately every 4 seconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A good starting point is to set up some CSS variables to mark out some key values to build a timeline around. It is the third part that has the most going on and has element relying on each other. This is what I set-up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/* begining point of title reveal */&lt;/span&gt;
  &lt;span class="py"&gt;--animation-delay-title-reveal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12.5s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;/* the delay between animation of each segment in title reveal */&lt;/span&gt;
  &lt;span class="py"&gt;--animation-delay-segment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;/* the duration of animation of each segment in title reveal */&lt;/span&gt;
   &lt;span class="py"&gt;--animation-duration-segment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.75s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go through each animation part now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1 - The background pan animation
&lt;/h3&gt;

&lt;p&gt;The key to getting this background pan animation right is the having an eye-catching image with a decent resolution. We only want to display a cross-section of the image zoomed in. Therefore it needs to look good up close.&lt;/p&gt;

&lt;p&gt;I created the background image using a composite of 2 space images I found from Unsplash. The &lt;a href="https://unsplash.com/photos/solar-eclipse-7YiZKj9A3DM" rel="noopener noreferrer"&gt;main image&lt;/a&gt; is a solar ecliplse. I painted in the distinctive orangish hue and touched it up to get it closer to the original.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-15-alien-title-animation%2Funsplash-space-pics.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-15-alien-title-animation%2Funsplash-space-pics.webp" alt="The main image I created the background image from was found on unsplash and is a solar eclipse."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is the finished background image. The green box in the figure below shows the section we display as the background.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-15-alien-title-animation%2Fbg-cross-section.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-15-alien-title-animation%2Fbg-cross-section.webp" alt="The background image is a planet in a shadown with a orangish eclipse. There is a green box around the middle section of the image highlighting the section of the image that will be used as the background"&gt;&lt;/a&gt;&lt;/p&gt;
The middle of the image is used as the background, desingated by the green box



&lt;p&gt;We want the background to be responsive but show that same cross section across all viewport sizes. Similar to a movie player, we can create a letterboxed appearance on smaller screen sizes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-15-alien-title-animation%2Fdesktop-vs-tablet.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-15-alien-title-animation%2Fdesktop-vs-tablet.webp" alt="On the desktop view, the background image takes up 100% of space. On tablet, the background image does not takes up all of the vertical space and has a letterbox appearance."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To do this we make the &lt;code&gt;body&lt;/code&gt; a grid and center the "title" &lt;code&gt;div&lt;/code&gt; that has the background image. We give  it a fixed aspect ratio (1920:1080) that matches the background image. We zoom in on the section by doubling the size of the background &lt;code&gt;background-size: 200%;&lt;/code&gt; and center it vertically using &lt;code&gt;background-position: 0% 50%;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="n"&gt;dvh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;place-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1920px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1920&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url("img/bg.webp")&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key to the animation is that the image has overflowed the viewport -- half of it is out of view on the right-hand side. To animate it, we want to change the X value of &lt;code&gt;background-position&lt;/code&gt; to shift the background horizontally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;bg-scroll&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a simplified example of this animation part.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/robatronbobby/embed/bGydYEN?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In the final version, a second animation is added to fade out the title at the end of the sequence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bg-scroll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fadeout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;210s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-timing-function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ease-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-fill-mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;forwards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forwards&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;120s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;fadeout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may notice that the duration of the &lt;code&gt;bg-scroll&lt;/code&gt; animation is over 3 and half minutes (210 seconds). Why is that? The easing is linear, we want a constant rate for the pan. However to compensate for not having the perfect image, I increase the &lt;code&gt;animation-duration&lt;/code&gt; so that it pans close to the edge of at a slow constant pace. The second animation &lt;code&gt;fadeout&lt;/code&gt; effectively hides the entire title at 2 minutes. Probably the better way to get to a perfect result would be to tweak the values for &lt;code&gt;background-size&lt;/code&gt; and &lt;code&gt;background-position&lt;/code&gt;, I just found this the quickest route to getting the desired outcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 2 - Credit reveal animation
&lt;/h3&gt;

&lt;p&gt;We to show the credit for "Ridley Scott" for 3+ seconds and then hide it. We are animating the &lt;code&gt;opacity&lt;/code&gt; of the element. Nothing unexpected here I would say!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;show-credit&lt;/span&gt; &lt;span class="m"&gt;4s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-timing-function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;show-credit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;10&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Part 3 - The title reveal animation
&lt;/h3&gt;

&lt;p&gt;In order to animate the title, we need cut up the letters into individual segments. This is the tricky part.&lt;/p&gt;

&lt;p&gt;The actual animation is fairly straightforward. We are transitioning the &lt;code&gt;background-color&lt;/code&gt; from transparent to a blueish hue to white finally.&lt;/p&gt;

&lt;p&gt;Initially, I animated &lt;code&gt;opacity&lt;/code&gt; also, but I found setting the intial &lt;code&gt;background-color&lt;/code&gt; as transparent and changing the value to blue and then white matched the original.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;reveal-bg-color&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;33&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was just a case of playing with the keyframes and easing to get the exact feel right.&lt;/p&gt;

&lt;p&gt;We can use the aforementioned CSS variables to place each segment animation at the correct point in the timeline using &lt;code&gt;animation-delay&lt;/code&gt;. We do a calcuation using these variables and multiply it by an ordinal number as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* second segment to show - diagonal leg of N */&lt;/span&gt;
&lt;span class="nc"&gt;.letter&lt;/span&gt;&lt;span class="nd"&gt;:nth-of-type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;i&lt;/span&gt;&lt;span class="nd"&gt;:nth-of-type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nl"&gt;animation-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--animation-delay-title-reveal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--animation-duration-segment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--animation-delay-segment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* 3rd segment to show - vertical bar of L */&lt;/span&gt;
&lt;span class="nc"&gt;.letter&lt;/span&gt;&lt;span class="nd"&gt;:nth-of-type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;i&lt;/span&gt;&lt;span class="nd"&gt;:nth-of-type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nl"&gt;animation-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--animation-delay-title-reveal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--animation-duration-segment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--animation-delay-segment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to learn how to cut up the letter into segments, read on.&lt;/p&gt;

&lt;h4&gt;
  
  
  Cutting up the letters into segments
&lt;/h4&gt;

&lt;p&gt;As a starter, let's get the size and spacing of the title correct. The title has a small margin on the top and sides. The letters are evenly and widely distributed in the available space.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-15-alien-title-animation%2Falien-title1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-15-alien-title-animation%2Falien-title1.png" alt="Initial design of title with word 'Alien'. A grid with 5 columns with each have 1fr unit."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's take the basic HTML where we have each letter as its own element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;A&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;L&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;I&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;E&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;N&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use a grid on the &lt;code&gt;h1&lt;/code&gt; to evenly divide the space with each grid item (letter) taking up 1 fractional unit (fr).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;justify-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;10%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;margin-block-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.25rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* other styles */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what we got so far...&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/robatronbobby/embed/vYwYweV?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The 4 options for cutting up the letters are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create 3 versions of each letter stacked up. You can duplicate each letter with the &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; pseudo-elements sourcing the letter from a &lt;code&gt;data-letter&lt;/code&gt; attribute e.g. &lt;code&gt;&amp;lt;div data-letter="A"&amp;gt;A&amp;lt;/div&amp;gt;&lt;/code&gt;. Then, you can cut out a portion of the letter with &lt;code&gt;clip-path&lt;/code&gt; to have 3 referencable segments of a letter. The limitation is that the letter 'E' requires 4 segments!&lt;/li&gt;
&lt;li&gt;You can create the segments of the letter using multiple &lt;code&gt;background-image&lt;/code&gt; instances utilising various gradients to create the shapes. Animating &lt;code&gt;background-image&lt;/code&gt; is more challenging as you may need to repeat values for keyframes.&lt;/li&gt;
&lt;li&gt;You could nest multiple elements into each letter &lt;code&gt;div&lt;/code&gt; to represent a letter part and style each one. It could be any arbitary element such &lt;code&gt;i&lt;/code&gt; to keep it short. It is more straightforward to animate an element rather than a background image. I think this is the easier option.&lt;/li&gt;
&lt;li&gt;You could create the title as a &lt;code&gt;text&lt;/code&gt; element in a SVG. Then transform each letter to a &lt;code&gt;path&lt;/code&gt;. Then, you can divide each &lt;code&gt;path&lt;/code&gt; into the segments required (more &lt;code&gt;path&lt;/code&gt; elements). In the &lt;a href="https://www.roboleary.net/2020/12/24/title-sequences.html" rel="noopener noreferrer"&gt;Killing Eve title sequence&lt;/a&gt; in this series, I converted a &lt;code&gt;text&lt;/code&gt; element into individual &lt;code&gt;path&lt;/code&gt; elements. You can read that if you are curious about that process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Personally, I find it is the easiest editing the title as a SVG in Inkscape (approach 4). However, I will go for approach 3 to demonstrate how we can stick with HTML and do it all in the browser. The markup looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!--since A has 3 segments, it contains 3 i elements --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;A&lt;span class="nt"&gt;&amp;lt;i&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;i&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;i&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!--since L has 2 segments, it contains 2 i elements --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;L&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!--other letters--&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want to position each &lt;code&gt;i&lt;/code&gt; element absolutely, relative to each letter &lt;code&gt;div&lt;/code&gt;. We can then style each &lt;code&gt;i&lt;/code&gt; as an overlay on top of the letter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="nt"&gt;i&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* hide the ones we are not interested in */&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="nt"&gt;i&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* this one is visible and can see letter underneath */&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0.3&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have a green overlay like below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-15-alien-title-animation%2Falien-title2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-15-alien-title-animation%2Falien-title2.png" alt="Initial design of title with word 'Alien'. There is a slightly transparent green box over the letter A."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I found easiest is to use a polygon &lt;code&gt;clip-path&lt;/code&gt; and edit it in the devtools (Firefox in my case). For example I will add a triangle polygon to the element: &lt;code&gt;clip-path: polygon(50% 0%, 0% 100%, 100% 100%);&lt;/code&gt;. We want the clip path points to be in percentages to ensure that it is reponsive.&lt;/p&gt;

&lt;p&gt;In the devtools, I can position each of the points to match the outline of the left arm of the letter A. I need to add a fourth point by double-clicking on the line to match the shape. You can see the process in action in the video below:&lt;/p&gt;





&lt;p&gt;This is the codepen with the segment of the letter &lt;em&gt;A&lt;/em&gt; defined:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/robatronbobby/embed/eYaYwEK?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This process needs to be repeated for each segment. Approximately a dozen times. Once this is done, you can set the &lt;code&gt;color&lt;/code&gt; of the &lt;code&gt;h1&lt;/code&gt; to transparent and then animate each segment in the correct order.&lt;/p&gt;

&lt;p&gt;I have left the text content for each letter. I used &lt;code&gt;clamp()&lt;/code&gt; with the &lt;code&gt;font-size&lt;/code&gt; to make the title responsive to the viewport.  &lt;/p&gt;

&lt;p&gt;Alternatively, you could remove the text content. You would need to set the &lt;code&gt;width&lt;/code&gt; and probably an &lt;code&gt;aspect-ratio&lt;/code&gt; to emulate the x-height of the text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;There was quite a bit of work for this one. Anything that requires preparing images is more time-consuming. While the actual keyframe animations are fairly straightforward, the associated styling required some experimentation to get everything coordinated. The background pan animation is intriguing, I may employ it in a regular webpage somewhere sometime. It is a new trick to add to my collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;The source code is available in &lt;a href="https://github.com/robole/title-sequences" rel="noopener noreferrer"&gt;this github repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can check out all of the animations of this series in &lt;a href="https://codepen.io/collection/nNmwgP" rel="noopener noreferrer"&gt;this codepen collection&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Written by &lt;a href="https://www.roboleary.net" rel="noopener noreferrer"&gt;Rob O'Leary&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.roboleary.net/feed.xml" rel="noopener noreferrer"&gt;Subscribe to web feed&lt;/a&gt; for the latest articles.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;© Rob OLeary 2024&lt;/small&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>animation</category>
    </item>
    <item>
      <title>Testing experimental browser features on Linux - Is Linux supported?</title>
      <dc:creator>Rob OLeary</dc:creator>
      <pubDate>Thu, 16 May 2024 11:30:00 +0000</pubDate>
      <link>https://dev.to/robole/testing-experimental-browser-features-on-linux-is-linux-supported-33kk</link>
      <guid>https://dev.to/robole/testing-experimental-browser-features-on-linux-is-linux-supported-33kk</guid>
      <description>&lt;p&gt;I was exploring new HTML and CSS feaures yesterday, a new proposal called &lt;a href="https://open-ui.org/components/invokers.explainer/" rel="noopener noreferrer"&gt;Invokers (InvokeEvent API)&lt;/a&gt; caught my attention. According to caniuse, &lt;a href="https://caniuse.com/mdn-api_invokeevent" rel="noopener noreferrer"&gt;invokers are available in the following browsers as an experimental feature&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Chrome - Available in Canary build only&lt;/li&gt;
&lt;li&gt;Firefox - Can be enabled by setting &lt;code&gt;dom.element.invokers.enabled&lt;/code&gt; to true&lt;/li&gt;
&lt;li&gt;Safari - Can be enabled by setting &lt;code&gt;InvokerAttributesEnabled&lt;/code&gt; to true&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On my linux workstation, I looked to install Chrome Canary. I couldn't find it in an app repository. When I looked at the Chrome Canary website I saw that Linux is not supported. That's a shame!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-08-experimental-browser-features-linux%2Fgoogle-canary-homepage.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-08-experimental-browser-features-linux%2Fgoogle-canary-homepage.webp" alt="Google Canary homepage says Linux is not supported. Try a supported OS."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I moved onto Firefox to enable the experimental setting. You can open the config settings by typing &lt;code&gt;about:config&lt;/code&gt; in the omnibar. There I searched for the setting and enabled it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-08-experimental-browser-features-linux%2Ffirefox-enable-setting.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-05-08-experimental-browser-features-linux%2Ffirefox-enable-setting.png" alt="The browser settings are opened by entering about:config into the omnibar. You can search for settings and enable them."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I tried out an &lt;a href="https://codepen.io/utilitybend/pen/GRLBVZX" rel="noopener noreferrer"&gt;invoker demo (codepen)&lt;/a&gt; from &lt;a href="https://utilitybend.com/blog/invoking-elements-by-using-only-html-a-first-look-at-invokers" rel="noopener noreferrer"&gt;Brecht De Ruyte's article on the topic&lt;/a&gt;, but it did not work! 😒 I tried the codepen in debug mode. Recently, &lt;a href="https://www.omgubuntu.co.uk/2021/09/ubuntu-makes-firefox-snap-default" rel="noopener noreferrer"&gt;Ubuntu has shifted to a snap as its default installation for Firefox.&lt;/a&gt;. Sometimes there can be gotchas around snaps. I installed Firefox through the Apt package to see if it made a difference, then tried again. No dice.&lt;/p&gt;

&lt;p&gt;Since Safari is OS X only, bar some devilish tricks to force an installation on Linux, that puts the kibosh on my experimentation!&lt;/p&gt;

&lt;p&gt;I don't know if I am just unlucky with my system configuration not to at least get Firefox to cooperate. It is sad that Linux is not given the same level of support as other Operating Systems in this regard.&lt;/p&gt;




&lt;p&gt;Written by &lt;a href="https://www.roboleary.net" rel="noopener noreferrer"&gt;Rob O'Leary&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.roboleary.net/feed.xml" rel="noopener noreferrer"&gt;Subscribe to web feed&lt;/a&gt; for the latest articles.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;© Rob OLeary 2024&lt;/small&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>linux</category>
    </item>
    <item>
      <title>How to create a slick CSS animation from Cocktail</title>
      <dc:creator>Rob OLeary</dc:creator>
      <pubDate>Wed, 24 Apr 2024 13:56:37 +0000</pubDate>
      <link>https://dev.to/robole/how-to-create-a-slick-css-animation-from-cocktail-4o8l</link>
      <guid>https://dev.to/robole/how-to-create-a-slick-css-animation-from-cocktail-4o8l</guid>
      <description>&lt;p&gt;Let's jump to the 80's and create an animation with a retro feel. Cocktail!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Cocktail_(1988_film)"&gt;Cocktail&lt;/a&gt; is a romantic comedy-drama made in 1988. If you have not heard of it, the most notably thing is that it stars a young Tom Cruise. He plays a New York City business student, who takes up bartending in order to make ends meet. Hijinks and bad fashion ensue.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;Here is the &lt;a href="https://codepen.io/robjoeol/pen/WNWZBKx"&gt;finished animation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/robjoeol/embed/WNWZBKx?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  About the title sequence
&lt;/h2&gt;

&lt;p&gt;The opening title sequence features neon text. Each title flickers on and off reminsicent of a neon sign. The text is ebullient with bright, saturated colours. The palette is blue, pink, and turquoise that was so prominent in the 80's. It is backed a trendy rock track by Starship.&lt;/p&gt;

&lt;p&gt;You can watch the &lt;a href="https://www.youtube.com/watch?v=yNYA6DyDUk8"&gt;full title sequence on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZNONxx6---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.roboleary.net/assets/img/blog/2024-04-24-cocktail-title-animation/title-reference.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZNONxx6---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.roboleary.net/assets/img/blog/2024-04-24-cocktail-title-animation/title-reference.webp" alt="The title card for the film Cocktail. It is a bright neon sign with blue writing saying Cocktail surrounded by a pink ring" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
The title card for Cocktail



&lt;p&gt;The title card looks like the sign for a bar. The text is in blue and it is surrounded by a pink ring. Looking at it through today's lens, it looks like a subpar design! I will resist reworking anything!&lt;/p&gt;

&lt;p&gt;The font used is hard to pinpoint. The closest I could find is &lt;a href="https://fontsgeek.com/gillies-gothic-light-font"&gt;Gillies Gothic Light&lt;/a&gt;. The cursive style is similar. However, it does not have many of the serifs that join adjacent letters and the flourishes that give a more nuanced perception of a neon sign. I will stick with this font and not edit the gylphs to mirror the original.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M3KVKDXT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.roboleary.net/assets/img/blog/2024-04-24-cocktail-title-animation/result.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M3KVKDXT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.roboleary.net/assets/img/blog/2024-04-24-cocktail-title-animation/result.webp" alt="This is my recreation of the title card for the film Cocktail using the Gillies Gothic Light font. It is a bright neon sign with blue writing saying Cocktail surrounded by a pink ring" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
My recreation of the title card for Cocktail using the Gillies Gothic Light font.



&lt;h2&gt;
  
  
  The animation
&lt;/h2&gt;

&lt;p&gt;A neon effect is created with a shadow with a blur radius. For the text, we use the &lt;code&gt;text-shadow&lt;/code&gt; property. When I was a CSS grasshopper, I thought that a single shadow with a large blur radius would suffice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* other styles */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alas, it looks quite faint..&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kofDNI2k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.roboleary.net/assets/img/blog/2024-04-24-cocktail-title-animation/single-text-shadow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kofDNI2k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.roboleary.net/assets/img/blog/2024-04-24-cocktail-title-animation/single-text-shadow.png" alt="a single text shadow with a blur radius of 30px" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you need to do is stack multiple shadows with larger and larger blur radii as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* other styles */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VtMAwakh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.roboleary.net/assets/img/blog/2024-04-24-cocktail-title-animation/multiple-text-shadow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VtMAwakh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.roboleary.net/assets/img/blog/2024-04-24-cocktail-title-animation/multiple-text-shadow.png" alt="mutliple text shadows with increasing blur radii" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To animate a &lt;code&gt;text-shadow&lt;/code&gt;, we can set the aforementioned &lt;code&gt;text-shadow&lt;/code&gt; in the &lt;code&gt;to&lt;/code&gt; block of a &lt;code&gt;@keyframes&lt;/code&gt;. We do not set any initial state. The browser does the work to interpolate the values for each of the shadows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flicker&lt;/span&gt; &lt;span class="m"&gt;2s&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;flicker&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;text-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/robatronbobby/embed/VwNgeXj?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I created the pink ring using the &lt;code&gt;::before&lt;/code&gt; pseudo-element. We cannot use &lt;code&gt;text-shadow&lt;/code&gt; on it because there is no text!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;70%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create a shadow for the pink ring, we use the &lt;code&gt;filter&lt;/code&gt; property with the &lt;code&gt;drop-shadow()&lt;/code&gt; function. We do not use &lt;code&gt;box-shadow&lt;/code&gt; because it will apply the shadow to the element's box which is a rectangle, thus ignores the shape of the element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;drop-shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;hsla&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;97%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60%&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;drop-shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;hsla&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;97%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60%&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;drop-shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;hsla&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;97%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60%&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To animate the filter, we can do similar to what we did with &lt;code&gt;text-shadow&lt;/code&gt;. This is what it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flicker-ring&lt;/span&gt; &lt;span class="m"&gt;2s&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;flicker-ring&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;drop-shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="n"&gt;hsla&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;97%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60%&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;drop-shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="n"&gt;hsla&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;97%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60%&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;drop-shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt; &lt;span class="n"&gt;hsla&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;97%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60%&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/robatronbobby/embed/abxXNLp?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;That is the core of the neon effect of the title card animation. I also animated &lt;code&gt;opacity&lt;/code&gt; in the final animation to create a staged reveal before lighting up the title.&lt;/p&gt;

&lt;p&gt;The final part of the animation is that the title card fades out. I think it is better to use 2 separate animations to get the desired result rather than to squash everything into a single &lt;code&gt;keyframes&lt;/code&gt;. Since the title remains in its "lit" state for a couple of seconds, we can use an &lt;code&gt;animation-delay&lt;/code&gt; to control this duration in the second animation.&lt;/p&gt;

&lt;p&gt;To recap, the first animation reveals and lights up the title, the second animation leaves the "lit" state linger before it unlights and fades out the title. Here is the CSS for the animations for the ring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;on-ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;off-ring&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--animation-duration&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--animation-duration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;animation-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--delay-duration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;animation-timing-function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ease-in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ease-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-fill-mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;forwards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forwards&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-iteration-count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;on-ring&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;drop-shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ring-glow-color&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;drop-shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ring-glow-color&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;drop-shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ring-glow-color&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;drop-shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ring-glow-color-outer&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;off-ring&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we use &lt;code&gt;filter: none;&lt;/code&gt; to animate out the shadows for the ring!&lt;/p&gt;

&lt;p&gt;That's it!&lt;/p&gt;




&lt;p&gt;Written by &lt;a href="https://www.roboleary.net"&gt;Rob O'Leary&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.roboleary.net/feed.xml"&gt;Subscribe to web feed&lt;/a&gt; for the latest articles.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;© Rob OLeary 2024&lt;/small&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>animation</category>
    </item>
    <item>
      <title>How to create a slick CSS animation from Jackie Brown</title>
      <dc:creator>Rob OLeary</dc:creator>
      <pubDate>Fri, 19 Apr 2024 13:36:56 +0000</pubDate>
      <link>https://dev.to/robole/how-to-create-a-slick-css-animation-from-jackie-brown-3j0d</link>
      <guid>https://dev.to/robole/how-to-create-a-slick-css-animation-from-jackie-brown-3j0d</guid>
      <description>&lt;p&gt;&lt;a href="https://www.imdb.com/title/tt0119396/" rel="noopener noreferrer"&gt;Jackie Brown&lt;/a&gt; is an American crime film that pays homage to 1970s blaxploitation films. The title sequence is simple but has some 70's flair. Let's recreate the reveal animation of the title card.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;Here is &lt;a href="https://codepen.io/robjoeol/pen/BaEdVwj" rel="noopener noreferrer"&gt;the finished animation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/robjoeol/embed/BaEdVwj?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  About the title sequence
&lt;/h2&gt;

&lt;p&gt;The opening title sequence for Jackie Brown is an introductory scene with the titles overlaid. It introduces us to the titular character while she is making her through an airport to board a flight. It is quite hypnotic seeing Jackie standing on a travelator (moving walkway) with a mosiac tile wall passing in the background. The framing and placement of the titles is very well done.&lt;/p&gt;

&lt;p&gt;Here is a short excerpt of the title sequence where the title of the movie is revealed:&lt;/p&gt;


 


&lt;p&gt;You can view the full sequence at: &lt;a href="https://www.youtube.com/watch?v=ivKGY-zAa1g" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=ivKGY-zAa1g&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The original font is probably ITC Tiffany Heavy. This has a commerical license, so I have not used it here. I used a free alternative called &lt;a href="https://www.whatfontis.com/FF_VI-Vong-Vang.font" rel="noopener noreferrer"&gt;VI Vong Vang&lt;/a&gt;. It has similarities but is missing some of the elongated serifs and some glyphs are more condensed. It is good enough to fufil my brief. You can appreciate the differences in the figure 1.0 below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-04-15-jackie-brown-title-animation%2Ffont-comparison.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-04-15-jackie-brown-title-animation%2Ffont-comparison.webp" alt="Comparsion of the original typeface used (top) with VI Vong Vang (bottom). VI Vong Vang is missing some of the elongated serifs and flourishes."&gt;&lt;/a&gt;&lt;/p&gt;
Figure 1.0 - Comparsion of the original font used (top) with VI Vong Vang (bottom). VI Vong Vang is missing some of the elongated serifs and some glyphs are more condensed.



&lt;p&gt;This is a good example of why you would pay for a typeface!&lt;/p&gt;

&lt;h2&gt;
  
  
  The reveal animation
&lt;/h2&gt;

&lt;p&gt;There are a couple of considerations for the reveal animation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The reveal animation has a slightly tapered end to create a vintage film feel. We want to have some partial transparency in the animation to give this impression.&lt;/li&gt;
&lt;li&gt;It has thick shadows, so we need to ensure that these are not truncated by the animation. We need to compensate for that space.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The effect can be created by a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/mask" rel="noopener noreferrer"&gt;mask&lt;/a&gt; as it allows partial transparency. We can use a linear gradient as the mask image to show a portion of the title at a time. The portion of the title we want to show, we use black in that portion of the gradient. The portion we want hidden, we have as transparent in the gradient.&lt;/p&gt;

&lt;p&gt;For example, this linear gradient is 30% transparent and 70% black.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="m"&gt;30%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="no"&gt;black&lt;/span&gt; &lt;span class="m"&gt;30%&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;dotted&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-04-15-jackie-brown-title-animation%2Flinear-gradient.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-04-15-jackie-brown-title-animation%2Flinear-gradient.webp" alt="A div with a linear gradient as a background image. It is 30% transparent and 70% black"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we apply the same gradient as a mask to the title with the &lt;code&gt;mask-image&lt;/code&gt;, the initial 30% of the title is hidden.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.title&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;mask-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="m"&gt;30%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="no"&gt;black&lt;/span&gt; &lt;span class="m"&gt;30%&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-04-15-jackie-brown-title-animation%2Ftitle-partial-mask.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-04-15-jackie-brown-title-animation%2Ftitle-partial-mask.webp" alt="The title uses the linear gradient as a mask. The initial 30% of the title is hidden."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To animate it, we want to move the mask across the title using &lt;code&gt;mask-position&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I found the easiest approach was to create a mask that is twice the width of the title. I made one half of the gradient transparent to completely hide the title, and the other half as mostly black to show the title. The intial position of the mask is to place the transparent half over the title.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-04-15-jackie-brown-title-animation%2Fanimate-mask.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-04-15-jackie-brown-title-animation%2Fanimate-mask.webp" alt="Demonstrating the intial position and movement of mask. The transparent portion of the gradient is positioned over the title initially to hide it. It is moved through the mask-position property"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the key CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;mask-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;black&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;10%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;51%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;30%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;51.5%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;51.5%&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;mask-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;mask-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200%&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reveal&lt;/span&gt; &lt;span class="m"&gt;1.5s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-fill-mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;forwards&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-iteration-count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;reveal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;mask-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I had to add some inline padding to the title to ensure that the mask did not truncate the edges of the title.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.title&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Written by &lt;a href="https://www.roboleary.net" rel="noopener noreferrer"&gt;Rob O'Leary&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.roboleary.net/feed.xml" rel="noopener noreferrer"&gt;Subscribe to RSS feed&lt;/a&gt; for the latest articles.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>animation</category>
    </item>
    <item>
      <title>Eleventy - Create a global production flag</title>
      <dc:creator>Rob OLeary</dc:creator>
      <pubDate>Mon, 19 Feb 2024 11:00:00 +0000</pubDate>
      <link>https://dev.to/robole/eleventy-create-a-global-production-flag-56p9</link>
      <guid>https://dev.to/robole/eleventy-create-a-global-production-flag-56p9</guid>
      <description>&lt;p&gt;A production flag enables you to run activities in dev or production such as minifying assets, showing draft posts, etc. There isn't a built-in flag or function that comes with &lt;a href="https://www.11ty.dev/" rel="noopener noreferrer"&gt;eleventy (11ty)&lt;/a&gt; specifically for this. However we have this info at our fingertips.&lt;/p&gt;

&lt;p&gt;What is the simplest way to add a production flag to our eleventy project?&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it global
&lt;/h2&gt;

&lt;p&gt;I think the best choice is to put the functionality in a &lt;a href="https://www.11ty.dev/docs/data-global/" rel="noopener noreferrer"&gt;global data file&lt;/a&gt;. This makes the flag available in every template. You can use in your eleventy config and other JavaScript files if you &lt;code&gt;require&lt;/code&gt; the data file. Other methods often led to some duplication.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-01-24-eleventy-production-flag%2Fglobal-flag.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-01-24-eleventy-production-flag%2Fglobal-flag.webp" alt="man with a glass globe in his hand with a flag stuck into it"&gt;&lt;/a&gt;&lt;/p&gt;
A handier way of doing things ;-)



&lt;p&gt;By default, global data files are located in the &lt;em&gt;_data&lt;/em&gt; folder. I will create a JavaScript file called &lt;em&gt;production.js&lt;/em&gt; in that folder to create a global &lt;code&gt;production&lt;/code&gt; variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code for the global &lt;code&gt;production&lt;/code&gt; flag
&lt;/h2&gt;

&lt;p&gt;Node.js exposes environment variables through &lt;a href="https://nodejs.org/api/process.html#process_process_env" rel="noopener noreferrer"&gt;&lt;code&gt;process.env&lt;/code&gt;&lt;/a&gt;. Eleventy supplies its own environment variables to &lt;code&gt;process.env&lt;/code&gt; also.&lt;/p&gt;

&lt;p&gt;Since version 2.0, eleventy has made a &lt;code&gt;process.env.ELEVENTY_RUN_MODE&lt;/code&gt; variable available. It has a value of &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;serve&lt;/code&gt;, or &lt;code&gt;watch&lt;/code&gt;. We can use this variable to make our production flag. The &lt;code&gt;build&lt;/code&gt; value equates to our production mode since we only build when we deploy our project to production.&lt;/p&gt;

&lt;p&gt;In our &lt;em&gt;package.json&lt;/em&gt;, we can add npm scripts for running dev and production modes. The "build" script is our production mode -- this is what is run when we deploy our eleventy website to production.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eleventy-production-flag"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eleventy --serve --incremental"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eleventy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@11ty/eleventy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will add the functionality to &lt;em&gt;_data/production.js&lt;/em&gt;. Its sole mission is to return a boolean value -- &lt;code&gt;true&lt;/code&gt; if it is running in production mode, otherwise &lt;code&gt;false&lt;/code&gt;. I will use an &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/IIFE" rel="noopener noreferrer"&gt;IIFE (Immediately Invoked Function Expression)&lt;/a&gt; that returns the boolean value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// _data/production.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ELEVENTY_RUN_MODE&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can now use the &lt;code&gt;production&lt;/code&gt; variable throughout your project!&lt;/p&gt;

&lt;p&gt;The nice thing about this methodology is that it is totally portable -- it works across all operating systems, and you &lt;strong&gt;don't need to set &lt;em&gt;any&lt;/em&gt; environment variable yourself anywhere, anytime&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use &lt;code&gt;production&lt;/code&gt; in layouts and includes
&lt;/h2&gt;

&lt;p&gt;You can use the &lt;code&gt;production&lt;/code&gt; variable in layouts and includes. You don't need to do anything else thanks to the data cascade! 🥰&lt;/p&gt;

&lt;p&gt;For example, we could have a different &lt;code&gt;title&lt;/code&gt; on pages when you are running in dev versus production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-01-24-eleventy-production-flag%2Ftitle-modes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-01-24-eleventy-production-flag%2Ftitle-modes.png" alt="two tabs open in a browser. the left tab shows the title 'dev - home page' for the dev version of the website, and the right tab has the title 'home page' for the production version of the website"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This can help when you are working on your website and occasionally checking deployments in the browser. It may prevent you mistaking a tab of the dev website for a tab of the production website. This is all the code you need in your &lt;em&gt;head.njk&lt;/em&gt; include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- head.njk --&amp;gt;
&amp;lt;head&amp;gt;
    {% if production %}
        &amp;lt;title&amp;gt;{{ title}}&amp;lt;/title&amp;gt;
    {% else %}
        &amp;lt;title&amp;gt;DEV - {{ title}}&amp;lt;/title&amp;gt;
    {% endif %}

    &amp;lt;!-- other stuff --&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I do a variation on this theme with favicons for my own website. I have a bright green square favicon in dev mode to catch my eye, whereas production mode it is my actual favicon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-01-24-eleventy-production-flag%2Ffavicons-modes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2024-01-24-eleventy-production-flag%2Ffavicons-modes.png" alt="two tabs open in a browser. the left tab shows a bright green square as the favicon for the dev version of website, and the right tab shows rob's actual favicon for the prod version of the website"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use &lt;code&gt;production&lt;/code&gt; in the eleventy config
&lt;/h2&gt;

&lt;p&gt;You can use the &lt;code&gt;production&lt;/code&gt; variable in your eleventy config but you must &lt;code&gt;require&lt;/code&gt; the file.&lt;/p&gt;

&lt;p&gt;For example, you can use it to exclude some files from the build. Here I am excluding my draft posts from the build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// eleventy.config.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;production&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./_data/production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;production&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// e.g. exclude draft files&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ignores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/drafts/*.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you split your eleventy config into multiple files, you can do the exact same thing in each of those files and it will work the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;The source code is available in this &lt;a href="https://github.com/robole/eleventy-tutorials" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You will find the project in the &lt;em&gt;production-flag&lt;/em&gt; subfolder.&lt;/p&gt;

&lt;p&gt;Did you enjoy this tutorial? If you did, could you give the repo a star to let me know please? 🌟🫶&lt;/p&gt;




&lt;p&gt;Written by &lt;a href="https://www.roboleary.net" rel="noopener noreferrer"&gt;Rob O'Leary&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.roboleary.net/feed.xml" rel="noopener noreferrer"&gt;Subscribe to web feed&lt;/a&gt; for the latest articles.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;© Rob OLeary 2024&lt;/small&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>eleventy</category>
      <category>javascript</category>
    </item>
    <item>
      <title>VS Code - Fix a task automation issue - `The terminal process failed to launch (exit code: 127)`</title>
      <dc:creator>Rob OLeary</dc:creator>
      <pubDate>Thu, 18 Jan 2024 15:58:10 +0000</pubDate>
      <link>https://dev.to/robole/vs-code-fix-a-task-automation-issue-the-terminal-process-failed-to-launch-exit-code-127-oof</link>
      <guid>https://dev.to/robole/vs-code-fix-a-task-automation-issue-the-terminal-process-failed-to-launch-exit-code-127-oof</guid>
      <description>&lt;p&gt;I like to run a web server automatically whenever I open a web project in VS Code. I use VS Code &lt;a href="https://code.visualstudio.com/docs/editor/tasks"&gt;tasks&lt;/a&gt; to do this. Suddenly, this has stopped working, without rhyme or reason! 😓&lt;/p&gt;

&lt;p&gt;I was greeted with the following error in the integrated terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;-&amp;gt; Executing task: pnpm dev

zsh:1: &lt;span class="nb"&gt;command &lt;/span&gt;not found: pnpm

The terminal process &lt;span class="s2"&gt;"/usr/bin/zsh '-c', 'pnpm dev'"&lt;/span&gt; failed to launch &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;code: 127&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Press any key to close the terminal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice to meet you exit code 127! Let's get to know each other! 🤗&lt;/p&gt;

&lt;h2&gt;
  
  
  The source of the issue
&lt;/h2&gt;

&lt;p&gt;If you are not familiar with tasks in VS Code, check out &lt;a href="https://www.roboleary.net/vscode/2020/10/19/vscode-task-onstartup.html"&gt;this post&lt;/a&gt; that covers this very use case.&lt;/p&gt;

&lt;p&gt;The task is running the (p)npm script called "dev". You can see the &lt;em&gt;tasks.json&lt;/em&gt; file below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Start server on startup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pnpm dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"presentation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reveal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"always"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"panel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"new"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"runOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"folderOpen"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "dev" script is running the &lt;a href="https://www.11ty.dev/"&gt;eleventy&lt;/a&gt; server in dev mode. The details of the script are not important for this discussion, but to round out the background here is an abbreviated version of my &lt;em&gt;package.json&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"some-eleventy-website"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eleventy --serve --incremental --quiet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I open a new integrated terminal and run &lt;code&gt;pnpm dev&lt;/code&gt;, it runs the command successfully! It looks like it is &lt;em&gt;specific&lt;/em&gt; to tasks.&lt;/p&gt;

&lt;p&gt;The problem is that tasks are executed with Zsh using &lt;code&gt;/bin/zsh -c&lt;/code&gt;, which starts a non-login, non-interactive session. This means that &lt;em&gt;~/.zshrc&lt;/em&gt; and &lt;em&gt;~/.zprofile&lt;/em&gt; are not sourced by Zsh. In my case, this means that I can't run the necessary local npm packages in a task. The solution should be to have tasks run in an interative shell session. How do we do that?&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;To start an interative Zsh shell session we want to pass the &lt;code&gt;i&lt;/code&gt; flag to Zsh. After some digging, I found that there is a &lt;a href="https://code.visualstudio.com/docs/terminal/profiles#_configuring-the-taskdebug-profile"&gt;task/debug profile setting&lt;/a&gt; that let's you specify the environment for tasks. The setting has a version for each platform (windows/linux/osx) in the form of &lt;code&gt;terminal.integrated.automationProfile.&amp;lt;platform&amp;gt;&lt;/code&gt;. For example, it would be &lt;code&gt;terminal.integrated.automationProfile.linux&lt;/code&gt; for linux.&lt;/p&gt;

&lt;p&gt;The fix for my linux setup was to add the following to the &lt;em&gt;settings.json&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"terminal.integrated.automationProfile.linux"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/zsh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-i"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>vscode</category>
      <category>productivity</category>
    </item>
    <item>
      <title>VS Code - Sticky code sections for improved contextual browsing (sticky scroll)</title>
      <dc:creator>Rob OLeary</dc:creator>
      <pubDate>Mon, 20 Nov 2023 14:00:31 +0000</pubDate>
      <link>https://dev.to/robole/vs-code-sticky-code-sections-for-improved-contextual-browsing-sticky-scroll-1o6</link>
      <guid>https://dev.to/robole/vs-code-sticky-code-sections-for-improved-contextual-browsing-sticky-scroll-1o6</guid>
      <description>&lt;p&gt;We read more code than we write. It is worthwhile considering if you have VS Code optimized for reading code. A telltale sign that things are subpar is if you find yourself scrolling up and down to re-establish the context of the code you are examining, absentmindedness excepted! Where am I again? 😵‍💫&lt;/p&gt;

&lt;p&gt;One impactful aid is sticky headings that outline the scope of your position in a file. Let's see if it makes a difference!&lt;/p&gt;

&lt;p&gt;First, let's take a quick look at what aids VS Code provides for establishing your current context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Establishing your current context in VS Code
&lt;/h2&gt;

&lt;p&gt;VS Code provides a number of visual aids to communicate where you are and what scope you are operating in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The current file you have opened has the file name on the editor tab and in the titlebar,&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://code.visualstudio.com/docs/getstarted/userinterface#_minimap" rel="noopener noreferrer"&gt;minimap&lt;/a&gt; shows your position in the current file in a floating graphical map,&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;Explorer&lt;/em&gt; pane in the sidebar shows you the filetree of your workspace with your current file highlighted,&lt;/li&gt;
&lt;li&gt;There are &lt;a href="https://www.roboleary.net/2021/11/06/vscode-you-dont-need-that-extension2.html#2-bracket-pair-coloring" rel="noopener noreferrer"&gt;coloured brackets pairs&lt;/a&gt; and &lt;a href="https://www.roboleary.net/2021/11/06/vscode-you-dont-need-that-extension2.html#3-indentation-guides-colorization" rel="noopener noreferrer"&gt;colored guidelines&lt;/a&gt; that outline the extent of scopes,&lt;/li&gt;
&lt;li&gt;You can have &lt;a href="https://code.visualstudio.com/Docs/editor/editingevolved#_breadcrumbs" rel="noopener noreferrer"&gt;breadcrumbs&lt;/a&gt; (&lt;code&gt;breadcrumbs.enabled&lt;/code&gt; setting) that outline the scope of your position within the current file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-11-20-vscode-sticky%2Fminimap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-11-20-vscode-sticky%2Fminimap.png" alt="The floating minimap of VS Code for a JavaScript file."&gt;&lt;/a&gt;&lt;/p&gt;
The floating minimap (righthand side) in VS Code shows your position in the file.



&lt;p&gt;The bit for me that is not done well is having an overview of my scope within a file - what is the scope of the code I am currently examining? I find that breadcrumbs are crumby (pardon the pun) for recognizing the current scope in some languages. The breadcrumb trail becomes too long and text gets truncated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sticky setting
&lt;/h2&gt;

&lt;p&gt;There is a nice setting called &lt;code&gt;editor.stickyScroll.enabled&lt;/code&gt; that pins lines to the top of a file to give an overview of the current scope. Let's browse a HTML file with the setting enabled and see how it works. As you scroll down, every unterminated element has the line with its opening tag pinned as a sticky heading:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi573j6uh2hbqavp92emc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi573j6uh2hbqavp92emc.gif" alt="Demonstration of the editor.stickyScroll.enabled setting on a HTML file."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This displays your current context in an easy to digest way. You will really appreciate it when you have messy HTML files!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-11-20-vscode-sticky%2Fhtml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-11-20-vscode-sticky%2Fhtml.png" alt="Demonstration of the editor.stickyScroll.enabled setting on a HTML file."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is what it looks for a JavaScript file within a &lt;code&gt;fetch&lt;/code&gt; closure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-11-20-vscode-sticky%2Fjavascript.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-11-20-vscode-sticky%2Fjavascript.png" alt="Demonstration of the editor.stickyScroll.enabled setting on a JavaScript file. You can see 2 function defintions are stuck as the top 2 lines. The first is the fetch closure, and the second is the inner function that I am currently in."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see 2 function definitions are stuck as the top 2 lines. As you scroll down whenever there is a nested scope such as a loop, it's declaration line will become stuck also. There are some exceptions, I noticed for JavaScript a plain old &lt;code&gt;for&lt;/code&gt; loop does not stick, but a &lt;code&gt;forEach()&lt;/code&gt; does!&lt;/p&gt;

&lt;p&gt;To get this behaviour for all languages, you can add the following to your &lt;code&gt;settings.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"editor.stickyScroll.enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So far, I like to have this in every language I use. If you don't want it for a particular language, you can disable it using the &lt;a href="https://code.visualstudio.com/docs/getstarted/settings#_multiple-languagespecific-editor-settings" rel="noopener noreferrer"&gt;multiple language-specific syntax&lt;/a&gt;. For example, the following disables sticky scrolling for HTML and XML:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"[html][xml]"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"editor.stickyScroll.enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can limit the number of sticky lines with the &lt;code&gt;editor.stickyScroll.maxLineCount&lt;/code&gt; setting. The default value is 5.&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The state of HTML</title>
      <dc:creator>Rob OLeary</dc:creator>
      <pubDate>Wed, 01 Nov 2023 10:33:57 +0000</pubDate>
      <link>https://dev.to/robole/the-state-of-html-57ek</link>
      <guid>https://dev.to/robole/the-state-of-html-57ek</guid>
      <description>&lt;p&gt;HTML is &lt;em&gt;the&lt;/em&gt; core technology of the web. Yet, it has changed very little in recent years. Is HTML complete? Is it neglected? What is the state of HTML?&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the state of HTML?
&lt;/h2&gt;

&lt;p&gt;My takes...&lt;/p&gt;

&lt;h3&gt;
  
  
  How is HTML used?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wkExjeHM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.roboleary.net/assets/img/blog/2023-10-31-state-of-html/fold-it-in.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wkExjeHM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.roboleary.net/assets/img/blog/2023-10-31-state-of-html/fold-it-in.webp" alt="Moira Rose from the TV show Schitts Creek saying the words 'Fold it in'" width="480" height="480"&gt;&lt;/a&gt;&lt;/p&gt;
How to make a webpage, a summary: Take the HTML and fold it into the JavaScript



&lt;ul&gt;
&lt;li&gt;People don't write HTML in a HTML file very often these days. They write HTML embedded in something else - a template language such as nunjucks, or in a web component (mostly React).&lt;/li&gt;
&lt;li&gt;For producing content, a lot of people write in markdown.&lt;/li&gt;
&lt;li&gt;For interactive content, some people prefer the authoring experience of markdown plus their favourite UI framework. &lt;a href="https://mdxjs.com/docs/what-is-mdx/"&gt;MDX&lt;/a&gt; is probably most popular variant of that.&lt;/li&gt;
&lt;li&gt;In some respects, HTML has become more of an output format like &lt;a href="https://en.wikipedia.org/wiki/PDF"&gt;PDF&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The medium
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;As a document-oriented medium, HTML has covered all of the bases for a long time. Dealing with text and multimedia content has been in the language since &lt;a href="https://en.wikipedia.org/wiki/HTML5"&gt;HTML 5&lt;/a&gt; for the most part. That was 2008.&lt;/li&gt;
&lt;li&gt;As an application-oriented medium, serving dynamic content as HTML in the client-server model is well served by server-side languages. People refer to this as Multi-Page Apps (MPAs) now. Quoting Jason Miller's &lt;a href="https://jasonformat.com/application-holotypes/"&gt;application holotypes&lt;/a&gt;, content websites and storefronts are well for catered here. The difficulty arises when you want a richer interactive experience - a desktop-like app in the browser.&lt;/li&gt;
&lt;li&gt;There are incongruities with what HTML offers and what is desirable for demanding applications. There is a desire to make granular changes to a webpage without requesting an entire HTML file from the server. A webpage is a HTML document, that is the unit of currency in the client-server world. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP"&gt;HTTP&lt;/a&gt; is a &lt;a href="https://en.wikipedia.org/wiki/Stateless_protocol"&gt;stateless protocol&lt;/a&gt;, meaning that the server does not keep any data (state) between two requests. Therefore every page request can be like a reset. These are the major hurdles for a desktop-like experience over a network using HTTP and HTML. This has led to the development of Single-Page Apps (SPAs) and various hybrid approaches, usually where HTML is generated client-side from data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Building user interfaces
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;People hands down reach for UI frameworks. Native web components were in the doldrums for some time. People may have their own component libraries or use UI kits.&lt;/li&gt;
&lt;li&gt;There are deficiencies for building user interfaces. Some UI controls (&lt;code&gt;input&lt;/code&gt; element and others like &lt;code&gt;select&lt;/code&gt;) are hard to customize their style and behaviour. Not many UI controls have pseudo-elements that can be referenced in CSS. People make their own custom UI controls often.&lt;/li&gt;
&lt;li&gt;There is interest in improving the state of the UI control elements but it hasn't yieled any results yet as far as I know. The &lt;a href="https://open-ui.org/"&gt;Open UI&lt;/a&gt; group are advocating for adopting an industry standard definition of UI on the web. They did get &lt;a href="https://chromestatus.com/feature/5737365999976448"&gt;an experimental input control called &lt;code&gt;selectmenu&lt;/code&gt;&lt;/a&gt; added to Chrome recently, it is proposed as a replacement for the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select"&gt;&lt;code&gt;select&lt;/code&gt; element&lt;/a&gt;. They have proposals for other UI elements too.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Evolution of HTML
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;There have been some but not many changes to HTML in the last 2 to 3 years. The changes I am aware of are: the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"&gt;&lt;code&gt;dialog&lt;/code&gt; element&lt;/a&gt; became evergreen, the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/search"&gt;&lt;code&gt;search&lt;/code&gt; element&lt;/a&gt; was added, the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inert"&gt;&lt;code&gt;inert&lt;/code&gt; attribute&lt;/a&gt; was added, lazy loading of images and iframes through the &lt;code&gt;loading&lt;/code&gt; attribute has gotten wider support, and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Components/Using_shadow_DOM"&gt;declarative shadow DOM&lt;/a&gt; has been implemented in some browsers.&lt;/li&gt;
&lt;li&gt;There does not seem to be an interest in augmenting HTML to send and retrieve data at a more granular level than a file. The &lt;a href="https://htmx.org/"&gt;HTMX&lt;/a&gt; library is an example of what this could look like. It gives you access to AJAX, CSS transitions, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API"&gt;web sockets&lt;/a&gt;, and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events"&gt;server-sent events&lt;/a&gt; directly in HTML using attributes. Other project like &lt;a href="https://hotwired.dev/"&gt;Hotwire (Ruby)&lt;/a&gt; offer a similar approach where HTML fragments are requested to partially update a  web page.&lt;/li&gt;
&lt;li&gt;People are still a bit confused about the versioning of HTML since it moved from a number such as HTML 4.01 to a &lt;a href="https://html.spec.whatwg.org/dev/introduction.html"&gt;living standard&lt;/a&gt; i.e. constant evolution.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Handling data that is bound to HTML
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Nowadays people use the &lt;code&gt;form&lt;/code&gt; element far less for sending data to a server. Having an independent backend and frontend (split architecture) became more prominent with the advent of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Guide/AJAX"&gt;AJAX (Asynchronous JavaScript and XML)&lt;/a&gt;. Now the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API"&gt;&lt;code&gt;fetch&lt;/code&gt; API&lt;/a&gt; or a similar library is used to retrieve data from a REST or GRAPHQL endpoint. The frontend is binding this data to HTML.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.cloudflare.com/learning/performance/static-site-generator/"&gt;Static-site generators&lt;/a&gt; have become more sophisticated at fetching data from different sources to generate HTML at build time.&lt;/li&gt;
&lt;li&gt;Application frameworks based on UI frameworks like &lt;a href="https://nextjs.org/"&gt;Next (React)&lt;/a&gt; and &lt;a href="https://nuxt.com/"&gt;Nuxt (Vue)&lt;/a&gt; provide data fetching as part of their featureset usually the code sits alongside your components. Most of the application frameworks tend to spit out a SPA, but some offer other "modes" such as a static site generation. Rich Harris calls this hybrid modality as &lt;a href="https://youtu.be/860d8usGC0o"&gt;Transitional Apps (TAs I guess)&lt;/a&gt; where you can opt into a mode for a component that will determine where and when your HTML is generated: client, server, or prerendered. He is trying to implement this concept in &lt;a href="https://kit.svelte.dev/"&gt;SvelteKit&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The trend now is to shifting towards a broad notion of server components. The concept is to have the same web component authoring experience but do it closer to the data on the server. The server returns mostly HTML rather than data and JavaScript. Frameworks like &lt;a href="https://astro.build/"&gt;Astro&lt;/a&gt; are calling this &lt;em&gt;content-first websites&lt;/em&gt;, where pre-rendering HTML and doing things server-side is the default. Astro allows you to bring your own UI framework and handles transformation. React is the first UI framework to formalize a standard (&lt;a href="https://react.dev/blog/2020/12/21/data-fetching-with-react-server-components"&gt;React Server Components&lt;/a&gt;) for this concept that application frameworks can implement. It is early days for it.&lt;/li&gt;
&lt;li&gt;Server-side languages are still doing their thing. The default is to serve HTML. This is considered boring tech now. A tonne of the web still lives on the &lt;a href="https://en.wikipedia.org/wiki/LAMP_%28software_bundle%29"&gt;LAMP stack&lt;/a&gt;. You can build a server-side web application in almost any language now.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Education
&lt;/h3&gt;

&lt;p&gt;I don't have much insight on how well HTML is taught in general now. From what I gather, there is a tendency of teaching just enough HTML and CSS to get to building things with JavaScript. It is left up to the student to cover things in more depth. It does appear some courses put more emphasis on accessibility now, which is a step forward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Perception of HTML
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0w8nPU4j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.roboleary.net/assets/img/blog/2023-10-31-state-of-html/hypertext.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0w8nPU4j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.roboleary.net/assets/img/blog/2023-10-31-state-of-html/hypertext.webp" alt='A star wars-eque jump to another galaxy with the words: really, fast, text"' width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
Join us on the interweb - Hypertext is like jumping to another galaxy



&lt;ul&gt;
&lt;li&gt;Maybe, the last time there was excitement about HTML was when HTML 5 was shipped. Remember the website &lt;strong&gt;html5rocks.com&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;There is a perception by some that there is not much to HTML. You can learn it quickly.&lt;/li&gt;
&lt;li&gt;The industry surveys run by &lt;a href="https://www.devographics.com/"&gt;devographics&lt;/a&gt; on frontend technology started with the State of JavaScript survey in 2016. Subsequently annual surveys were added for CSS in 2019, and HTML in 2023. It demonstrates the relative priority given to the frontend technologies. These surveys are used as an input to the &lt;a href="https://web.dev/blog/interop-2022"&gt;Interop initiative&lt;/a&gt; that is used to priortise improvement of browser features, so these surveys carry some weight.&lt;/li&gt;
&lt;li&gt;Some people don't see HTML as a programming language, some do. It's not a productive debate in my opinion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Defining moments
&lt;/h2&gt;

&lt;p&gt;When you look back to try to understand where we are, it is hard to extrapolate the reasons why this or that happened. I would say that probably the inability to build to a webpage composed from multiple HTML files was the biggest spur for change. It is not practical to duplicate common fragments such as the website navigation across many HTML files. In the early days, people had some sort of HTML concatenation happening on a server or in a build step. There was technologies like &lt;a href="https://en.wikipedia.org/wiki/Common_Gateway_Interface"&gt;Common Gateway Interface&lt;/a&gt; that enabled running scripts. Microsoft developed their server-side language &lt;a href="https://en.wikipedia.org/wiki/Active_Server_Pages"&gt;Active Server Pages&lt;/a&gt; to be able to write dynamic webpages (gasp). None of it was particularly pretty initially. It eventually led to the rise of PHP and WordPress on the web, which provided a better experience of creating HTML from data server-side.&lt;/p&gt;

&lt;p&gt;The web platform's proposal to resolve some of this in HTML was called &lt;em&gt;HTML includes&lt;/em&gt; or something similar. It never happened. I guess the route was to permit building custom elements (native web components) in HTML instead. Native web components had a very rocky inception and did not offer a viable alternative to UI frameworks. You can read the article &lt;a href="https://thenewobjective.com/web-development/a-criticism-of-web-components"&gt;&lt;em&gt;A Criticism of Web Components&lt;/em&gt;&lt;/a&gt; to get a background on the shortcomings. The platform was too stagnant in this area for too long and the solutions came from elsewhere.&lt;/p&gt;

&lt;p&gt;I believe that native web components are more mature now and are a viable option. I am not speaking from experience. Dave Rupert has advocated for them, he writes about them in his article and talk titled &lt;a href="https://daverupert.com/2021/10/html-with-superpowers/"&gt;&lt;em&gt;HTML with Superpowers&lt;/em&gt;&lt;/a&gt;. Superpowers are welcome! In any case, it will take considerable time before there is a significant shift to native web components. As much as you may want to just use the platform, there are challenges to overcome.&lt;/p&gt;

&lt;p&gt;The desire to have every type of application on the web with a desktop-like experience led to divergent architectures such as SPAs. The crux was atomic changes to a webpage and the perservation of appplication state. Perhaps a different path could have been made if HTML and related technologies were augmented to accomodate a wider range of applications with less reliance on JavaScript. At the moment, we are slimming down some of the fat clients that have become prevalent to reach a better compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewing survey questions from 2023 State of HTML Survey
&lt;/h2&gt;

&lt;p&gt;The inaugural &lt;a href="https://stateofhtml.com/en-US"&gt;State of HTML survey&lt;/a&gt; was done recently. The arc of the survey seems to be that it is trying to ascertain what parts of HTML you know and use, and what parts are difficult to use. It is quite comprehensive, there are over 100 questions. The questions reiterated to me that there are always some tidbits about HTML that you do not know! There are some things that I don't use, and some things that I may never use!&lt;/p&gt;

&lt;p&gt;Some things I never heard of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;input type="file" accept="video/*" capture&amp;gt;&lt;/code&gt; - Captures input from the user’s camera. That's an interesting one!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;controlslist&lt;/code&gt; attribute - Prevent certain controls from appearing in the toolbar of a media element e.g. &lt;code&gt;video&lt;/code&gt;. Nice to have the option but did not reach for it before.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;input.showPicker()&lt;/code&gt; - Programmatically opening the picker of form controls that have one (color pickers, date inputs etc).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contenteditable="plaintext-only"&lt;/code&gt; - Permits editing of the element's raw text, but not rich text formatting. I was aware of the attribute but not the value. Cool distinction but I'd always question if you should use this in the first place!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some things I heard of but have not used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;datalist&amp;gt;&lt;/code&gt; provides a method of providing a list of presets for a user to select in a form control, while still allowing custom options. Kind of helps with autocompletion. I never remember this one because it is not build into &lt;code&gt;input&lt;/code&gt;!!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;autocomplete&lt;/code&gt; attribute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;New things I heard of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dialog&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt; elements.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;inert&lt;/code&gt; attribute&lt;/li&gt;
&lt;li&gt;Popover API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regular pain points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Styling &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;select&lt;/code&gt; elements.&lt;/li&gt;
&lt;li&gt;Disclosure widgets - why can't I just use &lt;code&gt;details&lt;/code&gt;? I need to look up the caveats.&lt;/li&gt;
&lt;li&gt;Adding &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; to images to prevent Cumulative Layout Shift. Need to configure a tool to take care of this.&lt;/li&gt;
&lt;li&gt;There is a bit of a pardigm clash with responsive syntax for images that is based on viewport size and container queries that is based on the element size. I don't know how to tackle this in a consistent way yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HTML-y things that I haven't really used but would like to use more at some point in time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native web components&lt;/li&gt;
&lt;li&gt;Some Progressive Web App APIs - the ability to build a proper web app with core technologies and publish to an app store is a dream!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Web development is weird. Once all you had was HTML. It was &lt;em&gt;everything&lt;/em&gt; on the web. Now, HTML has become more of an final, output format like &lt;a href="https://en.wikipedia.org/wiki/PDF"&gt;PDF&lt;/a&gt; rather than something you write a webpage in! It is like a garnish on a dish, the most visible bit on top that you consider last and regard least! Yet, still a webpage is &lt;em&gt;nothing&lt;/em&gt; without HTML. It is a bit of paradox.&lt;/p&gt;

&lt;p&gt;I think there are improvements that can be made to HTML that will make the web better. HTML is not complete! The first port of call for me would be to improve the experience making user interfaces with HTML. Make UI controls (&lt;code&gt;select&lt;/code&gt; et al) easier to style and add behaviour to. The &lt;a href="https://open-ui.org/"&gt;Open UI&lt;/a&gt; group are advocating for adopting an industry standard definition of UI on the web. Let's listen to them. There is some movement on this front with the &lt;a href="https://chromestatus.com/feature/5737365999976448"&gt;experimental &lt;code&gt;selectmenu&lt;/code&gt; element&lt;/a&gt; in Chrome. Let's crank that up!&lt;/p&gt;

&lt;p&gt;I would like to see HTML evolve further. A paradigm to offer a partial update to a webpage is needed. There should be a HTML-y way to do the islands and hydration stuff that is sweeping through JavaScript. The &lt;a href="https://htmx.org/"&gt;HTMX&lt;/a&gt; library offers an example of what this could look like using attributes and leveraging existing protocols such as HTTP and websockets.&lt;/p&gt;

&lt;p&gt;I will be interested to see the results of the &lt;a href="https://stateofhtml.com/en-US"&gt;State of HTML survey&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How do you market software?</title>
      <dc:creator>Rob OLeary</dc:creator>
      <pubDate>Mon, 14 Aug 2023 09:10:23 +0000</pubDate>
      <link>https://dev.to/robole/how-do-you-market-software-15co</link>
      <guid>https://dev.to/robole/how-do-you-market-software-15co</guid>
      <description>&lt;p&gt;How do you market new software? Do you just splash it across social media and other tech platforms? Talk about it at conferences and on podcasts? Pay for advertising spots and sponsored posts?&lt;/p&gt;

&lt;p&gt;Mostly it seems people try to put their software in front of as many eyes and ears as possible. Is there something significant that will count in the longrun?&lt;/p&gt;

&lt;p&gt;I like &lt;a href="https://www.indiehackers.com/post/im-adam-wathan-i-created-tailwind-css-and-built-a-multi-million-dollar-business-around-it-ama-3c0732f724"&gt;Adam Whathan's observation about Tailwind's documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Some of the most important marketing we do I think though is our documentation. Making Tailwind as easy to learn and easy to be as successful with as possible is what makes the entire business able to work. Any effort I put into that I expect to have a much higher pay-off than paying for a Facebook ad for Tailwind UI or something, but it's absolutely marketing, and I think that's probably part of why it doesn't look like we are doing a lot of marketing from the outside. I try to put our marketing effort into things with a long-term pay-off, which is why we don't even do things like Black Friday sales anymore or anything either.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The docs are often the first impression that people have of your product. Great docs are one of the best things you can do to win people over. If someone can recommend your product by simply saying "just visit the website - its got everything you need to know", that's one of the best endorsements you can get.&lt;/p&gt;

&lt;p&gt;I recall a member of the Vue core team referring to their methodology as "documentation-driven development" when speaking on a podcast. The thesis was that if you can't explain a concept clearly and succinctly to others (through the docs), then maybe the code needs more work! It is similar to &lt;a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging"&gt;rubberducking&lt;/a&gt;, but for development. I think getting feedback like that early can really benefit the product. It is amazing how often we underestimate how hard it can be to explain something to an outsider who has not been involved in the project, you lose objectivity!&lt;/p&gt;

&lt;p&gt;Anything that gets you closer to making something people find intuitive and easy to pick up will put you on the road to success.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Is Lighthouse a misleading performance tool?</title>
      <dc:creator>Rob OLeary</dc:creator>
      <pubDate>Fri, 07 Jul 2023 10:00:00 +0000</pubDate>
      <link>https://dev.to/robole/is-lighthouse-a-misleading-performance-tool-4b14</link>
      <guid>https://dev.to/robole/is-lighthouse-a-misleading-performance-tool-4b14</guid>
      <description>&lt;p&gt;Google calls Lighthouse "an open-source, automated tool for improving the quality of web pages". It is not a performance tool per se, but a prominent feature is giving feedback on a webpage's performance. It is a &lt;em&gt;big&lt;/em&gt; challenge to get a top performance score for mobile in Lighthouse. If you have tried to attain a top score in Lighthouse -- it may have made you doubt yourself, the tool, or both! Let's explore Lighthouse to see why this is.&lt;/p&gt;

&lt;p&gt;Is Lighthouse misleading, or is it a misunderstanding?&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue 1 - The scoring scale is not linear
&lt;/h2&gt;

&lt;p&gt;You may think that the performance score is linear where &lt;strong&gt;a score of 100 is 10% better than a score of 90, but that is &lt;em&gt;not&lt;/em&gt; the case&lt;/strong&gt;. Actually the score follows a curved distribution, here is the &lt;a href="https://www.desmos.com/calculator/o98tbeyt1t" rel="noopener noreferrer"&gt;score curve of the Time to Interactive (TTI) metric&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Fscoring-curve-tti.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Fscoring-curve-tti.png" alt="TTI score curve"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Google &lt;a href="https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/#color-coding" rel="noopener noreferrer"&gt;mentions this in their docs when they discuss how scores are color coded&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To provide a good user experience, sites should strive to have a good score (90-100). A "perfect" score of 100 is extremely challenging to achieve and not expected. For example, taking a score from 99 to 100 needs about the same amount of metric improvement that would take a 90 to 94.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This characteristic of the calculation of the performance score means that the effort you put in to improve your score will vary depending on where you are on the curve. To make an analogy, it is like a runner putting in equal effort throughout a race:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running downhill: the runner will run faster;&lt;/li&gt;
&lt;li&gt;On the flat: the runner will run at their regular pace;&lt;/li&gt;
&lt;li&gt;Uphill: the runner will run slower.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perhaps, you did not expect this from a scoring system from zero to 100. I didn't! After all the word &lt;em&gt;percent&lt;/em&gt; means "one part in a hundred". This misunderstanding could have been mitigated if a different range or distribution was chosen. Maybe, it would trip less people up if they showed the score as a point on the curve for each metric?&lt;/p&gt;

&lt;p&gt;You can dig into the &lt;a href="https://web.dev/performance-scoring/#metric-scores" rel="noopener noreferrer"&gt;details of the scoring algorithm to understand it more deeply&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue 2 - Scores can vary a lot
&lt;/h2&gt;

&lt;p&gt;If you run Lighthouse on the same website using the same computer on the same network multiple times, you will get variable results. This feels weird at first. I'm repeating the exact same thing and getting a different result? Is this a bug or a warped reality?&lt;/p&gt;

&lt;p&gt;Google &lt;a href="https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/#fluctuations" rel="noopener noreferrer"&gt;says the following on score variability&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A lot of the variability in your overall Performance score and metric values is not due to Lighthouse. When your Performance score fluctuates it's usually because of changes in underlying conditions. Common problems include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A/B tests or changes in ads being served&lt;/li&gt;
&lt;li&gt;Internet traffic routing changes&lt;/li&gt;
&lt;li&gt;Testing on different devices, such as a high-performance desktop and a low-performance laptop&lt;/li&gt;
&lt;li&gt;Browser extensions that inject JavaScript and add/modify network requests&lt;/li&gt;
&lt;li&gt;Antivirus software&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is not due to Lighthouse? 🤔 Are we are trying to handcuff lightning here? 😏&lt;/p&gt;

&lt;p&gt;How variable can it be?&lt;/p&gt;

&lt;p&gt;Take testing on different hardware. The swing can be dramatic. Zach Leatherman discussed this in an article -- &lt;a href="https://www.zachleat.com/web/lighthouse-deception/" rel="noopener noreferrer"&gt;The Art of Deception, Lighthouse Score Edition&lt;/a&gt; -- running Lighthouse on a Macbook (2012) versus a MacBook Air (M1, 2020) resulted in &lt;strong&gt;a 30 point swing&lt;/strong&gt;! That's a lot.&lt;/p&gt;

&lt;p&gt;It appears that you can mitigate the impact of hardware by running Lighthouse through &lt;a href="https://developers.google.com/speed/docs/insights/v5/about" rel="noopener noreferrer"&gt;PageSpeed Insights (PSI)&lt;/a&gt;, Google's web-based user experience tool. I guess this hits a particular set of servers consistently.&lt;/p&gt;

&lt;p&gt;Google gives &lt;a href="https://github.com/GoogleChrome/lighthouse/blob/master/docs/variability.md#sources-of-variability" rel="noopener noreferrer"&gt;a full list of the technical factors for these variances&lt;/a&gt; if you want to get into the nitty gritty.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/GoogleChrome/lighthouse/blob/master/docs/variability.md#summary" rel="noopener noreferrer"&gt;advice in Lighthouse's GitHub repo to reduce variability&lt;/a&gt; is to "run Lighthouse multiple times and beware of variability before drawing conclusions about a performance-impacting change". Why not build this behaviour into Lighthouse to reduce variability?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.webpagetest.org/" rel="noopener noreferrer"&gt;WebPageTest&lt;/a&gt; is a rival web performance tool and their default behaviour is to give a median performance score based on 3 runs. The WebPageTest team have been &lt;a href="https://docs.webpagetest.org/running-lighthouse/#consistency-of-results" rel="noopener noreferrer"&gt;critical of the consistency of Lighthouse results&lt;/a&gt;. It is possible to run Lighthouse through WebPageTest, and they claim they can provide more consistent results from Lighthouse because they provide a more consistent test environment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While some variability between tests is to be expected, by providing a consistent test environment for all Lighthouse runs, WebPageTest helps to minimize that variability and provide a realistic and repeatable point of comparison.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They point to Lighthouse's use of simulated throttling as one source of variability that could be mitigated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By default, Lighthouse uses &lt;em&gt;simulated throttling&lt;/em&gt;: the test gets run without throttling, and then Lighthouse simulates what a throttled load might look like based on the unthrottled results.&lt;/p&gt;

&lt;p&gt;WebPageTest, on the other hand, uses &lt;em&gt;packet-level&lt;/em&gt; throttling for all tests, including Lighthouse tests run through WebPageTest. Because packet-level throttling enables network shaping at the packet-level, it's a far more accurate modeling of real network-conditions (there's a fascinating study by the &lt;a href="https://docs.google.com/document/d/1BqtL-nG53rxWOI5RO0pItSRPowZVnYJ_gBEQCJ5EeUE/edit" rel="noopener noreferrer"&gt;Lighthouse team about throttling accuracy&lt;/a&gt; if you want to wade into the weeds on the topic).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Issue 3 - The vast majority of websites are ranked as not good
&lt;/h2&gt;

&lt;p&gt;Let's go back to 2020, this was when Google made a big change regarding their performance rating -- they introduced the &lt;a href="https://web.dev/vitals/" rel="noopener noreferrer"&gt;Core Web Vitals&lt;/a&gt;. I want to discuss this timeframe because it was the last point where there is clear comparable data between the performance metric set (5 metrics) and the Core Web Vitals (3 metrics). The Core Web Vitals is a subset of the performance metric set.&lt;/p&gt;

&lt;p&gt;The Core Web Vitals was introduced as an effort to simplify things. To quote Google:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Site owners should not have to be performance gurus in order to understand the quality of experience they are delivering to their users. The Web Vitals initiative aims to simplify the landscape, and help sites focus on the metrics that matter most, the Core Web Vitals.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://almanac.httparchive.org/en/2020/performance" rel="noopener noreferrer"&gt;Web Almanac 2020 edition demonstrated in their performance review of the web&lt;/a&gt; that Lighthouse reported 0.7% of websites having a mobile performance score of 100, and &lt;strong&gt;5.7% of websites were in the good category (90-100). Was web performance really that bad? Or is the bar too high?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Fweb-almanac-perf-sept2020.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Fweb-almanac-perf-sept2020.png" alt="Graph of distribution of lighthouse scores between version 5 and version 6"&gt;&lt;/a&gt;&lt;/p&gt;
I used the same dataset as the Web Alamanac to extrapolate how many websites fell into the "good" catgegory for that period. The data can be found in &lt;a href="https://docs.google.com/spreadsheets/d/164FVuCQ7gPhTWUXJl1av5_hBxjncNi0TK8RnNseNPJQ/edit#gid=275193589" rel="noopener noreferrer"&gt;this Google spreadsheet&lt;/a&gt;.



&lt;p&gt;I was trying to understand how Google picks the good category thresholds and this is &lt;a href="https://web.dev/performance-scoring/#metric-scores" rel="noopener noreferrer"&gt;their clearest explanation, specifically for the Largest Contentful Paint (LCP) metric&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Based on real website data, top-performing sites render LCP in about 1,220ms, so that metric value is mapped to a score of 99.&lt;/p&gt;

&lt;p&gt;Going a bit deeper, the Lighthouse scoring curve model uses HTTPArchive data to determine two control points that then set the shape of a log-normal curve. The 25th percentile of HTTPArchive data becomes a score of 50 (the median control point), and the 8th percentile becomes a score of 90 (the good/green control point).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Does that mean that the upper 8% of the data represents a score of 90 and above? I don't get their explanation to be honest! 😕 Although it sounds about right based on my previous analysis from the Web Almanac.&lt;/p&gt;

&lt;p&gt;Barry Pollard did some analysis of Lighthouse scores across the web by querying data on the HTTP Archive in his article, &lt;a href="https://www.tunetheweb.com/blog/what-do-lighthouse-scores-look-like-across-the-web/" rel="noopener noreferrer"&gt;&lt;em&gt;What do Lighthouse Scores look like across the web?&lt;/em&gt;&lt;/a&gt;, and the results are similiar. He said the following about &lt;a href="https://www.tunetheweb.com/blog/what-do-lighthouse-scores-look-like-across-the-web/#top-level-scores" rel="noopener noreferrer"&gt;top level scores&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[..] 90% of sites score 80 or less on Performance or, to put it another way, only 10% of sites score higher than 80 in the Performance category.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It will always be that only a small portion of websites achieve a "good" performance score because it is the upper 8th percentile that make up this category. If web peformance dramatically improved across millions of websites overnight, the bar will be raised and even more is required to get into the "good" category.&lt;/p&gt;

&lt;p&gt;Based on the same data (the Chrome User Experience Report data that is available through the HTTP archive) for the same approximate period (August to October 2020), &lt;a href="https://httparchive.org/reports/chrome-ux-report?start=2020_08_01&amp;amp;end=2020_10_01&amp;amp;view=list#cruxPassesCWV" rel="noopener noreferrer"&gt;&lt;strong&gt;22.3% of pages passed all 3 Core Web Vital metrics with a "good" score&lt;/strong&gt;&lt;/a&gt;. More websites pass the Core Web Vitals than get a "good" performance score in Lighthouse.&lt;/p&gt;

&lt;p&gt;In the subsequent years, refinements to the performance scoring have been made. The latest version of Lighthouse is 10. Five of the same metrics are used in the scoring since version 6, the thresholds and weights have been tweaked. A new metric called &lt;a href="https://web.dev/inp/" rel="noopener noreferrer"&gt;Interaction to Next Paint (INP)&lt;/a&gt; has been introduced recently and will replace First Input Delay (FID) in March 2024 as a Core Web Vital metric.&lt;/p&gt;

&lt;p&gt;What I find strange is that Lighthouse in Chrome's devtools does not mention Core Web Vitals &lt;em&gt;at all&lt;/em&gt;. It still gives the performance score on 5 metrics. Why give people the more complex and more challenging set of metrics then?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Flighthouse-devtools-performance-section.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Flighthouse-devtools-performance-section.png" alt="lighthouse scores for roboleary.net"&gt;&lt;/a&gt;&lt;/p&gt;
No mention of Core Web Vitals in results for Lighthouse in the browser devtools



&lt;p&gt;For defining the thresholds, Google &lt;a href="https://blog.chromium.org/2020/05/the-science-behind-web-vitals.html" rel="noopener noreferrer"&gt;explains the science behind the thresholds related to human perception thresholds and relevant HCI research&lt;/a&gt;. The thresholds are based on how we percieve things, but how achievable is that on the web? Google &lt;a href="https://web.dev/defining-core-web-vitals-thresholds/#achievable-by-existing-web-content" rel="noopener noreferrer"&gt;says the following in their article on defining thresholds&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To confirm that a threshold is achievable, we require that at least 10% of origins currently meet the "good" threshold. Additionally, to ensure that well-optimized sites are not misclassified due to variability in field data, we also verify that well-optimized content consistently meets the "good" threshold.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So with all the numbers mentioned, the minimum requirement by Google is that 10% of the web is classified as meeting the "good" performance threshold for the Core Web Vitals. That sounds like the Core Web Vitals are a bit more lenient than the overall performance set, but are still very challenging.&lt;/p&gt;

&lt;p&gt;We can see &lt;a href="https://httparchive.org/reports/chrome-ux-report?start=2020_08_01&amp;amp;end=2020_10_01&amp;amp;view=list#cruxPassesCWV" rel="noopener noreferrer"&gt;figures for the Core Web Vitals for the last 3 plus years on HTTPArchive&lt;/a&gt;, the percentage of origins passing the Core Web Vitals for mobile has increased from 22.6% to 40.7%.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Fpassing-core-web-vitals-graph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Fpassing-core-web-vitals-graph.png" alt="Passing rate of Core web vitals graphed from 2020 to 2023. Mobile performance score has improved from 22.3% to 40.7%"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would love to see the same graph for the overall performance score. My guess is that would be a lot lower.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue 4 - Is it field data or lab data?
&lt;/h2&gt;

&lt;p&gt;It is important to understand the difference between lab data and field data. &lt;strong&gt;Lighthouse is a lab-based tool, also known as a synthetic tool&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Lab data is collected within a controlled environment with predefined device and network settings. Its main use is for debugging performance issues because it provides a reproducible testing and debugging environment. The downside is that lab data does not capture real-world bottlenecks well.&lt;/p&gt;

&lt;p&gt;Field data is performance data collected from real page loads your users are experiencing in the wild. Tools that gather field data are often referred to as Real User Monitoring (RUM) tools. &lt;strong&gt;Field data captures true real-world user experience.PageSpeed Insights uses the &lt;a href="https://developers.google.com/web/tools/chrome-user-experience-report" rel="noopener noreferrer"&gt;Chrome User Experience Report (CrUX) dataset&lt;/a&gt; to augment lab data provided by Lighthouse for the same metrics.&lt;/strong&gt; However, your page or origin may not be in the dataset because it is not publicly discoverable or there are not a large enough number of visitors in order to create a statistically significant dataset.&lt;/p&gt;

&lt;p&gt;A good example of this dicothomy is to view a PSI report on &lt;a href="//web.dev"&gt;web.dev&lt;/a&gt;, this is Google's blog that has a lot of information on Lighthouse. You can see the result of the very test I ran at this URL: &lt;a href="https://pagespeed.web.dev/analysis/https-web-dev/hp4cd34d4i?form_factor=mobile" rel="noopener noreferrer"&gt;https://pagespeed.web.dev/analysis/https-web-dev/hp4cd34d4i?form_factor=mobile&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Fpsi-report-webdotdev.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Fpsi-report-webdotdev.webp" alt="PageSpeed Insights report on web.dev"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lighthouse reported a performance score of 96, but it failed the Core Web Vitals! At a glance, it can look like a mistake! How did that happen?&lt;/p&gt;

&lt;p&gt;It is because PSI reports different figures for the LCP metric for the Core Web Vitals and the overall performance score (see yellow highlights in screenshot below)! The figures are different because PSI uses field data from the CrUX dataset for the Core Web Vitals (when it is available) in the first section, whereas lab data is used for the performance score in the second section.&lt;/p&gt;

&lt;p&gt;You may miss this! Having 2 different metric sets using 2 different datasets side by side was confusing for me initially. Also, if you are focusing on the Core Web Vitals, there are 2 sets based on the testing method:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lab testing in Lighthouse:&lt;/strong&gt; Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS), &lt;em&gt;Total Blocking Time (TBT)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Field testing in PageSpeed Insights:&lt;/strong&gt; Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS), &lt;em&gt;First Input Delay (FID)&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Previously, the PSI report was more explicit about whether field data or lab data is being used the results shown. Here is an example screenshot of the PSI report from a few years ago:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Fpsi-previous-ui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Fpsi-previous-ui.png" alt="PSI report from 2021 has a different UI than now. It shows that"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think that the updates to the UI look prettier but are less apparent.&lt;/p&gt;

&lt;p&gt;You can read more about how think of tools in &lt;a href="https://web.dev/speed-tools/" rel="noopener noreferrer"&gt;How To Think About Speed Tools by web.dev&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue 5 - Mobile or Desktop?
&lt;/h2&gt;

&lt;p&gt;When people discuss and compare Lighthouse scores, often they take screenshots to keep a record. There is &lt;strong&gt;no indication in the UI if results are for mobile or desktop&lt;/strong&gt;. The thresholds for mobile performance are higher. This is avenue for mistakes and misrepresentation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Fmy-lighthouse-score.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Fmy-lighthouse-score.png" alt="lighthouse scores for roboleary.net"&gt;&lt;/a&gt;&lt;/p&gt;
Is this a Mobile or Desktop score?



&lt;p&gt;There has been discussion about &lt;a href="https://github.com/GoogleChrome/lighthouse/issues/8178" rel="noopener noreferrer"&gt;adding a visual indicator&lt;/a&gt; to make the mode more obvious, but it has not made it into Chrome devtools!&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue 6 - People inevitably aim for near perfect scores
&lt;/h2&gt;

&lt;p&gt;Inevitably, people aim to get a near perfect performance score. People take pride in what they do and want to point to something they made, and say "check out the performance of this". If you build a tool with high thresholds, then you put achieving a top score out of reach for some types of websites and web applications. There is no differentiation between a demanding web store like amazon, a web application like Google Docs, and a personal website.&lt;/p&gt;

&lt;p&gt;To highlight this situation, there is &lt;a href="https://github.com/GoogleChrome/lighthouse/discussions/12949" rel="noopener noreferrer"&gt;a discussion thread, "Instruction to get score 100 on the mobile" on the Lighthouse GithHub repo&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I have used the lighthouse to monitor a website for the performance. However, it's really hard to get 100 score for the mobile. I only can get the score 100 for the mobile with the site that contains only a static text without css, javascript.&lt;/p&gt;

&lt;p&gt;I'm not sure if lighthouse team considers that the website contains only a static text is popular nowaday for the modern website.&lt;/p&gt;

&lt;p&gt;Of course, the PWA is not standard today yet and even for the PWA, we must load for "full state" mode as well.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I was surprised by this a while back too. I approached rebuilding my personal website by starting with the simplest possible homepage. I had no images, quite a small stylesheet, and I think I used 3 web fonts. It did not get a "good" mobile score! I had to optimize these assets to climb into the 90's.&lt;/p&gt;

&lt;p&gt;Another part of this is that when numbers are involved, it can led to a competitive element. Frameworks and libraries lean into this to promote the speed and performance of their offering. Eleventy has a &lt;a href="https://www.11ty.dev/speedlify/" rel="noopener noreferrer"&gt;leaderboard&lt;/a&gt; that uses a Lighthouse-based plugin called &lt;a href="https://github.com/zachleat/speedlify/" rel="noopener noreferrer"&gt;speedlify&lt;/a&gt; to rank websites.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Feleventy-leaderboard.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.roboleary.net%2Fassets%2Fimg%2Fblog%2F2023-07-06-lighthouse-misleading%2Feleventy-leaderboard.webp" alt="eleventy website leaderboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Is Lighthouse suitable for comparing sites in this way? 🤨&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Measuring web performance is a difficult proposition. We are not making homogeneous web-based products in an uniform way. This makes it a challenge to define what is good performance for something on the web. Google has been active in defining what is good performance through its metrics and tools, and has a big say on the matter.&lt;/p&gt;

&lt;p&gt;Google calls Lighthouse "an open-source, automated tool for improving the quality of web pages". It inspects a few different facets of a webpage in its audits such as: performance, SEO, and accessibility. It is not a performance auditing tool per se, but it has a big presence in that space because Google made it, put it into Chrome, and &lt;a href="https://developers.google.com/search/docs/appearance/core-web-vitals" rel="noopener noreferrer"&gt;announced it that the Core Web Vitals metrics are a factor in their search ranking&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Lighthouse is primarily a lab-based tool that is used for performance debugging. It has some characteristics that are not apparent. The scoring calculation is byzantine, results can be very variable, and it is very difficult to get a "good" performance score for mobile. As I covered in this article, some of it can attributed to the need to understand web performance and Lighthouse fairly well, but in some ways Lighthouse is misleading.&lt;/p&gt;

&lt;p&gt;Google says a perfect mobile performance score of 100 is "extremely challenging to achieve". Their approach to performance classification is a lot more stick than carrot. In late 2020, by Lighthouse's classification less than 6% of web origins were deemed to have attained "good" performance, whereas 22.3% passed the Core Web Vital metrics. The Core Web Vital is a more lenient set of metrics.&lt;/p&gt;

&lt;p&gt;The Core Web Vitals has made more businesses pay attention to web performance. As the &lt;a href="https://almanac.httparchive.org/en/2022/performance" rel="noopener noreferrer"&gt;Web Almanac put it in 2022 performance review&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Google’s decision to make CWV [Core Web Vital] part of search ranking catapulted performance to the top of many companies’ roadmaps, especially in the SEO industry. Individual site owners are certainly working hard to improve their performance and played a major role in the CWV improvements over the last year, even if those individual efforts are much harder to spot at this scale.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The percentage of origins passing the Core Web Vitals for mobile at the time of writing is 40.7%.&lt;/p&gt;

&lt;p&gt;The aim of the Web Vitals initiative was to simplify the performance landscape, it hasn't done that well in my opinion. There is a lack of clarity and focus. Your performance score is still based on the complete set of metrics. The complete metric set is shown in Chrome's devtools, which is where many people encounter Lighthouse for the first time.&lt;/p&gt;

&lt;p&gt;The CWV metrics haven't been embraced fully anywhere really. PSI shows the CWV metrics first, but 3 more metrics sit right alongside them. It does not give a clear message to users - should you be passing CWV or getting a "good" performance score or both? And what is a &lt;em&gt;realistic&lt;/em&gt; score for your particular type of application?&lt;/p&gt;

&lt;p&gt;Score variability means that Lighthouse comes with caveats. Generally it is not a very reliable performance debugging tool. Since score variability is skewed by your machine's performance when it is run locally, it is probably not a good idea to run Lighthouse in the Chrome's devtools. It is better to use Lighthouse through WebPageTest where it does more to mitigate variability, or use other tools for debugging performance.&lt;/p&gt;

&lt;p&gt;I would recommend using Lighthouse primarily to understand how Google classifies your website. The &lt;em&gt;opportunities&lt;/em&gt; presented by the Lighthouse audit give you a rough guide to improve performance but take it with a dash of salt. Field data gives you a more realistic view of user experience and you should favour that for understanding the performance of your website.&lt;/p&gt;




&lt;p&gt;You can subscribe to &lt;a href="https://www.roboleary.net/feed.xml" rel="noopener noreferrer"&gt;my RSS feed&lt;/a&gt; to get my latest articles.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>testing</category>
      <category>productivity</category>
    </item>
    <item>
      <title>AI and the open web</title>
      <dc:creator>Rob OLeary</dc:creator>
      <pubDate>Mon, 03 Jul 2023 10:00:00 +0000</pubDate>
      <link>https://dev.to/robole/ai-and-the-open-web-4pnb</link>
      <guid>https://dev.to/robole/ai-and-the-open-web-4pnb</guid>
      <description>&lt;p&gt;It seems that there is a growing protectionist trend where large platforms are restricting access to data more tightly. It has come to the forefront recently as &lt;a href="https://en.wikipedia.org/wiki/Large_language_model"&gt;large language models&lt;/a&gt; such as &lt;a href="https://en.wikipedia.org/wiki/Generative_pre-trained_transformer"&gt;GPT&lt;/a&gt; used by &lt;a href="https://en.wikipedia.org/wiki/ChatGPT"&gt;ChatGPT&lt;/a&gt; have become a more mainstream success story. Platforms are concerned their data is being used to fuel the burgeoning industry and they are not being compensated. If it leads to more closed behaviour on the web, it will become a negative trend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protectionist trend - Reddit, now Twitter
&lt;/h2&gt;

&lt;p&gt;In June, Reddit raised prices on their API. Reddit’s owners are planning to take the company public, and they are looking to boost revenue from the social news site before they do. &lt;a href="https://www.nytimes.com/2023/04/18/technology/reddit-ai-openai-google.html"&gt;Reddit founder and CEO Steve Huffman told The New York Times&lt;/a&gt; "The Reddit corpus of data is really valuable, but we don’t need to give all of that value to some of the largest companies in the world for free."&lt;/p&gt;

&lt;p&gt;This has led to an ongoing strike with volunteer moderators that has caused mass disruption on the platform. Steve Huffman said that the business will not be backing down. He told The Associated Press, "Protest and dissent is important. The problem with this one is it’s not going to change anything because we made a business decision that we’re not negotiating on." It has reached an impasse.&lt;/p&gt;

&lt;p&gt;Yesterday, Elon Musk announced that Twitter is putting a limit on how many posts you can read per day. This is what he said in a tweet:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To address extreme levels of data scraping &amp;amp; system manipulation, we’ve applied the following temporary limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verified accounts are limited to reading 6000 posts/day&lt;/li&gt;
&lt;li&gt;Unverified accounts to 600 posts/day&lt;/li&gt;
&lt;li&gt;New unverified accounts to 300/day&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Later, Musk tweeted that the limit had been raised to 10,000, 1,000, and 500 respectively.&lt;/p&gt;

&lt;p&gt;"Several hundred organizations (maybe more) were scraping Twitter data extremely aggressively, to the point where it was affecting the real user experience," Musk said.&lt;/p&gt;

&lt;p&gt;It sounds strange that there is this kind of scraping being done at scale. It is an inefficient way to gather that kind of data. Even if Twitter is worried that some companies are getting around paying for access to its API by scraping webpages, restricting usage for regular users seems like cutting off your nose to spite your face. Usually, businesses want to encourage people to use their service as much as possible, because that is how they make money!&lt;/p&gt;

&lt;h2&gt;
  
  
  How will it play out?
&lt;/h2&gt;

&lt;p&gt;It is hard to tell how this will play out. It is a battle to monetize this new frontier. The data holders want a slice of the pie if they are a prime sources for language models to train models to interact in a more human-like fashion. It could be that this is being opportunistically used to increase prices for API access. Blame the bots! The truth is that it is hard to know what the reality is unless you are behind the scenes.&lt;/p&gt;

&lt;p&gt;Users are suffering as they are put in the middle. The market for third-party apps shrinks and it can become untenable for some small businesses. That is bad for consumer choice.&lt;/p&gt;

&lt;p&gt;Web standards need to adapt. At the moment, I guess AI bots are indexing pages like search engine bots based on the &lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots/robots-faq"&gt;&lt;em&gt;robots.txt&lt;/em&gt;&lt;/a&gt; file. Permission for using data for language models is not explicit as far as I know. You may have to explicitly block a bot to opt out. For example, OpenAI has published &lt;a href="https://platform.openai.com/docs/plugins/bot"&gt;instructions for blocking its bot&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is likely that regulation will be required in the long-term. The major players are large companies and they have a big advantage. It will depend on if they want to defend their high ground aggressively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Personally, I don't see this as an alarming thing. This is a familiar fight. It is just something that we need to figure out.&lt;/p&gt;

&lt;p&gt;Open information and commerce have always been incongruent. This is a battle over information —  who produces it, how you access it, and who gets paid for it. In Reddit's case, it is galling that their data is moderated by users for free and is being sold at a growing cost -- it will be an interesting test case to see how this side of the AI revolution evolves. It is important how this is settled because it will shape what the web will become.&lt;/p&gt;

&lt;p&gt;We should try to perserve openness, it is a great strength of the web. There needs to be a viable commercial solution to satisfy business needs. If one is not found, we need to mitigate harm being done through regulation.&lt;/p&gt;

</description>
      <category>news</category>
    </item>
  </channel>
</rss>
