<?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: Joe Dietrich</title>
    <description>The latest articles on DEV Community by Joe Dietrich (@joedietrichdev).</description>
    <link>https://dev.to/joedietrichdev</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%2F691454%2F831c77bf-0a91-44be-9234-6c9d6107a42f.jpeg</url>
      <title>DEV Community: Joe Dietrich</title>
      <link>https://dev.to/joedietrichdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joedietrichdev"/>
    <language>en</language>
    <item>
      <title>Must/Should/Can - a Personal Organization System</title>
      <dc:creator>Joe Dietrich</dc:creator>
      <pubDate>Sun, 20 Feb 2022 23:17:04 +0000</pubDate>
      <link>https://dev.to/joedietrichdev/mustshouldcan-a-personal-organization-system-499e</link>
      <guid>https://dev.to/joedietrichdev/mustshouldcan-a-personal-organization-system-499e</guid>
      <description>&lt;p&gt;Repo: &lt;a href="https://github.com/joedietrich-dev/must-should-can"&gt;joedietrich-dev/must-should-can&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;A little while ago, I found myself struggling to bring order to my tasks at work. I'd tried many different organizational systems. Some didn't suit my work style, others were way too complicated - adding to my daily tasks rather than making them easier. I decided to put together a system that worked for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The System
&lt;/h2&gt;

&lt;p&gt;I divide my tasks for the day into three buckets: Tasks I &lt;strong&gt;must&lt;/strong&gt; do today, Tasks I &lt;strong&gt;should&lt;/strong&gt; do today, Tasks I &lt;strong&gt;can&lt;/strong&gt; do today. Every day, I rewrite and carry over any incomplete tasks to the next day. It's simple, but it works for me!&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Features
&lt;/h2&gt;

&lt;p&gt;I took the simple pen-and-paper tools and made them digital. The features of Must/Should/Can are straightforward, as is system itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Account Creation and Login&lt;/li&gt;
&lt;li&gt;Task Creation, Editing, and Prioritization&lt;/li&gt;
&lt;li&gt;Task Resets&lt;/li&gt;
&lt;li&gt;Task Archiving and Deleting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Used
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://rubyonrails.org/"&gt;Ruby on Rails&lt;/a&gt; as the framework for the API&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rails-api/active_model_serializers"&gt;ActiveModelSerializers&lt;/a&gt; to build JSON views&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://postgresql.org"&gt;PostgreSQL&lt;/a&gt; as the database&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/bcrypt-ruby/bcrypt-ruby"&gt;bcrypt&lt;/a&gt; gem to improve password security in tandem with the ActiveRecord &lt;a href="https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password"&gt;&lt;code&gt;has_secure_password&lt;/code&gt;&lt;/a&gt; feature&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt; / &lt;a href="https://create-react-app.dev/"&gt;Create React App&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://reactrouter.com/"&gt;React Router v6&lt;/a&gt; - For client-side routing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://styled-components.com/"&gt;Styled Components&lt;/a&gt; to style the application&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Authorization, Passwords, and Salting
&lt;/h2&gt;

&lt;p&gt;While building Must/Should/Can, it did not escape my attention that a user's tasks could be very private, so there was a need to protect them as much as possible. To ensure that privacy, I not only implemented user authorization and password authentication, I protected their passwords with the ActiveRecord &lt;code&gt;has_secure_password&lt;/code&gt; feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;has_secure_password&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If you're storing passwords in any system, it is a &lt;strong&gt;very bad idea&lt;/strong&gt; to store them in plaintext &lt;strong&gt;anywhere&lt;/strong&gt; in your application. Doing so exposes you and your users to potential data losses, which is a Bad Thing. The &lt;code&gt;has_secure_password&lt;/code&gt; feature adds methods to an ActiveRecord model that make setting and authenticating securely hashed and salted passwords on your user models easy. &lt;/p&gt;

&lt;p&gt;Under the hood, &lt;code&gt;has_secure_password&lt;/code&gt; uses the &lt;code&gt;bcrypt&lt;/code&gt; gem to hash and salt your user's passwords. This process makes it very difficult for bad actors to access your users' password data, even if they manage to steal your database. &lt;/p&gt;

&lt;p&gt;Hashing is the process of taking data and processing it to create a new value, usually of a fixed length (sometimes called a fingerprint). The process is unidirectional, meaning once a value has been hashed, it is incredibly impractical (with current technology) to reverse the process to derive the original value from the hash. For example, using bcrypt, the password &lt;code&gt;Wolfgang the puppy&lt;/code&gt; might hash to the value &lt;code&gt;$2a$12$j29LhAzasXWN7glfGjp9NuFXcOYBCffkE4RWcQJwBFzxsAsUsQ2nK&lt;/code&gt;. This unidirectionality is what makes hashed passwords more secure than plaintext passwords - a hacker will need to do extra work to break the encryption involved.&lt;/p&gt;

&lt;p&gt;Or they might have a Rainbow Table, which is a precomputed set of values that will let an attacker look up the password based on a given hash. If the hashing function is known to the attacker, hashing alone won't be enough to protect a user's password, since the same input value will always produce the same output hash.&lt;/p&gt;

&lt;p&gt;This is why bcrypt will also &lt;strong&gt;salt&lt;/strong&gt; a password before storing the hash in your database. A salt is data added to the input of a hash function. In bcrypt's implementation, a unique salt is added to every password on generation. This means that an attacker would need to use a different pre-computed Rainbow Table for every single password, which is computationally prohibitive. &lt;/p&gt;

&lt;p&gt;All of this means that, properly implemented, using &lt;code&gt;has_secure_password&lt;/code&gt; and &lt;code&gt;bcrypt&lt;/code&gt; in your application is one important step protect you and your users from bad actors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;I plan to introduce the ability to add notes to tasks so you can, for example, sketch out an agenda for a meeting, or divide tasks into subtasks. I also plan to enhance the archive with grouping and sorting. Later on, I'll enhance the user's account management experience, letting them reset their password and edit their user name.&lt;/p&gt;

&lt;h2&gt;
  
  
  End
&lt;/h2&gt;

&lt;p&gt;Thanks for reading! For a walkthrough, take a look at the &lt;a href="https://youtu.be/rDLsf4lwwns"&gt;demo video&lt;/a&gt;. Access the application itself at &lt;a href="https://must-should-can.herokuapp.com/"&gt;https://must-should-can.herokuapp.com/&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>rails</category>
    </item>
    <item>
      <title>Building an Event Landing Page Management System</title>
      <dc:creator>Joe Dietrich</dc:creator>
      <pubDate>Mon, 20 Dec 2021 00:07:15 +0000</pubDate>
      <link>https://dev.to/joedietrichdev/building-an-event-landing-page-management-system-1d3m</link>
      <guid>https://dev.to/joedietrichdev/building-an-event-landing-page-management-system-1d3m</guid>
      <description>&lt;p&gt;Frontend: &lt;a href="https://github.com/joedietrich-dev/event-page-client"&gt;joedietrich-dev/event-page-client&lt;/a&gt;&lt;br&gt;
Backend: &lt;a href="https://github.com/joedietrich-dev/event-pages-server"&gt;joedietrich-dev/event-pages-server&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;I was inspired to build this when I was tasked with repeatedly hand-coding landing pages in simple html. This was inefficient in terms of time spent and meant I was the only person around able to maintain the pages or make any changes. I set out to create a proof-of-concept / MVP event page builder. It has nearly all the entities I need to build those hand-coded pages.&lt;/p&gt;
&lt;h2&gt;
  
  
  Basic Features
&lt;/h2&gt;

&lt;p&gt;This is the backend for a simple proof-of-concept event page creator. It is designed to let you build events with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hosts&lt;/li&gt;
&lt;li&gt;Honorees&lt;/li&gt;
&lt;li&gt;Panels &amp;amp; Panelists&lt;/li&gt;
&lt;li&gt;Sponsors of different levels&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What I Used
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt; / &lt;a href="https://create-react-app.dev/"&gt;Create React App&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://reactrouter.com/"&gt;React Router v6&lt;/a&gt; - For client-side routing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mui.com/"&gt;MUI&lt;/a&gt; - A UI component library&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mui.com/components/material-icons/"&gt;MUI Icons&lt;/a&gt; - For iconography&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://sinatrarb.com/"&gt;Sinatra&lt;/a&gt; - a domain-specific language used to build the API framework for the app&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://guides.rubyonrails.org/active_record_basics.html"&gt;ActiveRecord&lt;/a&gt; - Rails' ORM, here used with Sinatra to map the Ruby classes to the database&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; - the database engine used in this project&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ruby/rake"&gt;Rake&lt;/a&gt; - to run commands on the command line&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/faker-ruby/faker"&gt;Faker&lt;/a&gt; - used to build out seed data&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Entities and their relationships
&lt;/h2&gt;

&lt;p&gt;Before I built out the migrations for the database, I sat down and planned the data structure. Below is a diagram of the different tables in the application's database and their relationships to one another.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XtMR2M3k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xk0m1epkda7xbrl6fde4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XtMR2M3k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xk0m1epkda7xbrl6fde4.png" alt="Event Page Management Entity relationships" width="880" height="888"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Faker
&lt;/h2&gt;

&lt;p&gt;Before I started building out the front end of the application, I knew I needed data to work with. Having real-looking data helps in the design process, and using &lt;a href="https://github.com/faker-ruby/faker"&gt;Faker&lt;/a&gt; means you don't need to build out your database by hand.&lt;br&gt;&lt;br&gt;
Faker has a large number of different generators you can use to make your data more realistic or even if you just want to get a little more creative. I used and would recommend the:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Company generator - for company names&lt;/li&gt;
&lt;li&gt;Lorem generator - for lorem ipsum placeholder text&lt;/li&gt;
&lt;li&gt;Number generator - for years&lt;/li&gt;
&lt;li&gt;Nation and Hobby generators - for somewhat sensical sounding event names (e.g. 2022 Malaysian Drumming Awards)&lt;/li&gt;
&lt;li&gt;Hipster and GreekPhilosopher generators - for English (or English-adjacent) text placeholders&lt;/li&gt;
&lt;li&gt;Name and Job generators - for people's names and job titles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these together helped me create reasonable placeholder data, which you can see in the &lt;a href="https://www.youtube.com/watch?v=Mzy1-Xqw9qE"&gt;demo video&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Eliminating Orphan Records
&lt;/h2&gt;

&lt;p&gt;When I started building the interface for the app, I realized that deleting certain entities, like events or panels, could leave orphaned records. These were records that could not logically exist independently of their parent entities - especially the linking records between the panels and panelists and events and sponsors. I didn't want this, since orphaned records are just taking up space, at best, and could cause unexpected behavior at worst.&lt;/p&gt;

&lt;p&gt;I solved this problem by setting the &lt;a href="https://guides.rubyonrails.org/association_basics.html#dependent"&gt;&lt;code&gt;dependent: :destroy&lt;/code&gt;&lt;/a&gt; option on some of my models' associations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Panel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:event&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:panel_panelists&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:panelists&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;through: :panel_panelists&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:panel_sponsors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:sponsors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;through: :panel_sponsors&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take the &lt;code&gt;Panel&lt;/code&gt; class as an example. A panel belongs to an event, and has many panelists and sponsors through linking models. When a panel is deleted, the panelists and sponsors should remain, since they can be associated with many panels. The linking records, &lt;code&gt;panel_panelists&lt;/code&gt; and &lt;code&gt;panel_sponsors&lt;/code&gt; are unique to the panel being deleted. With &lt;code&gt;dependent: :destroy&lt;/code&gt;, those records are deleted along with the panel.&lt;/p&gt;

&lt;h2&gt;
  
  
  End
&lt;/h2&gt;

&lt;p&gt;Thanks for reading! For a walkthrough, take a look at the &lt;a href="https://www.youtube.com/watch?v=Mzy1-Xqw9qE"&gt;demo video&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Build a fixed-aspect-ratio photo gallery with hover effects (using only HTML &amp; CSS!)</title>
      <dc:creator>Joe Dietrich</dc:creator>
      <pubDate>Fri, 22 Oct 2021 02:56:09 +0000</pubDate>
      <link>https://dev.to/joedietrichdev/build-a-fixed-aspect-ratio-photo-gallery-with-hover-effects-using-only-html-css-5b40</link>
      <guid>https://dev.to/joedietrichdev/build-a-fixed-aspect-ratio-photo-gallery-with-hover-effects-using-only-html-css-5b40</guid>
      <description>&lt;p&gt;In this article, I'll explain how to create a gallery of photos with text overlays using modern CSS and HTML, as seen in the Code Sandbox below. &lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/zen-keldysh-096lt"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;As I was building out a test photo gallery, app I realized that I wanted my main page to be a grid of square images. The pictures were supposed to be the star of the show, but I wanted the viewer to be able to see a description of each image when they hovered over it. To spice it up a little, I decided to add a "zoom in" effect on hover.&lt;/p&gt;

&lt;p&gt;My goal was to use modern CSS and HTML only, if I could. I decided to start with CSS Grid as a base.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step One - The Grid
&lt;/h2&gt;

&lt;p&gt;The first step was building out the grid itself. To start with, I created a container div and a few tiles:&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid-tile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid-tile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid-tile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid-tile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid-tile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid-tile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid-tile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid-tile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;grid-container&lt;/code&gt; will hold all our &lt;code&gt;grid-tiles&lt;/code&gt;. The &lt;code&gt;grid-tiles&lt;/code&gt; will then hold the images and the overlays.&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;.grid-container&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="n"&gt;auto-fit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;150px&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;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.grid-tile&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="n"&gt;rgba&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="m"&gt;0&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="m"&gt;0.1&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;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;Here, we set the &lt;code&gt;grid-container&lt;/code&gt; to display as a grid with a 20px gap between its rows and columns. We also define the width of the columns of the grid in &lt;code&gt;grid-template-columns&lt;/code&gt;. With &lt;code&gt;repeat(auto-fit, minmax(150px, 1fr))&lt;/code&gt;, we declare that we want to create columns that are at least 150px and will otherwise evenly grow to fill the available width of the container (&lt;code&gt;minmax(150px, 1fr)&lt;/code&gt;). The &lt;code&gt;auto-fit&lt;/code&gt; keyword will only create as many columns as we need - if we only have two child elements, only two columns will be created.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For a great auto-fit / auto-fill breakdown, check out (Auto-Sizing Columns in CSS Grid)[&lt;a href="https://css-tricks.com/auto-sizing-columns-css-grid-auto-fill-vs-auto-fit/"&gt;https://css-tricks.com/auto-sizing-columns-css-grid-auto-fill-vs-auto-fit/&lt;/a&gt;]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The critical part of the &lt;code&gt;grid-tile&lt;/code&gt; style is &lt;code&gt;aspect-ratio: 1&lt;/code&gt;. This tells the browser that we want square tiles. &lt;code&gt;aspect-ratio&lt;/code&gt; accepts whole numbers or fractions - without units - in the format &lt;code&gt;&amp;lt;width&amp;gt; / &amp;lt;height&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; &lt;code&gt;aspect-ratio&lt;/code&gt; is supported in the most recent version of all major browsers as of this writing, but support does not include Internet Explorer or Safari before version 15. You can replicate some of this without &lt;code&gt;aspect-ratio&lt;/code&gt; by setting &lt;code&gt;grid-template-columns: repeat(auto-fit, 150px);&lt;/code&gt; and &lt;code&gt;grid-auto-rows: 150px;&lt;/code&gt; in the &lt;code&gt;grid-container&lt;/code&gt; class. You lose flexibility, but maintain the fixed aspect ratio.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With these rules in place we should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/zen-keldysh-096lt?initialpath=/step_one/index.html"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Step Two - The Overlay
&lt;/h2&gt;

&lt;p&gt;Now that we have the grid itself, we can add a text overlay.&lt;/p&gt;

&lt;p&gt;We'll add the following code inside each of our &lt;code&gt;grid-tile&lt;/code&gt; divs:&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tile-overlay tile-overlay-bottom"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tile-overlay-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello, World!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The a &lt;code&gt;tile-overlay&lt;/code&gt; div will contain an elements that we want to appear on hover over the tile. The &lt;code&gt;tile-overlay-bottom&lt;/code&gt; styles will ensure the element appears on the bottom of the tile. We'll style &lt;code&gt;tile-overlay-text&lt;/code&gt; for readability on a darker background.&lt;/p&gt;

&lt;p&gt;We need the tile to have &lt;code&gt;position: relative&lt;/code&gt; so we can place its children absolutely, otherwise, the children will be positioned relative to the window itself, which isn't what we want.&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;.grid-tile&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="n"&gt;rgba&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="m"&gt;0&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="m"&gt;0.1&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;1&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="nc"&gt;.tile-overlay&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;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&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="m"&gt;0&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="m"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.tile-overlay-bottom&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;bottom&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;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="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.tile-overlay-text&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;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="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&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;blockquote&gt;
&lt;p&gt;As a challenge to yourself, try to implement an overlay element that will appear in the top-right of each tile.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the end of Step Two, you'll have something like this:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/zen-keldysh-096lt?initialpath=/step_two/index.html"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Step Three - Overlay Hover Effect
&lt;/h2&gt;

&lt;p&gt;So far, we have a grid of tiles and we have an overlay. That's great, but the overlay is there all the time and I promised you hover effects.&lt;/p&gt;

&lt;p&gt;To get us there, we'll need to add a couple lines to our 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;.tile-overlay&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;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&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="m"&gt;0&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="m"&gt;0.75&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="nc"&gt;.grid-tile&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="nc"&gt;.tile-overlay&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;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;With these few lines of code, we're giving everything with the &lt;code&gt;tile-overlay&lt;/code&gt; class an opacity of 0%, which means the element will still take up its usual space, but will not be visible. &lt;/p&gt;

&lt;p&gt;The next rule says, "Every time a viewer &lt;code&gt;hover&lt;/code&gt;s over a &lt;code&gt;grid-tile&lt;/code&gt;, I want the &lt;code&gt;opacity&lt;/code&gt; of the &lt;code&gt;tile-overlay&lt;/code&gt;s to go to 100% (be completely visible)."&lt;/p&gt;

&lt;p&gt;And with that, we have one of our hover effects!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For a smoother &lt;code&gt;transition&lt;/code&gt; between opacity 0% and opacity 100%, you can insert a &lt;code&gt;transition&lt;/code&gt; property into the &lt;code&gt;.tile-overlay&lt;/code&gt; rule: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;transition: opacity 0.33s;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will extend the length of the transition from 0% to 100% opacity from instant to a third of a second.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The end of Step Three should look like this:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/zen-keldysh-096lt?initialpath=/step_three/index.html"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Step Four - Adding Images
&lt;/h2&gt;

&lt;p&gt;So far, we've built the cards and an overlay, but our photo gallery isn't any good if there aren't any photos! Start off by adding &lt;code&gt;img&lt;/code&gt; tags to your &lt;code&gt;grid-tile&lt;/code&gt; divs. When you're done, the &lt;code&gt;grid-tile&lt;/code&gt; html should look something 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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid-tile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tile-image"&lt;/span&gt;
      &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://source.unsplash.com/random/200x250"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tile-overlay tile-overlay-bottom"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tile-overlay-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello, World!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &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;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you check out your page now, you'll see that the images are overflowing the tiles, so we're going to need to turn to CSS for help.&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;.tile-image&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;object-fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cover&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;This new CSS rule will constrain our images to the height and width of their parent - in this case, the &lt;code&gt;grid-tile&lt;/code&gt;. A value of &lt;code&gt;cover&lt;/code&gt; for the &lt;code&gt;object-fit&lt;/code&gt; property means that the image will maintain its aspect ratio, but anything outside of the parent element will be clipped. This keeps our images from being distorted, but means we lose some of the image content.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One last thing to note for this phase. You may have noticed that our tiles are no longer perfect squares, depending on your browser. There may be a small strip of gray background below the image in each tile. I believe this has something to do with the image distorting the grid, but I haven't been able to figure it out yet. If you know why this happens, please leave a comment!&lt;/p&gt;

&lt;p&gt;We can fix that strip pretty easily by adding &lt;code&gt;overflow: hidden&lt;/code&gt; to the &lt;code&gt;grid-tile&lt;/code&gt; rule. This will also be important for the next step, so let's go ahead and do it now:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.grid-tile&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="n"&gt;rgba&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="m"&gt;0&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="m"&gt;0.1&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;1&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="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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the end of Step Four, you'll have something like this:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/zen-keldysh-096lt?initialpath=/step_four/index.html"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Step Five - The Image Zoom Hover Effect
&lt;/h2&gt;

&lt;p&gt;What we have is great so far, but let's jazz it up a little. We want it to look like the image is coming slightly closer to you when you hover over it.&lt;/p&gt;

&lt;p&gt;First, we'll add some styles to the image to provide a baseline for what we'll do on hover:&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;.tile-image&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="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="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;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;object-fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.33s&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;These styles give us a baseline for our transition on hover. They tell the browser that we're consciously placing our image exactly where it normally goes in the flow of the document. Note also the height and width. Those will change when we hover over the &lt;code&gt;grid-tile&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="nc"&gt;.grid-tile&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="nc"&gt;.tile-image&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;-5px&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;-5px&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="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;10px&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="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;10px&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;With this, when a visitor hovers over the &lt;code&gt;grid-tile&lt;/code&gt;, the image will grow 10px in width and height (the &lt;code&gt;calc&lt;/code&gt; statement makes sure of that!), at the same time, we're shifting the image 5 pixels up and to the left. This makes sure that the effect we're going for is centered in our tile. Without the position change, it would look like the image is just growing from the upper left.&lt;/p&gt;

&lt;p&gt;You might ask, "But if the image is growing, why isn't the tile growing too, and changing the layout!?" This is where the &lt;code&gt;overflow: hidden;&lt;/code&gt; rule from the last step comes in. Since the overflow is set to hidden, any change in the image size will be clipped!&lt;/p&gt;

&lt;p&gt;This leads us back to where we started out:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/zen-keldysh-096lt?initialpath=/step_five/"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If you liked this, or have questions, let me know!&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a basic photo album in React</title>
      <dc:creator>Joe Dietrich</dc:creator>
      <pubDate>Wed, 20 Oct 2021 01:54:09 +0000</pubDate>
      <link>https://dev.to/joedietrichdev/building-a-basic-photo-album-in-react-17m4</link>
      <guid>https://dev.to/joedietrichdev/building-a-basic-photo-album-in-react-17m4</guid>
      <description>&lt;h2&gt;
  
  
  Purpose
&lt;/h2&gt;

&lt;p&gt;I've loved taking pictures since I was little, but I don't really have a place to show off the best pictures I've taken. I decided to build myself a little photo gallery using React for the frontend, and a very simple JSON Server based backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Features
&lt;/h2&gt;

&lt;p&gt;For my photo gallery, I broke the required features down to the basics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display images in a gallery format&lt;/li&gt;
&lt;li&gt;Display an individual image&lt;/li&gt;
&lt;li&gt;Add an image to the gallery&lt;/li&gt;
&lt;li&gt;Edit the image's description&lt;/li&gt;
&lt;li&gt;Delete the image if needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To those, I added a couple additional features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mark images a "favorites"&lt;/li&gt;
&lt;li&gt;Display favorite images in a second gallery&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Used
&lt;/h2&gt;

&lt;p&gt;To get to my basic feature set, I decided to use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/facebook/create-react-app"&gt;Create React App&lt;/a&gt; to scaffold out the basic React boilerplate&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/remix-run/react-router"&gt;React Router&lt;/a&gt; for routing within the app&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://styled-components.com/"&gt;Styled Components&lt;/a&gt; to style the application&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/json-server"&gt;JSON Server&lt;/a&gt; as a backend for this minimum version of my app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building the App
&lt;/h2&gt;

&lt;p&gt;The photo gallery has four major components, which each have their own Routes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Gallery&lt;/em&gt;, which displays image thumbnails in a grid and links to individual &lt;em&gt;ImageDetails&lt;/em&gt;: "/images" or "/favorites"&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;ImageDetails&lt;/em&gt;, which displays a larger version of a selected image, along with the image description: nested under the gallery paths - "/images/" or "/favorites/"&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;ImageDetailsEdit&lt;/em&gt;, which provides a way to edit the description of an image: nested under the image detail paths - "/images//edit" or "/favorites//edit"&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;NewImage&lt;/em&gt;, which contains a form and the functions needed to add new images to the gallery: "/new"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Within and surrounding these components, I created a number of additional components to style the contents of each page and provide additional functionality. The majority of the components I created this way were Styled Components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;This was the first time I'd used Styled Components in a meaningful way. In the past, I've used a single stylesheet and classes to apply styles, but I wanted to challenge myself to something new. &lt;/p&gt;

&lt;p&gt;My first thought when reading the &lt;a href="https://styled-components.com/"&gt;documentation&lt;/a&gt; was to wonder about the use of sting literals I hadn't encountered much before. If you haven't seen a styled component component before, it looks something like this:&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="nx"&gt;styled&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;styled-components&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;DetailCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="s2"&gt;`
  display: grid;
  grid: "p i n" auto ". d ." auto / min-content 1fr min-content;
  max-width: 800px;
  margin: 0 auto;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking this down:&lt;br&gt;&lt;br&gt;
&lt;code&gt;styled&lt;/code&gt; is the default export from the &lt;code&gt;styled-components&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;div&lt;/code&gt; is a function property of the &lt;code&gt;styled&lt;/code&gt; object.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A function? But where are the parentheses?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notice that the CSS styles themselves are contained within the backticks (&lt;code&gt;\&lt;/code&gt;) of an ES6 &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals"&gt;template literal&lt;/a&gt;. In this case, it is a &lt;em&gt;tagged&lt;/em&gt; template literal. The tag of a tagged template literal is a function (here, &lt;code&gt;div&lt;/code&gt;) that takes an array of strings as its first argument, and the values of any substitutions (indicated by the &lt;code&gt;${}&lt;/code&gt; syntax in a template literal) as additional arguments.&lt;/p&gt;

&lt;p&gt;This means the example above could be rewritten as the below, with no change in functionality:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DetailCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="s2"&gt;`
  display: grid;
  grid: "p i n" auto ". d ." auto / min-content 1fr min-content;
  max-width: 800px;
  margin: 0 auto;
`&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;Things get a bit more complicated when you introduce those substitutions. For example, with styled components, you can use props to change a component's styles:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NavButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="s2"&gt;`
  grid-area: &lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;
  padding: 0 1rem;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to pass in an area prop to define where the component will display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NavButton&lt;/span&gt; &lt;span class="na"&gt;area&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"n"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Next&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;NavButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And translates to CSS like this:&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;grid-area&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;n&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nt"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="nt"&gt;rem&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would be equivalent to, but much less convenient than, calling the &lt;code&gt;button&lt;/code&gt; function property of &lt;code&gt;styled&lt;/code&gt; directly:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NavButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grid-area: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;; padding: 0 1rem;&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="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I was able to wrap my head around what this syntax meant, it was much easier to use styled components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Styled Component Tips:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Don't be afraid of making a lot of styled components, as long as they each serve a purpose!&lt;/li&gt;
&lt;li&gt;Keep single-use components near where you use them. I kept mine in the same file as the component I used them in.&lt;/li&gt;
&lt;li&gt;If you find yourself using the same (or similar) styles or components in multiple places, you can pull the styled component into its own file and reuse it! Don't repeat yourself if you don't have to!&lt;/li&gt;
&lt;li&gt;Read the documentation&lt;/li&gt;
&lt;li&gt;Read this (Josh W Comeau Article on Styled Components in React)[&lt;a href="https://www.joshwcomeau.com/css/styled-components/"&gt;https://www.joshwcomeau.com/css/styled-components/&lt;/a&gt;]. I found it very helpful, especially the section on making sure you have a single source of styles for each component.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Plans for the future
&lt;/h2&gt;

&lt;p&gt;As I have time, I'm planning to implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic login and user validation&lt;/li&gt;
&lt;li&gt;Real photo upload instead of url submissions&lt;/li&gt;
&lt;li&gt;Additional albums&lt;/li&gt;
&lt;li&gt;Server-side photo resizing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Visit the most recent version of the site
&lt;/h2&gt;

&lt;p&gt;You can see a working version of the site at: &lt;a href="https://photo-album.joedietrich.dev"&gt;https://photo-album.joedietrich.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building Dinerd using js, HTML, and CSS</title>
      <dc:creator>Joe Dietrich</dc:creator>
      <pubDate>Sun, 22 Aug 2021 20:10:33 +0000</pubDate>
      <link>https://dev.to/joedietrichdev/building-dinerd-using-js-html-and-css-232l</link>
      <guid>https://dev.to/joedietrichdev/building-dinerd-using-js-html-and-css-232l</guid>
      <description>&lt;h2&gt;
  
  
  Dinerd
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;You can see &lt;a href="https://joedietrich-dev.github.io/dinerd/"&gt;&lt;strong&gt;&lt;em&gt;Dinerd&lt;/em&gt;&lt;/strong&gt; in action&lt;/a&gt; or watch me &lt;a href="https://www.youtube.com/watch?v=Ov_H7td_QYk"&gt;walk through the app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Turn to &lt;em&gt;Dinerd&lt;/em&gt; to help you answer the age-old question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Where do you want to eat tonight?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Purpose
&lt;/h2&gt;

&lt;p&gt;I developed &lt;em&gt;Dinerd&lt;/em&gt; to help me break out of one of the routines I found myself falling into during the past year - always going to or ordering from the same restaurants over and over again.&lt;/p&gt;

&lt;p&gt;Comfort food is great! But every so often it's good to branch out and try new things - and this is where &lt;em&gt;Dinerd&lt;/em&gt; comes in. &lt;em&gt;Dinerd&lt;/em&gt; leverages the &lt;a href="https://www.yelp.com/developers/documentation/v3/get_started"&gt;Yelp Fusion API&lt;/a&gt; to serve the prospective diner random restaurants near them, and lets them skip ones they've already been to!&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Features
&lt;/h2&gt;

&lt;p&gt;When a diner first lands on &lt;em&gt;Dinerd&lt;/em&gt;, they will see a form that asks them for a location, the distance from that location they'd like results from, and a price-level preference. After they have submitted their selections, &lt;em&gt;Dinerd&lt;/em&gt; presents the diner with a randomized list of up to 20 restaurants, pulling details from Yelp Fusion.&lt;/p&gt;

&lt;p&gt;If a diner has already visited a particular restaurant, they can mark it as visited and it will no longer show up in their search results. They can see the restaurants they have already visited in a pop-out sidebar menu and remove them from the visited list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Strategy and Process
&lt;/h2&gt;

&lt;p&gt;Before I built &lt;em&gt;Dinerd&lt;/em&gt;, I researched restaurant-locator APIs. Yelp was the best I found by far, with a generous daily API limit and high-quality data. After doing research on the data I could expect to fetch from the Yelp Fusion API, I signed up for an API key, and then started creating simple wireframes using Figma - one for the landing form, one for the visited restaurant sidebar, and one for the restaurant card. &lt;/p&gt;

&lt;p&gt;Then I started to code.&lt;/p&gt;

&lt;p&gt;I started by trying to play with the API. I quickly realized that building a purely front-end application with the Yelp Fusion API &lt;a href="https://github.com/Yelp/yelp-fusion/issues/386"&gt;wouldn't work&lt;/a&gt; (and would also expose my API key to the world, which made me uncomfortable).&lt;/p&gt;

&lt;h3&gt;
  
  
  Back-end Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/joedietrich-dev/dinerd-backend"&gt;View the full back-end source&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup
&lt;/h4&gt;

&lt;p&gt;I had previously researched creating a server using Node.js, so my mind immediately went in that direction to solve my problems. I would build a very small Node.js server to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pass my front-end queries on to the Yelp Fusion API&lt;/li&gt;
&lt;li&gt;Return the results of the query back to the front-end application&lt;/li&gt;
&lt;li&gt;Allow me keep my API key secret&lt;/li&gt;
&lt;li&gt;Provide the opportunity for future expansion (logins, database integrations, result processing and caching)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While it would have been possible to meet my requirements using vanilla Node.js, I deceided to use &lt;a href="https://expressjs.com"&gt;Express&lt;/a&gt; to create the server and &lt;a href="https://axios-http.com"&gt;Axios&lt;/a&gt; to retrieve the API data from Yelp Fusion in an asyncronous, promise-friendly way.&lt;/p&gt;

&lt;p&gt;To start, I initialized a Node.js project using &lt;code&gt;npm init&lt;/code&gt;, and followed the prompts in my console. Then I created a few files I knew I would need, aside from the &lt;code&gt;package.json&lt;/code&gt; file created by &lt;code&gt;npm init&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;index.js&lt;/code&gt; - The gateway for the application, and where I put all the code for the server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env&lt;/code&gt; - The file where I stored my environment variables (in this case, primarily the API key). It has two lines:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  YELP_KEY=&amp;lt;yelp secret key&amp;gt;
  PORT=3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.gitignore&lt;/code&gt; - The file that tells git to ignore other files and folders. This is important to ensure the &lt;code&gt;.env&lt;/code&gt; file doesn't get synced to a cloud repository like GitHub, potentially exposing the secrets it contains. Configured correctly, it will also prevent the node_modules folder from being synced as well. For these purposes, it should contain at least these two lines:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  node_modules/
  .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once those files were properly configured, I ran the command &lt;code&gt;npm i express axios dotenv&lt;/code&gt;, which installed the Express, Axios, and dotenv dependencies in my Node.js project. &lt;/p&gt;

&lt;h4&gt;
  
  
  index.js
&lt;/h4&gt;

&lt;p&gt;At the top of the &lt;code&gt;index.js&lt;/code&gt; file, I put the &lt;code&gt;require&lt;/code&gt; statements, which make the dependences I previously installed available in the code. I also defined the port the application listens on and initialized the Express server:&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="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&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;port&lt;/span&gt; &lt;span class="o"&gt;=&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;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;80&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next few lines set up the route we'll use to query the Yelp Fusion API:&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/restaurants&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://api.yelp.com/v3/businesses/search?term=food&amp;amp;limit=50&amp;amp;location=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;radius=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;price=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&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;YELP_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yelpResponse&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yelpResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No match for requested URL found.&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;&lt;code&gt;app&lt;/code&gt; is the server object. &lt;code&gt;.get&lt;/code&gt; is a method that takes a route and a callback. When someone tries to access the route provided using the &lt;code&gt;GET&lt;/code&gt; http method, Express will call the callback method provided as the second parameter to &lt;code&gt;.get&lt;/code&gt;, passing in information about the request as the first parameter, and information about the response to the request as the second parameter.&lt;/p&gt;

&lt;p&gt;For &lt;em&gt;Dinerd&lt;/em&gt;, I expect my client-side application to make a request that contains three parameters - the three fields on the initial form:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;location&lt;/li&gt;
&lt;li&gt;price options&lt;/li&gt;
&lt;li&gt;distance from location chosen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the &lt;code&gt;req&lt;/code&gt; (request) contains the query parameters &lt;code&gt;location&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, and &lt;code&gt;distance&lt;/code&gt;, then I use Axios to send the request through to the Yelp Fusion API. For my purposes, I passed in an object containing the http method to use with Axios (&lt;code&gt;get&lt;/code&gt;), the url to send the request to (the Yelp Fusion API &lt;code&gt;search&lt;/code&gt; endpoint, with my query parameters interpolated), and the required &lt;code&gt;Authorization&lt;/code&gt; header. The header contains a reference to the API key stored in my &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;If Yelp Fusion responds to my request with valid data, I pass it back to the requester in the &lt;code&gt;res&lt;/code&gt; object, using the response's &lt;code&gt;send&lt;/code&gt; method. If there were no results for the search parameters passed in, I respond to the client with a &lt;code&gt;400&lt;/code&gt; error indicating a bad request, and the error message from Yelp.&lt;/p&gt;

&lt;p&gt;If the &lt;code&gt;req&lt;/code&gt; is not well-formed - that is, if it does not contain a location, price, and distance - then I respond to the client with a &lt;code&gt;404&lt;/code&gt; error, since the url is not valid and doesn't match the required pattern.&lt;/p&gt;

&lt;p&gt;All of the above sets up the Express server, but it's no good if it doesn't start listening for requests:&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Listening on port &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code tells the server to listen on the port provided. And with that, the &lt;em&gt;Dinerd&lt;/em&gt; back end is ready - or almost.&lt;/p&gt;

&lt;h4&gt;
  
  
  CORS
&lt;/h4&gt;

&lt;p&gt;If you run the command &lt;code&gt;node index.js&lt;/code&gt; now, the server will start up and start listening for connections. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt;: Try to issue a fetch request from the browser:&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="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:3000/restaurants?price=1,2,3,4&amp;amp;location=10001&amp;amp;distanc=2000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you'll see an error like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Access to fetch at 'http://localhost:3000/restaurants?price=1,2,3,4&amp;amp;location=10001&amp;amp;distance=2000' from origin 'http://localhost:5500' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"&gt;CORS&lt;/a&gt;, or Cross-Origin Resource Sharing, error. For security reasons, most browsers will prevent HTTP requests made from within a script or a browser's console from being successfully completed if the requested resource is on a different origin, or domain. For example, a site at &lt;code&gt;https://example-a.com/&lt;/code&gt; can make a successful request to &lt;code&gt;https://example-a.com/api&lt;/code&gt;, but not necessarily to &lt;code&gt;https://example-b.com/api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One way around this is to specify which origins a specific resource accepts requests from. In &lt;em&gt;Dinerd&lt;/em&gt;, I did this using an &lt;a href="https://expressjs.com/en/guide/using-middleware.html#using-middleware"&gt;Express middleware&lt;/a&gt; function to set the headers on every response from my server. I placed the below in &lt;code&gt;index.js&lt;/code&gt; above the &lt;code&gt;app.get&lt;/code&gt; line.&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;next&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;Express middleware has access to the request and response objects. With the above code, I intercept the responses the server sends out and add a line to the header. As written, this will signal to the requester that any origin (&lt;code&gt;*&lt;/code&gt;) is allowed to access the resources on my server.&lt;/p&gt;

&lt;p&gt;With the above in place, the backend is ready to go!&lt;/p&gt;

&lt;h3&gt;
  
  
  Front-end Code
&lt;/h3&gt;

&lt;p&gt;(View the full front-end source)[&lt;a href="https://github.com/joedietrich-dev/dinerd"&gt;https://github.com/joedietrich-dev/dinerd&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dinerd&lt;/em&gt;'s front end is written in vanilla javascript, HTML, and CSS. The form you see when you land on the home view is fully in the static HTML, with event listeners added when the javascript loads.&lt;/p&gt;

&lt;p&gt;I use &lt;code&gt;fetch&lt;/code&gt; to make calls to the back-end server created above, and render the restaurant cards using a &lt;code&gt;renderRestaurant&lt;/code&gt; function that I created to translate the JSON data into visible and interactive components. The map on each card is created using the &lt;a href="https://leafletjs.com"&gt;Leaflet&lt;/a&gt; library and &lt;a href="https://www.openstreetmap.org/"&gt;Open Streetmap&lt;/a&gt; data, combined with each restaurant's location data returned from the API.&lt;/p&gt;

&lt;p&gt;For this version of the app, I use the browser's local storage to persist a diner's previously visited restaurants. This means their choices will only be visible when they're using the same browser on the same device, and will be removed if they clear their local caches, but it does remove the need for a back-end database.&lt;/p&gt;

&lt;p&gt;All animations including the sidebar slidein, error state appearance and disappearance, and card transitions are executed using CSS transistions. &lt;/p&gt;

&lt;h2&gt;
  
  
  Future Plans
&lt;/h2&gt;

&lt;p&gt;In future iterations of this app, I would like to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login and restaurant selection persistence using a back-end database instead of local storage.&lt;/li&gt;
&lt;li&gt;More filtering options when selecting a restaurant, including the ability to select only restaurants that are open when the search is performed.&lt;/li&gt;
&lt;li&gt;Autofilling the location from the device's gps&lt;/li&gt;
&lt;li&gt;Improved styles on very wide screens&lt;/li&gt;
&lt;li&gt;Swipe to navigate cards&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tools / Libraries / APIs Used
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Front-end
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://figma.com"&gt;Figma&lt;/a&gt; - Design and wireframing tool.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://leafletjs.com"&gt;Leaflet&lt;/a&gt; - Library for mapping location data. Uses &lt;a href="https://www.openstreetmap.org/"&gt;Open Streetmap&lt;/a&gt; data.&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://maps.stamen.com/toner"&gt;Stamen Toner&lt;/a&gt; - Map tile theme.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage"&gt;localStorage&lt;/a&gt; - The Web Storage API method for storing and retrieving data within a user's browser.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pattern.monster/"&gt;Pattern Monster&lt;/a&gt; - SVG Pattern generator, as seen in the site's background.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://realfavicongenerator.net/"&gt;Favicon Generator&lt;/a&gt; - Multi-platform favicon generator.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://iconfinder.com/"&gt;Icon Finder&lt;/a&gt; - Source of MIT licensed SVG icons.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://necolas.github.io/normalize.css/"&gt;Normalize CSS&lt;/a&gt; - Provide a better cross-browser baseline for CSS styles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Back-end
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.yelp.com/developers/documentation/v3/get_started"&gt;Yelp Fusion API&lt;/a&gt; - Source of data on restaurants by location.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org"&gt;Node.js&lt;/a&gt; - JavaScript runtime that powers the back-end of Dinerd.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://expressjs.com"&gt;Express&lt;/a&gt; - Web application framework used to create API route to pass queries to Yelp and return results to client application.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://axios-http.com"&gt;Axios&lt;/a&gt; - HTTP client for Node.js (like fetch, but for Node).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/dotenv"&gt;dotenv&lt;/a&gt; - NPM package that loads environment variables from a .env file into a location accessible by a Node.js application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gYcHX8La--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kd6jmfz0y7e1fv7p5c1i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gYcHX8La--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kd6jmfz0y7e1fv7p5c1i.png" alt="Restaurant Card"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
