<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Damilola Oniyide - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Damilola Oniyide - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 22:23:58 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/LolaVictoria/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Implement a Service Worker with WorkBox in a Progressive Web App ]]>
                </title>
                <description>
                    <![CDATA[ Imagine having a web app that looks and feels just like a native mobile app. It launches from your home screen, runs in full-screen mode, and responds smoothly to your interactions. But here’s the surprising part: it wasn’t downloaded from an app sto... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/implement-a-service-worker-with-workbox-in-a-pwa/</link>
                <guid isPermaLink="false">68595d64f486735784954566</guid>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML5 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Service Workers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ workbox ]]>
                    </category>
                
                    <category>
                        <![CDATA[ progressive web apps ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ manifest ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Damilola Oniyide ]]>
                </dc:creator>
                <pubDate>Mon, 23 Jun 2025 13:57:56 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750687028879/b12e57cb-290a-4562-8584-95eb5713a871.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Imagine having a web app that looks and feels just like a native mobile app. It launches from your home screen, runs in full-screen mode, and responds smoothly to your interactions. But here’s the surprising part: it wasn’t downloaded from an app store. It’s a Progressive Web App (PWA).</p>
<p>PWAs bring the power of the web to your fingertips with the experience of a mobile app. Even better? If you lose internet connection while on the go, the app can still function, showing your previously loaded data and getting updates once you’re back online.</p>
<p>In this tutorial, you’ll learn how to implement a service worker with WorkBox in a weather app using HTML, CSS, and JavaScript. We’ll start by understanding what a PWA is, the core components behind the scenes, especially service workers, and how to use Workbox to supercharge your app with offline capabilities.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-well-cover">What We’ll Cover</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-progressive-web-app-pwa">What is a Progressive Web App (PWA)?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-makes-a-web-app-progressive">What Makes a Web App “Progressive”?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-components-of-a-pwa">Components of a PWA</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-service-worker-in-pwa">What is a Service Worker in PWA?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-use-workbox-instead-of-manual-service-workers">Why Use Workbox Instead of Manual Service Workers?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-introduction-to-workbox">Introduction to WorkBox</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-setup">Project Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-the-offline-html-structure">Creating the Offline HTML Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-styling-with-css">Styling with CSS</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-appjs-and-configjs">How to Set Up app.js and config.js</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-a-manifest-file">How to Create a Manifest File</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-add-workbox-to-your-service-workerjs-file">How to Add WorkBox to Your service-worker.js File</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-your-service-worker-in-the-service-workerjs-file">How to Create your Service Worker in the service-worker.js File</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-app-installation">How to Set Up App Installation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-install-the-weather-app">How to Install the Weather App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-well-cover">What We’ll Cover</h2>
<ul>
<li><p><strong>Setting Up the Project:</strong> We'll build a simple weather app using HTML, CSS, and JavaScript. This approach is perfect for this tutorial because it keeps things simple and accessible while focusing on core PWA concepts without the added complexity of frameworks like React or Vue.</p>
</li>
<li><p><strong>Turning the App into a PWA:</strong> Next, we’ll walk through the concept of a Progressive Web App, covering the key features and best practices of PWAs.</p>
</li>
<li><p><strong>Implementing Service Worker via WorkBox:</strong> Finally, we’ll dive deeper into how service workers function and explore why using Workbox simplifies the process.</p>
</li>
</ul>
<p>Here’s what the final application will look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747272664555/8ec876bc-0881-4a63-8010-02136de91db3.png" alt="Weatherly app interface showing Tokyo weather with 24°C temperature, overcast clouds, city search functionality, and location services button" class="image--center mx-auto" width="720" height="1417" loading="lazy"></p>
<h3 id="heading-audience"><strong>Audience</strong></h3>
<p>This tutorial is for web developers of all levels. Whether you're new to Progressive Web Apps (PWAs) or just starting to explore service workers, this guide will walk you through the core concepts and demonstrate why using a Google-backed library like Workbox to implement service workers can be more efficient than manual implementation.</p>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<p>Before you begin</p>
<ol>
<li><p>Get a free API key from the <a target="_blank" href="https://openweathermap.org/">OpenWeatherAPI</a> website</p>
</li>
<li><p>Make sure you’re familiar with HTML, CSS, and JavaScript.</p>
</li>
<li><p>If you’re new to PWAs, you might want to read some introductory articles to get a quick overview.</p>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps">Progressive web apps</a></p>
</li>
<li><p><a target="_blank" href="https://web.dev/articles/workbox">Workbox</a></p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-what-is-a-progressive-web-app-pwa">What is a Progressive Web App (PWA)?</h2>
<p>A PWA is a web application that combines the best of web and mobile apps. It’s built using standard web technologies like HTML, CSS, and JavaScript, but it behaves and feels like a native mobile app on your phone or tablet.</p>
<p>Think of apps like Instagram Web, Twitter Lite, or Spotify Web Player. Even though you’re not using a native app from an app store:</p>
<ul>
<li><p>You can still scroll your feed, view media, and send messages.</p>
</li>
<li><p>It works even on slow or unstable networks.</p>
</li>
<li><p>You can “install” it on your home screen and launch it like a regular app.</p>
</li>
<li><p>You even get push notifications just like a mobile app!</p>
</li>
</ul>
<p>With PWAs, you get the reach of the web and the feel of an app without the heavy storage or installation process.</p>
<h2 id="heading-what-makes-a-web-app-progressive">What Makes a Web App “Progressive”?</h2>
<p>A PWA is not just any website. It’s built to progressively enhance the user experience, depending on their device and browser capabilities. Here are the core characteristics that define a PWA:</p>
<ul>
<li><p><strong>Responsive</strong>: Works on all screen sizes, that is, phones, tablets, and desktops.</p>
</li>
<li><p><strong>Reliable</strong>: Loads instantly, even when offline or on poor networks.</p>
</li>
<li><p><strong>Installable</strong>: Can be added to the home screen without needing an app store.</p>
</li>
<li><p><strong>Engaging</strong>: Supports features like push notifications and background sync.</p>
</li>
</ul>
<h2 id="heading-components-of-a-pwa"><strong>Compone</strong>nts of a PWA</h2>
<p>Before your web app can be considered a PWA, it must include the following:</p>
<h3 id="heading-a-web-application-manifest">A Web Application Manifest</h3>
<p>The web app manifest is a JSON file that tells the browser about your web app, how it should appear, and behave when installed on a user's device.</p>
<p>Think of it like your app’s business card. It includes details like:</p>
<ul>
<li><p><strong>App name and short name</strong> – How your app is labeled on the home screen or app list.</p>
</li>
<li><p><strong>Icons</strong> – Images used for app icons on different screen sizes and resolutions.</p>
</li>
<li><p><strong>Theme color and background color</strong> – Defines the look of your app’s UI and loading screen.</p>
</li>
<li><p><strong>Start URL</strong> – The page that opens when the app is launched.</p>
</li>
<li><p><strong>Display mode</strong> – Controls whether the app opens in a browser tab, fullscreen, or a native-like window.</p>
</li>
<li><p><strong>Screenshots</strong> – Optional preview images that show how your app looks on different devices in app stores or installation prompts.</p>
</li>
</ul>
<h3 id="heading-a-service-worker">A Service Worker</h3>
<p>This is a script that runs in the background. It handles offline behaviour, caching, background sync, and push notifications needed to make your PWA function.</p>
<p>More details about the service worker will be discussed later in this article.</p>
<h3 id="heading-https">HTTPS</h3>
<p>PWAs must be served over HTTPS. This is not optional. Here’s why:</p>
<ul>
<li><p>It protects users by ensuring secure data transfer.</p>
</li>
<li><p>It enables important features like service workers and push notifications.</p>
</li>
<li><p>Browsers won’t allow service workers to register on non-secure origins.</p>
</li>
</ul>
<p>If you're testing locally, you can use <a target="_blank" href="http://localhost"><code>localhost</code></a> (which is treated as secure), But for production, your site must have an SSL certificate.</p>
<h2 id="heading-what-is-a-service-worker-in-pwa">What is a Service Worker in PWA?</h2>
<p>In PWAs, a service worker is a JavaScript file that runs in the background, separate from your main app, and acts like a network proxy. It can:</p>
<ul>
<li><p>Cache resources and serve them offline</p>
</li>
<li><p>Intercept network requests and apply caching strategies</p>
</li>
<li><p>Handle background syncs</p>
</li>
<li><p>Manage push notifications</p>
</li>
</ul>
<p>Think of it as your app’s behind-the-scenes assistant—makes it load fast, works offline, and stays updated, even when you're not looking.</p>
<h2 id="heading-why-use-workbox-instead-of-manual-service-workers">Why Use Workbox Instead of Manual Service Workers?</h2>
<p>Service workers are essential in creating a PWA, but getting started with them can be challenging. Writing service worker code from scratch can often be tedious and prone to errors. For example, you'd need to:</p>
<ul>
<li><p>Manually configure caching strategies</p>
</li>
<li><p>Handle service worker updates</p>
</li>
<li><p>Write and maintain a lot of repetitive boilerplate code</p>
</li>
</ul>
<p>Workbox, a library from Google, makes things easier by letting developers focus on what matters, without worrying about the complicated parts of service workers.</p>
<p>However, it’s still important to understand how service workers function, since they handle some complex tasks under the hood.</p>
<p>Here are key things a service worker (with or without Workbox) does:</p>
<ul>
<li><p><strong>Install event</strong>: Set up cache</p>
</li>
<li><p><strong>Activate event</strong>: Clean up old caches</p>
</li>
<li><p><strong>Fetch event</strong>: Intercept network requests and serve from cache</p>
</li>
</ul>
<p>With Workbox, these are wrapped in easy-to-use functions.</p>
<h2 id="heading-introduction-to-workbox">Introduction to WorkBox</h2>
<p>Workbox is a collection of libraries that helps developers build efficient service workers quickly, with best practices built right in. It supports strategies like:</p>
<ul>
<li><p><code>CacheFirst</code>: Load from cache, fall back to network</p>
</li>
<li><p><code>NetworkFirst</code> : Try network, fall back to cache</p>
</li>
<li><p><code>StaleWhileRevalidate</code>: Serve from cache and update in the background</p>
</li>
</ul>
<h3 id="heading-understanding-workbox-modules">Understanding Workbox Modules</h3>
<p>Workbox is more than just a tool. It is a collection of powerful modules, each designed to simplify different parts of working with service workers. These modules are flexible and can be used in three key contexts:</p>
<ul>
<li><p><strong>Service Worker Context</strong> – Inside your service worker file, where you handle caching, routing, and other background tasks.</p>
</li>
<li><p><strong>Window Context</strong> – Inside your main application (the client-side JS), where you register and communicate with the service worker.</p>
</li>
<li><p><strong>Build Tools Integration</strong> – Tools like Webpack use Workbox to generate service worker files and precache manifests during your build process.</p>
</li>
</ul>
<p>Let’s break down some of the most popular and essential modules Workbox offers:</p>
<ol>
<li><strong>workbox-routing</strong></li>
</ol>
<p>This module handles routing network requests within your service worker. Think of it like a traffic director that listens for <code>fetch</code> events and decides what to do with them.</p>
<p><strong>Use case:</strong> Route API requests to the network while routing static asset requests to the cache.</p>
<ol start="2">
<li><strong>workbox-strategies</strong></li>
</ol>
<p>This is where caching strategies like <code>CacheFirst</code>, <code>NetworkFirst</code>, and <code>StaleWhileRevalidate</code> are used. It provides a clean and consistent API for handling how your app responds to different requests.</p>
<p><strong>Use case:</strong> Apply different caching behaviours for images, fonts, or dynamic data with minimal code.</p>
<ol start="3">
<li><strong>workbox-precaching</strong></li>
</ol>
<p>This module handles precaching by storing static assets during the service worker’s install phase. It makes it easy to cache files ahead of time and ensures that updates are managed efficiently.</p>
<p><strong>Use case:</strong> Preload essential assets (like HTML, CSS, and logo images) so your app loads instantly, even offline.</p>
<ol start="4">
<li><strong>workbox-expiration</strong></li>
</ol>
<p>It is used as a plugin alongside caching strategies. This module adds smart cache expiration. You can automatically remove old or excessive items from the cache based on how long they've been stored or how many items exist.</p>
<p><strong>Use case:</strong> Keep your cache size under control without manually tracking and deleting outdated files.</p>
<p><strong>workbox-window</strong></p>
<p>This module is designed for the browser (window) side of your app. It simplifies service worker registration and allows you to communicate with the service worker from your page easily.</p>
<p><strong>Use case:</strong> Detect when a new service worker is available and prompt the user to refresh the app to update.</p>
<p>You can use WorkBox via:</p>
<ul>
<li><p>npm</p>
</li>
<li><p>CDN (which we'll use here for simplicity)</p>
</li>
</ul>
<h2 id="heading-project-setup">Project Setup</h2>
<p>Let's start by creating our project structure:</p>
<pre><code class="lang-plaintext">weather-pwa/
├── index.html
├── style.css
├── js/
│   ├── app.js
│   └── install.js
├── service-worker.js
├── images/
│   └── [your image files and folders here]
├── manifest.json
├── config.js  
└── offline.html
</code></pre>
<h3 id="heading-the-html-structure">The HTML Structure</h3>
<p>First, let's build our <code>index.html</code> file:</p>
<pre><code class="lang-xml">
<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
    <span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/images/logo.png"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/png"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"Simple Weather Progressive Web App"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/styles.css"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Weatherly<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>


<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"header"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"logo"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"images/logo.png"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Weatherly Logo"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Weatherly<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"main"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"weather-card"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"location-container"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"location-input"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Enter city name"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"search-btn"</span>&gt;</span>Search<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"locationBtn"</span>&gt;</span>📍 Use My Location<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"installBtn"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"display: none;"</span>&gt;</span>Install App<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"offline-message"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"offline-message"</span>&gt;</span>
                You are currently offline. Weather data may not be up-to-date.
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>


            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"error"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"error-message"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"weather-container"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"weather-container"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Your last searched location weather:<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"location-info"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"city"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"date"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"current-weather"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"weather-icon"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">""</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Weather icon"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"temperature-container"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"temperature"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"weather-description"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"weather-details"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"detail"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"humidity-icon"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/images/humidity.png"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Humidity icon"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"label"</span>&gt;</span>Humidity<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"humidity"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"value"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"detail"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"wind-icon"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/images/wind.png"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Wind icon"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"label"</span>&gt;</span>Wind<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"wind"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"value"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-comment">&lt;!-- Your location weather --&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"location-weather"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Your location's weather:<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"weather-info"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"weatherInfo"</span>&gt;</span>

                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">footer</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Made with ❤️ by <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"www.linkedin.com/in/damilola-oniyide"</span>&gt;</span>Damilola Oniyide<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/js/app.js"</span> <span class="hljs-attr">defer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="heading-creating-the-offline-html-structure">Creating the Offline HTML Structure</h2>
<p>The <code>offline.html</code> is the page that users will see when they lose network connection and try to navigate to a page that isn’t cached.</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"theme-color"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"#2196f3"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Weatherly - Offline<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/styles.css"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
    <span class="hljs-selector-class">.offline-icon</span> {
      <span class="hljs-attribute">font-size</span>: <span class="hljs-number">5rem</span>;
      <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1.5rem</span>;
      <span class="hljs-attribute">color</span>: <span class="hljs-number">#2196f3</span>;
    }

    <span class="hljs-selector-class">.offline-message</span> {
      <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5rem</span>;
      <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1.5rem</span>;
    }

    <span class="hljs-selector-class">.offline-subtext</span> {
      <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
      <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">2rem</span>;
      <span class="hljs-attribute">color</span>: <span class="hljs-number">#666</span>;
    }

    <span class="hljs-selector-class">.retry-button</span> {
      <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.75rem</span> <span class="hljs-number">1.5rem</span>;
      <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2196f3</span>;
      <span class="hljs-attribute">color</span>: white;
      <span class="hljs-attribute">border</span>: none;
      <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">12px</span>;
      <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
      <span class="hljs-attribute">cursor</span>: pointer;
      <span class="hljs-attribute">transition</span>: background-color <span class="hljs-number">0.3s</span>;
    }

    <span class="hljs-selector-class">.retry-button</span><span class="hljs-selector-pseudo">:hover</span> {
      <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2980b9</span>;
    }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Weatherly<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"app-container"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"weather-card"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"offline-container"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"offline-icon"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"1em"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"1em"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"currentColor"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 16 16"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"</span>/&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zm-2.715 5.933a.5.5 0 0 1-.183-.683A4.498 4.498 0 0 1 8 9.5a4.5 4.5 0 0 1 3.898 2.25.5.5 0 0 1-.866.5A3.498 3.498 0 0 0 8 10.5a3.498 3.498 0 0 0-3.032 1.75.5.5 0 0 1-.683.183zM10 8c-.552 0-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5S10.552 8 10 8z"</span>/&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"offline-message"</span>&gt;</span>You're offline<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"offline-subtext"</span>&gt;</span>Please check your internet connection and try again.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"retry-button"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"window.location.href='/'"</span>&gt;</span>Retry<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">footer</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Made with ❤️ by Damilola Oniyide<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="heading-styling-with-css">Styling with CSS</h2>
<p>Now, let's create our <code>style.css</code> file for a responsive and user-friendly design:</p>
<pre><code class="lang-css">* {
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">box-sizing</span>: border-box;
}

<span class="hljs-selector-tag">body</span> {
    <span class="hljs-attribute">font-family</span>: <span class="hljs-string">'Segoe UI'</span>, Tahoma, Geneva, Verdana, sans-serif;
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f5f5f5</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#333</span>;
    <span class="hljs-attribute">line-height</span>: <span class="hljs-number">1.6</span>;
}

<span class="hljs-selector-class">.header</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2196f3</span>;
    <span class="hljs-attribute">color</span>: white;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">align-items</span>: center;
    <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">2px</span> <span class="hljs-number">5px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>);
}

<span class="hljs-selector-class">.header</span> <span class="hljs-selector-tag">h1</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5rem</span>;
}


<span class="hljs-selector-class">.header</span> <span class="hljs-selector-tag">img</span> {
    <span class="hljs-attribute">width</span>: <span class="hljs-number">55px</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">55px</span>;
    <span class="hljs-attribute">border</span>: <span class="hljs-number">#ffff</span> <span class="hljs-number">1px</span> solid;
    <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">4px</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">10%</span>;
}


<span class="hljs-selector-class">.main</span> {
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">max-width</span>: auto;
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;
}

<span class="hljs-selector-class">.weather-card</span> {
    <span class="hljs-attribute">background-color</span>: white;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">8px</span>;
    <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">2px</span> <span class="hljs-number">10px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>);
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1.5rem</span> <span class="hljs-number">3rem</span>;
    <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-comment">/* Location input styles */</span>
<span class="hljs-selector-class">.location-container</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1.5rem</span>;
    <span class="hljs-attribute">justify-content</span>: center;
}

<span class="hljs-selector-id">#location-input</span> {
    <span class="hljs-attribute">flex</span>: <span class="hljs-number">1</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.75rem</span>;
    <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ddd</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">4px</span>;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">max-width</span>: <span class="hljs-number">240px</span>;
}

<span class="hljs-selector-id">#location-input</span><span class="hljs-selector-pseudo">:focus</span> {
    <span class="hljs-attribute">outline</span>: none;
    <span class="hljs-attribute">border-color</span>: <span class="hljs-number">#2196f3</span>;
}
<span class="hljs-selector-id">#location-input</span><span class="hljs-selector-pseudo">::placeholder</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#999</span>;
}   

<span class="hljs-selector-id">#search-btn</span>, <span class="hljs-selector-id">#locationBtn</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2196f3</span>;
    <span class="hljs-attribute">color</span>: white;
    <span class="hljs-attribute">border</span>: none;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.75rem</span> <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">0</span> <span class="hljs-number">4px</span> <span class="hljs-number">4px</span> <span class="hljs-number">0</span>;
    <span class="hljs-attribute">cursor</span>: pointer;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">2.5px</span>;
}


<span class="hljs-selector-id">#installBtn</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2196f3</span>;
    <span class="hljs-attribute">color</span>: white;
    <span class="hljs-attribute">border</span>: none;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.75rem</span> <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span>;
    <span class="hljs-attribute">cursor</span>: pointer;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;

}

<span class="hljs-selector-id">#search-btn</span><span class="hljs-selector-pseudo">:focus</span>, <span class="hljs-selector-id">#locationBtn</span><span class="hljs-selector-pseudo">:focus</span>, <span class="hljs-selector-id">#installBtn</span><span class="hljs-selector-pseudo">:focus</span> {
    <span class="hljs-attribute">outline</span>: none;
    <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">5px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">33</span>, <span class="hljs-number">150</span>, <span class="hljs-number">243</span>, <span class="hljs-number">0.5</span>);
}
<span class="hljs-selector-id">#search-btn</span><span class="hljs-selector-pseudo">:hover</span>, <span class="hljs-selector-id">#locationBtn</span><span class="hljs-selector-pseudo">:hover</span>, <span class="hljs-selector-id">#installBtn</span><span class="hljs-selector-pseudo">:hover</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#1976d2</span>;
}

<span class="hljs-selector-class">.error</span>, <span class="hljs-selector-class">.loading</span> {
    <span class="hljs-attribute">text-align</span>: center;
    <span class="hljs-attribute">font-weight</span>: bold;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">14px</span>;
    <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">10px</span>;
    <span class="hljs-attribute">display</span>: none;;
}

<span class="hljs-selector-class">.error-message</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#d32f2f</span>;

}
<span class="hljs-comment">/* Weather display styles */</span>
<span class="hljs-selector-class">.weather-container</span> {
    <span class="hljs-attribute">display</span>: none 
}

<span class="hljs-selector-id">#weather-icon</span> {
    <span class="hljs-attribute">width</span>: <span class="hljs-number">1000px</span>; 
    <span class="hljs-attribute">height</span>: <span class="hljs-number">100px</span>;
  }

<span class="hljs-selector-class">.current-weather</span>{
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">justify-content</span>: center;
}

<span class="hljs-selector-class">.location-weather</span>{
    <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">flex-direction</span>: column;
}


<span class="hljs-selector-id">#weather-icon</span> {
    <span class="hljs-attribute">width</span>: <span class="hljs-number">80px</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">80px</span>;
    <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-selector-class">.location-info</span> {
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">flex-direction</span>: column;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">align-items</span>: center;
}

<span class="hljs-selector-class">.location-info</span> <span class="hljs-selector-tag">h2</span>,  <span class="hljs-selector-class">.current-weather</span> <span class="hljs-selector-tag">h3</span>, <span class="hljs-selector-class">.weather-container</span> <span class="hljs-selector-tag">h3</span>, <span class="hljs-selector-class">.location-weather</span> <span class="hljs-selector-tag">h3</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.8rem</span>;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">0.25rem</span>;
}



<span class="hljs-selector-class">.location-info</span> <span class="hljs-selector-tag">p</span>, <span class="hljs-selector-class">.current-weather</span> <span class="hljs-selector-tag">p</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#666</span>;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.4rem</span>;
}

<span class="hljs-selector-class">.temperature-container</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">flex-direction</span>: column;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">align-items</span>: center;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-selector-class">.temperature-container</span> <span class="hljs-selector-tag">h3</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2.5rem</span>;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">0.25rem</span>;
}

<span class="hljs-selector-class">.temperature-container</span> <span class="hljs-selector-tag">p</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#666</span>;
    <span class="hljs-attribute">text-transform</span>: capitalize;
}

<span class="hljs-selector-class">.weather-details</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f9f9f9</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-selector-id">#humidity-icon</span>, <span class="hljs-selector-id">#wind-icon</span>{
    <span class="hljs-attribute">width</span>: <span class="hljs-number">40px</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">40px</span>;
}

<span class="hljs-selector-class">.detail</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">flex-direction</span>: column;
    <span class="hljs-attribute">align-items</span>: center;
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">text-align</span>: center;
}

<span class="hljs-selector-class">.label</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">0.9rem</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#666</span>;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">0.25rem</span>;
}

<span class="hljs-selector-class">.value</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.2rem</span>;
    <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">500</span>;
}

<span class="hljs-comment">/* Error and offline message styles */</span>
<span class="hljs-selector-class">.error-message</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#d32f2f</span>;
    <span class="hljs-attribute">text-align</span>: center;
    <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">display</span>: none;
} 

<span class="hljs-selector-class">.offline-message</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#ffab91</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#7f0000</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.75rem</span>;
    <span class="hljs-attribute">text-align</span>: center;
    <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span>;
    <span class="hljs-attribute">display</span>: none;
}


<span class="hljs-comment">/* 5 days forecast weather */</span>
<span class="hljs-selector-class">.forecast-container</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">justify-content</span>: space-around;
    <span class="hljs-attribute">gap</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-selector-class">.forecast-item</span> {
    <span class="hljs-attribute">background-color</span>: white;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">8px</span>;
    <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">2px</span> <span class="hljs-number">10px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>);
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span> <span class="hljs-number">4rem</span>;
    <span class="hljs-attribute">text-align</span>: center;
}



<span class="hljs-selector-tag">footer</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2196f3</span>;
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">align-items</span>: center;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">padding</span>: .<span class="hljs-number">7rem</span> <span class="hljs-number">0</span>;
}

<span class="hljs-selector-tag">footer</span> <span class="hljs-selector-tag">p</span>, <span class="hljs-selector-tag">footer</span> <span class="hljs-selector-tag">a</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#f9f9f9</span>;
    <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">500</span>;
}
<span class="hljs-comment">/* Responsive styles */</span>
<span class="hljs-keyword">@media</span> (<span class="hljs-attribute">max-width:</span> <span class="hljs-number">480px</span>) {
    <span class="hljs-selector-class">.header</span> <span class="hljs-selector-tag">h1</span> {
        <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.2rem</span>;
    }

    <span class="hljs-selector-class">.location-container</span> {
        <span class="hljs-attribute">flex-direction</span>: column;
        <span class="hljs-attribute">align-items</span>: center;
        <span class="hljs-attribute">gap</span>: .<span class="hljs-number">6rem</span>
    }


    <span class="hljs-selector-class">.current-weather</span> {
        <span class="hljs-attribute">flex-direction</span>: column;
        <span class="hljs-attribute">justify-content</span>: center;
        <span class="hljs-attribute">align-items</span>: center;
    }

    <span class="hljs-selector-class">.weather-container</span> <span class="hljs-selector-tag">h3</span>,  <span class="hljs-selector-class">.location-weather</span> <span class="hljs-selector-tag">h3</span>, <span class="hljs-selector-class">.forecast</span> <span class="hljs-selector-tag">h3</span> {
        <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5rem</span>;
    }

    <span class="hljs-selector-id">#weather-icon</span> {
        <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">0</span>;
        <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1rem</span>;
    }

    <span class="hljs-selector-class">.forecast-container</span> {
        <span class="hljs-attribute">flex-direction</span>: column;
        <span class="hljs-attribute">align-items</span>: center;
    }
}
</code></pre>
<h2 id="heading-how-to-set-up-appjs-and-configjs">How to Set Up <code>app.js</code> and <code>config.js</code></h2>
<p>Now, let's create our <code>app.js</code> file to add functionality to the weather app. Before proceeding, ensure you’ve obtained your <strong>API key</strong> from <a target="_blank" href="https://openweathermap.org/">OpenWeather</a>. For best practice, store your API key in a separate file like <code>config.js</code> to keep things organized and avoid hardcoding sensitive data.</p>
<p>Here's what your <code>config.js</code> should look like:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> CONFIG = {
    <span class="hljs-attr">WEATHER_API_KEY</span>: <span class="hljs-string">"WRITE-YOUR-API-KEY-HERE"</span>,
};
</code></pre>
<p>Ensure you add the <code>config.js</code> file to <code>.gitignore</code> to avoid leaking sensitive information on a public platform like GitHub.</p>
<p>Now let’s move to <code>app.js</code>. This is where the main logic of your weather app will live. You can now reference your API key using <code>Weather_API_KEY</code> from the <code>config.js</code> file.</p>
<p>Below is the structure of your <code>app.js</code> file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { CONFIG } <span class="hljs-keyword">from</span> <span class="hljs-string">'./config.js'</span>;
<span class="hljs-keyword">const</span> BASE_URL = <span class="hljs-string">`https://api.openweathermap.org/data/2.5/weather?&amp;appid=<span class="hljs-subst">${CONFIG.WEATHER_API_KEY}</span>&amp;units=metric&amp;q=`</span>;

<span class="hljs-keyword">const</span> cityName = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'location-input'</span>);
<span class="hljs-keyword">const</span> searchButton = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'search-btn'</span>);
<span class="hljs-keyword">const</span> weatherIcon = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'weather-icon'</span>);
<span class="hljs-keyword">const</span> locationBtn = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'locationBtn'</span>);
<span class="hljs-keyword">const</span> weatherInfo = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'weatherInfo'</span>);


<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getWeatherIcon</span>(<span class="hljs-params">condition</span>) </span>{
  <span class="hljs-keyword">switch</span> (condition) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">"Clear"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/clear.png"</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">"Clouds"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/clouds.png"</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">"Drizzle"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/drizzle.png"</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">"Rain"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/drizzle.png"</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">"Mist"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/mist.png"</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">"Snow"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/snow.png"</span>;
    <span class="hljs-keyword">default</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/default.png"</span>;
  }
}
<span class="hljs-comment">//Search for weather by city name</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkWeatherBySearch</span>(<span class="hljs-params">city</span>)</span>{
    <span class="hljs-keyword">if</span>(city.length == <span class="hljs-number">0</span>) {
        <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].style.display = <span class="hljs-string">'block'</span>;
        <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].innerHTML = <span class="hljs-string">"Please enter a city name!"</span>;
        <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].style.color = <span class="hljs-string">'red'</span>;
        <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'weather-container'</span>).style.display = <span class="hljs-string">'none'</span>; 
        <span class="hljs-keyword">return</span>;
    }
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(BASE_URL + city);
    <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].style.display = <span class="hljs-string">'block'</span>;
    <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].innerHTML = <span class="hljs-string">"Wait a sec, your location's data will be displayed soon!"</span>;

    <span class="hljs-keyword">if</span> (response.status == <span class="hljs-number">404</span>) {
        <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].style.display = <span class="hljs-string">'block'</span>;
        <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].innerHTML = <span class="hljs-string">"City not found! Please enter a valid city name."</span>;
        <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].style.color = <span class="hljs-string">'red'</span>;
        <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'weather-container'</span>).style.display = <span class="hljs-string">'none'</span>;       
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'weather-container'</span>).style.display = <span class="hljs-string">'block'</span>;
      <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].style.display = <span class="hljs-string">'none'</span>;
      <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'lastCity'</span>, city);
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'city'</span>).innerHTML = data.name;
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'date'</span>).innerHTML = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(data.dt * <span class="hljs-number">1000</span>).toLocaleDateString();
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"temperature"</span>).innerHTML = <span class="hljs-built_in">Math</span>.round(data.main.temp) + <span class="hljs-string">"°C"</span>;
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"humidity"</span>).innerHTML = data.main.humidity + <span class="hljs-string">"%"</span>;
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"wind"</span>).innerHTML = data.wind.speed + <span class="hljs-string">"m/s"</span>;
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'weather-description'</span>).innerHTML = data.weather[<span class="hljs-number">0</span>].description;
      <span class="hljs-keyword">const</span> weatherCondition = data.weather[<span class="hljs-number">0</span>].main;
      weatherIcon.src = getWeatherIcon(weatherCondition);
    }
}

 <span class="hljs-comment">// display next 5-day forecast by coordinates</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">display5DaysForecast</span>(<span class="hljs-params">forecast</span>) </span>{
   <span class="hljs-keyword">const</span> fragment = <span class="hljs-built_in">document</span>.createDocumentFragment(); 
    <span class="hljs-keyword">const</span> forecastWrapper = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
    forecastWrapper.className = <span class="hljs-string">'forecast'</span>;

    <span class="hljs-keyword">const</span> heading = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'h3'</span>);
    heading.innerHTML = <span class="hljs-string">"Your location's next 5 days forecast:"</span>;

    <span class="hljs-keyword">const</span> container = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
    container.className = <span class="hljs-string">'forecast-container'</span>;

    <span class="hljs-keyword">const</span> addedDates = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>();
    <span class="hljs-keyword">const</span> today = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toDateString();

    forecast.forEach(<span class="hljs-function">(<span class="hljs-params">entry</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> entryDateObj = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(entry.dt * <span class="hljs-number">1000</span>);
      <span class="hljs-keyword">const</span> entryDateStr = entryDateObj.toDateString();

      <span class="hljs-keyword">if</span> (entryDateStr !== today &amp;&amp; !addedDates.has(entryDateStr)) {
        addedDates.add(entryDateStr);
        <span class="hljs-keyword">if</span> (addedDates.size &gt; <span class="hljs-number">6</span>) <span class="hljs-keyword">return</span>;


        <span class="hljs-keyword">const</span> condition = entry.weather[<span class="hljs-number">0</span>].main;
        <span class="hljs-keyword">const</span> iconSrc = getWeatherIcon(condition);

        <span class="hljs-keyword">const</span> forecastItem = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
        forecastItem.className = <span class="hljs-string">'forecast-item'</span>;

        <span class="hljs-keyword">const</span> date = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'p'</span>);
        date.id = <span class="hljs-string">'date'</span>;
        date.innerHTML = <span class="hljs-string">`&lt;strong&gt;<span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(entry.dt * <span class="hljs-number">1000</span>).toLocaleDateString()}</span>&lt;/strong&gt;`</span>;

        <span class="hljs-keyword">const</span> icon = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'img'</span>);
        icon.loading = <span class="hljs-string">'lazy'</span>;
        icon.id = <span class="hljs-string">'weather-icon'</span>;
        icon.src = iconSrc;
        icon.alt = <span class="hljs-string">`<span class="hljs-subst">${condition}</span> icon`</span>;

        <span class="hljs-keyword">const</span> tempContainer = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
        tempContainer.className = <span class="hljs-string">'temperature-container'</span>;

        <span class="hljs-keyword">const</span> temp = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'h3'</span>);
        temp.id = <span class="hljs-string">'temperature'</span>;
        temp.innerHTML = <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">Math</span>.round(entry.main.temp)}</span> °C`</span>;

        <span class="hljs-keyword">const</span> description = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'p'</span>);
        description.id = <span class="hljs-string">'weather-description'</span>;
        description.innerHTML = <span class="hljs-string">`<span class="hljs-subst">${entry.weather[<span class="hljs-number">0</span>].description}</span>`</span>;

        tempContainer.appendChild(temp);
        tempContainer.appendChild(description);
        forecastItem.appendChild(date);
        forecastItem.appendChild(icon);
        forecastItem.appendChild(tempContainer);
        container.appendChild(forecastItem);
      }
    });

    forecastWrapper.appendChild(heading);
    forecastWrapper.appendChild(container);
    fragment.appendChild(forecastWrapper);
    weatherInfo.appendChild(fragment); 
}

<span class="hljs-comment">// Fetch next 5-day forecast by coordinates</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get5DaysForecast</span>(<span class="hljs-params">lat, lon</span>) </span>{
    fetch(
      <span class="hljs-string">`https://api.openweathermap.org/data/2.5/forecast?lat=<span class="hljs-subst">${lat}</span>&amp;lon=<span class="hljs-subst">${lon}</span>&amp;appid=<span class="hljs-subst">${CONFIG.WEATHER_API_KEY}</span>&amp;units=metric`</span>
    )
      .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
      .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
        requestIdleCallback(<span class="hljs-function">() =&gt;</span> {
          <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> display5DaysForecast(data.list), <span class="hljs-number">0</span>);
        });        
      })
      .catch(<span class="hljs-function">() =&gt;</span> {
        weatherInfo.innerHTML = <span class="hljs-string">'Error fetching forecast data.'</span>;
    });
}

 <span class="hljs-comment">// Display current weather data</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">displayUserWeather</span>(<span class="hljs-params">data</span>) </span>{
    <span class="hljs-keyword">const</span> weatherCondition = data.weather[<span class="hljs-number">0</span>].main;
    <span class="hljs-keyword">const</span> iconSrc = getWeatherIcon(weatherCondition);

    weatherInfo.innerHTML = <span class="hljs-string">`
      &lt;h2 id="city"&gt;<span class="hljs-subst">${data.name}</span>, <span class="hljs-subst">${data.sys.country}</span>&lt;/h2&gt;

      &lt;div class="current-weather"&gt;
        &lt;img loading="lazy" id="weather-icon" src="<span class="hljs-subst">${iconSrc}</span>" alt="Weather icon"&gt;
        &lt;div class="temperature-container"&gt;
          &lt;h3 id="temperature"&gt; <span class="hljs-subst">${<span class="hljs-built_in">Math</span>.round(data.main.temp)}</span> °C&lt;/h3&gt;
          &lt;p id="weather-description"&gt;<span class="hljs-subst">${data.weather[<span class="hljs-number">0</span>].description}</span>&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="weather-details"&gt;
        &lt;div class="detail"&gt;
          &lt;img loading="lazy" id="humidity-icon" src="/images/humidity.png" alt="Humidity icon"&gt;
          &lt;span class="label"&gt;Humidity&lt;/span&gt;
          &lt;span id="humidity" class="value"&gt; <span class="hljs-subst">${data.main.humidity}</span>%&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class="detail"&gt;
          &lt;img loading="lazy" id="wind-icon" src="/images/wind.png" alt="Wind icon"&gt;
          &lt;span class="label"&gt;Wind&lt;/span&gt;
          &lt;span id="wind" class="value"&gt; <span class="hljs-subst">${data.wind.speed}</span> m/s&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    `</span>;
  }

<span class="hljs-comment">// Fetch weather by coordinates</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getWeatherByCoords</span>(<span class="hljs-params">lat, lon</span>) </span>{
    fetch(
      <span class="hljs-string">`https://api.openweathermap.org/data/2.5/weather?lat=<span class="hljs-subst">${lat}</span>&amp;lon=<span class="hljs-subst">${lon}</span>&amp;appid=<span class="hljs-subst">${CONFIG.WEATHER_API_KEY}</span>&amp;units=metric`</span>
    )
      .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
      .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
        displayUserWeather(data);
        get5DaysForecast(lat, lon);
      })
      .catch(<span class="hljs-function">() =&gt;</span> {
        weatherInfo.innerHTML = <span class="hljs-string">'Please turn on your device&amp;apos;s location to get weather data.'</span>;;
      });
  }

<span class="hljs-comment">// Event listeners for search button and input field</span>
cityName.addEventListener(<span class="hljs-string">'keypress'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'Enter'</span>) checkWeatherBySearch(cityName.value);
});

  <span class="hljs-comment">// Search button click event</span>
searchButton.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">()=&gt;</span>{
    checkWeatherBySearch(cityName.value);
});

<span class="hljs-comment">// Geolocation button</span>
locationBtn.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(
        <span class="hljs-function"><span class="hljs-params">pos</span> =&gt;</span> {
          <span class="hljs-keyword">const</span> { latitude, longitude } = pos.coords;
          getWeatherByCoords(latitude, longitude);
        },
        <span class="hljs-function">() =&gt;</span> {
          weatherInfo.innerHTML = <span class="hljs-string">'Unable to retrieve location.'</span>;
        }
      );
    } <span class="hljs-keyword">else</span> {
      weatherInfo.innerHTML = <span class="hljs-string">'Geolocation not supported.'</span>;
    }
});


<span class="hljs-comment">// Load last searched city</span>
<span class="hljs-built_in">window</span>.onload = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> lastCity = <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">'lastCity'</span>);
    <span class="hljs-keyword">if</span> (lastCity) {
        checkWeatherBySearch(lastCity);
    }

    <span class="hljs-keyword">if</span> (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(
          <span class="hljs-function"><span class="hljs-params">pos</span> =&gt;</span> {
            <span class="hljs-keyword">const</span> { latitude, longitude } = pos.coords;
            getWeatherByCoords(latitude, longitude);
          },
          <span class="hljs-function">() =&gt;</span> {
            weatherInfo.innerHTML = <span class="hljs-string">'Unable to retrieve location.'</span>;
          }
        );
      } <span class="hljs-keyword">else</span> {
        weatherInfo.innerHTML = <span class="hljs-string">'Geolocation not supported.'</span>;
      }
};
</code></pre>
<p>Now that we have our weather app. Let’s go further to make it a progressive web app.</p>
<h2 id="heading-how-to-create-a-manifest-file">How to Create a Manifest File</h2>
<p>We need to create a <code>manifest.json</code> file, a critical part of making your app a PWA. We’ll also use <a target="_blank" href="https://www.npmjs.com/package/pwa-asset-generator"><strong>pwa-asset-generator</strong></a>, a CLI tool that helps you to generate all the necessary icons and splash screens from a single image (like your logo). This tool also updates your <code>manifest.json</code> and optionally injects relevant <code>&lt;link&gt;</code> tags into <code>index.html</code>.</p>
<p>Below is the <code>manifest.json</code> file containing key properties that define how the Progressive Web App behaves and appears when installed.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Weatherly"</span>,                      <span class="hljs-comment">// The full name of your app that may be shown to users.</span>
  <span class="hljs-attr">"short_name"</span>: <span class="hljs-string">"Weatherly"</span>,               <span class="hljs-comment">// A shorter name used when space is limited, like on the home screen.</span>
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"A simple weather Progressive Web App"</span>, <span class="hljs-comment">// A short description of what your app does.</span>
  <span class="hljs-attr">"start_url"</span>: <span class="hljs-string">"/index.html"</span>,              <span class="hljs-comment">// The page that opens when the app is launched from the home screen.</span>
  <span class="hljs-attr">"display"</span>: <span class="hljs-string">"standalone"</span>,                 <span class="hljs-comment">// Makes the app look like a native app without browser UI (like address bar).</span>
  <span class="hljs-attr">"background_color"</span>: <span class="hljs-string">"#ffffff"</span>,           <span class="hljs-comment">// The background color used when the app is loading.</span>
  <span class="hljs-attr">"theme_color"</span>: <span class="hljs-string">"#2196f3"</span>,                <span class="hljs-comment">// The main color of the app’s UI, like the status bar.</span>
  <span class="hljs-attr">"orientation"</span>: <span class="hljs-string">"portrait"</span>,                <span class="hljs-comment">// Locks the screen orientation to portrait mode.</span>
   <span class="hljs-attr">"screenshots"</span>: [                         <span class="hljs-comment">//helps show users a preview of your app before installing it — especially in places like the "Add to Home screen" prompt on Android or in app stores that support PWAs.</span>
        {
          <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/screenshots/desktop-screenshot.png"</span>,
          <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"1337x645"</span>,
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
          <span class="hljs-attr">"form_factor"</span>: <span class="hljs-string">"wide"</span>
        },
        {
          <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/screenshots/mobile-screenshot.png"</span>,
          <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"720x1417"</span>,
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
          <span class="hljs-attr">"form_factor"</span>: <span class="hljs-string">"narrow"</span>
        }
      ]
}
</code></pre>
<h3 id="heading-how-to-generate-icons-and-splash-screens">How to Generate Icons and Splash Screens</h3>
<p>Inside your <code>images</code> folder, create a new folder called <code>assets</code>. This will store all the generated icons and splash screens. When your app is launched from the home screen, these splash screens will help improve the user experience on iOS devices.</p>
<p>Run the following command to generate PWA assets, update the <code>manifest.json</code>, and inject <code>&lt;link&gt;</code> tags into <code>index.html</code></p>
<pre><code class="lang-powershell">npx pwa<span class="hljs-literal">-asset</span><span class="hljs-literal">-generator</span> logo.png ./images/assets <span class="hljs-literal">-m</span> manifest.json <span class="hljs-literal">-i</span> index.html
</code></pre>
<h3 id="heading-injected-link-tags-in-indexhtml">Injected Link Tags in <code>index.html</code></h3>
<p>Once the command runs successfully, a series of <code>&lt;link&gt;</code> and <code>&lt;meta&gt;</code> Tags will be automatically added to your <code>index.html</code> <code>&lt;head&gt;</code>. These tags ensure support for splash screens and icons across various Apple devices:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
  <span class="hljs-comment">&lt;!-- Other meta/link tags --&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/assets/apple-icon-180.png"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"mobile-web-app-capable"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"yes"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-startup-image"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/assets/apple-splash-2048-2732.jpg"</span> <span class="hljs-attr">media</span>=<span class="hljs-string">"(device-width: 1024px) and (device-height: 1366px) and (orientation: portrait)"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-startup-image"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/assets/apple-splash-2732-2048.jpg"</span> <span class="hljs-attr">media</span>=<span class="hljs-string">"(device-width: 1024px) and (device-height: 1366px) and (orientation: landscape)"</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- ...more splash screen tags for various devices... --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
</code></pre>
<p>Here’s how the <code>manifest.json</code> file should look like now:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Weatherly"</span>,
    <span class="hljs-attr">"short_name"</span>: <span class="hljs-string">"Weatherly"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"A simple weather Progressive Web App"</span>,
    <span class="hljs-attr">"start_url"</span>: <span class="hljs-string">"/index.html"</span>,
    <span class="hljs-attr">"display"</span>: <span class="hljs-string">"standalone"</span>,
    <span class="hljs-attr">"background_color"</span>: <span class="hljs-string">"#ffffff"</span>,
    <span class="hljs-attr">"theme_color"</span>: <span class="hljs-string">"#2196f3"</span>,
    <span class="hljs-attr">"orientation"</span>: <span class="hljs-string">"portrait"</span>,
    <span class="hljs-attr">"icons"</span>: [
        [
            {
              <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/assets/manifest-icon-192.maskable.png"</span>,
              <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"192x192"</span>,
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
              <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"any"</span>
            },
            {
              <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/assets/manifest-icon-192.maskable.png"</span>,
              <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"192x192"</span>,
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
              <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"maskable"</span>
            },
            {
              <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/assets/manifest-icon-512.maskable.png"</span>,
              <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"512x512"</span>,
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
              <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"any"</span>
            },
            {
              <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/assets/manifest-icon-512.maskable.png"</span>,
              <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"512x512"</span>,
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
              <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"maskable"</span>
            }
          ]
        ],
    <span class="hljs-attr">"screenshots"</span>: [
        {
          <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/screenshots/desktop-screenshot.png"</span>,
          <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"1337x645"</span>,
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
          <span class="hljs-attr">"form_factor"</span>: <span class="hljs-string">"wide"</span>
        },
        {
          <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/screenshots/mobile-screenshot.png"</span>,
          <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"720x1417"</span>,
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
          <span class="hljs-attr">"form_factor"</span>: <span class="hljs-string">"narrow"</span>
        }
      ]
    }
</code></pre>
<p>You can then link your manifest file to your HTML file:</p>
<pre><code class="lang-json">&lt;link rel=<span class="hljs-string">"manifest"</span> href=<span class="hljs-string">"manifest.json"</span> /&gt;
</code></pre>
<h2 id="heading-how-to-add-workbox-to-your-service-workerjs-file">How to Add WorkBox to Your <code>service-worker.js</code> File</h2>
<p>In this tutorial, WorkBox will be added to <code>index.html</code> via CDN. You can copy the import code below or visit WorkBox to get the link. You can then add it to the <code>index.html</code> file by placing the URL inside a <code>&lt;script&gt;</code> tag. You can copy the import code below or visit the WorkBox website for the latest link.</p>
<pre><code class="lang-javascript">importScripts(<span class="hljs-string">'https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js'</span>);
</code></pre>
<h2 id="heading-how-to-create-your-service-worker-in-the-service-workerjs-file">How to Create your Service Worker in the <code>service-worker.js</code> File</h2>
<p>Here, we’ll implement the necessary functionalities needed to make the weather app a PWA</p>
<h3 id="heading-step-1-activate-the-new-service-worker-immediately"><strong>Step 1:</strong> Activate the New Service Worker Immediately</h3>
<p>Add <code>workbox.core.skipWaiting()</code> to make the newly installed service worker activate right away instead of waiting for the old one to be removed in the <code>service-worker.js</code> file.</p>
<pre><code class="lang-javascript">workbox.core.skipWaiting();
</code></pre>
<h3 id="heading-step-2-take-control-of-open-tabs"><strong>Step 2:</strong> Take Control of Open Tabs</h3>
<p>Add <code>workbox.core.clientsClaim()</code> to ensure that the activated service worker takes control of all currently open pages, so the latest version of your app works immediately across all tabs after it becomes active.</p>
<pre><code class="lang-javascript">workbox.core.clientsClaim();
</code></pre>
<h3 id="heading-step-3-check-if-workbox-is-loaded">Step 3: Check if Workbox is Loaded</h3>
<p>Before using Workbox, make sure it has loaded properly.</p>
<pre><code class="lang-js"><span class="hljs-keyword">if</span> (workbox) {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Workbox loaded successfully'</span>);
} <span class="hljs-keyword">else</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Workbox failed to load'</span>);
}
</code></pre>
<p>This confirms that the <code>workbox</code> object is available and ready to use. If not, the fallback message in the <code>else</code> block will be shown.</p>
<p>We then proceed to create the functions inside the <code>if</code> block</p>
<h3 id="heading-step-4-pre-cache-core-files">Step 4: Pre-cache Core Files</h3>
<p>Pre-cache essential files enable your app to work offline. This caches your app shell (HTML, CSS, JS), so it loads even without a network connection.</p>
<pre><code class="lang-js">workbox.precaching.precacheAndRoute([
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/index.html'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'3'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/style.css'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'11'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/app.js'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'7'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/images/logo.png'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'3'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/manifest.json'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'5'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/offline.html'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'1'</span> },
  ]);
</code></pre>
<p>The <code>revision</code> helps with updating cached files when changes are made.</p>
<h3 id="heading-step-5-cache-api-responses-dynamically">Step 5: Cache API Responses Dynamically</h3>
<p>Set up a route to cache data from your weather API using the <code>NetworkFirst</code> caching strategy. This tells Workbox to try fetching fresh data from the network first. If the network fails, it serves the cached version instead.</p>
<pre><code class="lang-js"> <span class="hljs-comment">// Cache API requests </span>
  workbox.routing.registerRoute(
    <span class="hljs-function">(<span class="hljs-params">{ url }</span>) =&gt;</span> url.origin === <span class="hljs-string">'https://api.openweathermap.org'</span>,
    <span class="hljs-keyword">new</span> workbox.strategies.NetworkFirst({
      <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'weather-api-cache'</span>,
      <span class="hljs-attr">plugins</span>: [
        <span class="hljs-keyword">new</span> workbox.expiration.ExpirationPlugin({
          <span class="hljs-attr">maxAgeSeconds</span>: <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span>,
          <span class="hljs-attr">maxEntries</span>: <span class="hljs-number">10</span>,
        }),
      ],
    })
  );
</code></pre>
<h3 id="heading-step-6-dynamic-image-caching">Step 6: Dynamic Image Caching</h3>
<p>This function enables dynamic caching for images using the <code>StaleWhileRevalidate</code> strategy. When a user requests an image, Workbox first serves it from the cache (if available) for faster load times, while simultaneously fetching an updated version from the network to refresh the cache. This ensures users get a quick response without missing out on updated content. It’s a smart way to handle images by balancing speed and freshness.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Cache images</span>
  workbox.routing.registerRoute(
    <span class="hljs-function">(<span class="hljs-params">{ request }</span>) =&gt;</span> request.destination === <span class="hljs-string">'image'</span>,
    <span class="hljs-keyword">new</span> workbox.strategies.StaleWhileRevalidate({
      <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'image-cache'</span>,
    })
  );
</code></pre>
<h3 id="heading-step-7-serve-cached-resources">Step 7: Serve Cached Resources</h3>
<p>The commonly used static files (like HTML, CSS, JS, fonts, and so on) are served quickly from the cache. It uses the <code>CacheFirst</code> strategy, meaning that the service worker will look in the cache first and only fetch from the network if the file isn’t already stored. The cache is named <code>"static-cache"</code> and it’s set to automatically remove items older than seven days using the <code>expiration</code> plugin. This helps keep the cache fresh and avoids taking up too much space.</p>
<pre><code class="lang-javascript">  <span class="hljs-comment">// Serve Cached Resources </span>
  workbox.routing.registerRoute(
    <span class="hljs-function">(<span class="hljs-params">{url}</span>) =&gt;</span> url.origin === self.location.origin,  
    <span class="hljs-keyword">new</span> workbox.strategies.CacheFirst({
      <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'static-cache'</span>,  
      <span class="hljs-attr">plugins</span>: [
        <span class="hljs-keyword">new</span> workbox.expiration.ExpirationPlugin({
          <span class="hljs-attr">maxAgeSeconds</span>: <span class="hljs-number">7</span> * <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span>,  <span class="hljs-comment">// Cache static resources for 7 days</span>
        }),
      ],
    })
  );
</code></pre>
<h3 id="heading-step-8-cache-html-pages-with-offline-support"><strong>Step 8: Cache</strong> HTML Pages with Offline Support</h3>
<p>The <code>index.html</code> page will be handled using the NetworkFirst strategy. This means that the service worker tries to fetch the latest version from the network first. If the user is offline or the network fails, it falls back to the cached version. The cache is named <code>"pages-cache"</code> and the offline fallback page (<code>offline.html</code>) is returned when the requested page isn’t available. This ensures that users can still navigate the app even without an internet connection.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Serve HTML pages with Network First and offline fallback</span>
workbox.routing.registerRoute(
  <span class="hljs-function">(<span class="hljs-params">{ request }</span>) =&gt;</span> request.mode === <span class="hljs-string">'navigate'</span>,
  <span class="hljs-keyword">async</span> ({ event }) =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> workbox.strategies.networkFirst({
        <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'pages-cache'</span>,
        <span class="hljs-attr">plugins</span>: [
          <span class="hljs-keyword">new</span> workbox.expiration.ExpirationPlugin({
            <span class="hljs-attr">maxEntries</span>: <span class="hljs-number">50</span>,
          }),
        ],
      }).handle({ event });
      <span class="hljs-keyword">return</span> response || <span class="hljs-keyword">await</span> caches.match(<span class="hljs-string">'/offline.html'</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> caches.match(<span class="hljs-string">'/offline.html'</span>);
    }
  }
);
</code></pre>
<h3 id="heading-step-9-handle-when-workbox-doesnt-load">Step 9: Handle When Workbox Doesn’t Load</h3>
<p>You should always provide a fallback in case something goes wrong. The <code>if</code> block will have an <code>else</code> block to catch issues during development and debugging.</p>
<pre><code class="lang-js"><span class="hljs-keyword">else</span> {
     <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Workbox failed to load'</span>);
}
</code></pre>
<p>Once the service worker finishes handling the different conditions in the <code>if-else</code> block, we add a general cleanup step to remove any outdated or unused caches.</p>
<h3 id="heading-step-10-clean-up-outdated-caches"><strong>Step 10:</strong> Clean Up Outdated Caches</h3>
<p>During the service worker's activation phase, old or unused caches are removed. It compares all existing cache names with a list of current ones (<code>precache</code>, <code>weather-api-cache</code>, <code>image-cache</code>, <code>pages-cache</code>, and <code>static-resources</code>). If a cache doesn’t match the current list, it gets deleted. This helps keep the app lightweight and ensures that outdated data doesn't persist.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Clean up old/unused caches during activation</span>
self.addEventListener(<span class="hljs-string">'activate'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> currentCaches = [
    workbox.core.cacheNames.precache,
    <span class="hljs-string">'weather-api-cache'</span>,
    <span class="hljs-string">'image-cache'</span>,
    <span class="hljs-string">'pages-cache'</span>,
    <span class="hljs-string">'static-cache'</span>
  ];

  event.waitUntil(
    caches.keys().then(<span class="hljs-function"><span class="hljs-params">cacheNames</span> =&gt;</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.all(
        cacheNames.map(<span class="hljs-function"><span class="hljs-params">cacheName</span> =&gt;</span> {
          <span class="hljs-keyword">if</span> (!currentCaches.includes(cacheName)) {
            <span class="hljs-keyword">return</span> caches.delete(cacheName);
          }
        })
      );
    })
  );
});
</code></pre>
<p>This is what your <code>service-worker.js</code> file should look like:</p>
<pre><code class="lang-javascript">importScripts(<span class="hljs-string">'https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js'</span>);

<span class="hljs-comment">// Force waiting service worker to become active</span>
workbox.core.skipWaiting();
workbox.core.clientsClaim();

<span class="hljs-keyword">if</span> (workbox) {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Workbox loaded successfully'</span>);

  <span class="hljs-comment">// Precache critical files with revisions (update revisions when files change)</span>
  workbox.precaching.precacheAndRoute([
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/index.html'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'3'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/style.css'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'11'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/app.js'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'7'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/images/logo.png'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'3'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/manifest.json'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'5'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/offline.html'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'1'</span> },
  ]);

  <span class="hljs-comment">// Cache API requests </span>
  workbox.routing.registerRoute(
    <span class="hljs-function">(<span class="hljs-params">{ url }</span>) =&gt;</span> url.origin === <span class="hljs-string">'https://api.openweathermap.org'</span>,
    <span class="hljs-keyword">new</span> workbox.strategies.NetworkFirst({
      <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'weather-api-cache'</span>,
      <span class="hljs-attr">plugins</span>: [
        <span class="hljs-keyword">new</span> workbox.expiration.ExpirationPlugin({
          <span class="hljs-attr">maxAgeSeconds</span>: <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span>,
          <span class="hljs-attr">maxEntries</span>: <span class="hljs-number">10</span>,
        }),
      ],
    })
  );

  <span class="hljs-comment">// Cache images</span>
  workbox.routing.registerRoute(
    <span class="hljs-function">(<span class="hljs-params">{ request }</span>) =&gt;</span> request.destination === <span class="hljs-string">'image'</span>,
    <span class="hljs-keyword">new</span> workbox.strategies.StaleWhileRevalidate({
      <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'image-cache'</span>,
    })
  );

    <span class="hljs-comment">// Serve Cached Resources </span>
  workbox.routing.registerRoute(
    <span class="hljs-function">(<span class="hljs-params">{url}</span>) =&gt;</span> url.origin === self.location.origin,  
    <span class="hljs-keyword">new</span> workbox.strategies.CacheFirst({
      <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'static-cache'</span>,  
      <span class="hljs-attr">plugins</span>: [
        <span class="hljs-keyword">new</span> workbox.expiration.ExpirationPlugin({
          <span class="hljs-attr">maxAgeSeconds</span>: <span class="hljs-number">7</span> * <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span>,  <span class="hljs-comment">// Cache static resources for 7 days</span>
        }),
      ],
    })
  );

  <span class="hljs-comment">// Serve HTML pages with Network First and offline fallback</span>
workbox.routing.registerRoute(
  <span class="hljs-function">(<span class="hljs-params">{ request }</span>) =&gt;</span> request.mode === <span class="hljs-string">'navigate'</span>,
  <span class="hljs-keyword">async</span> ({ event }) =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> workbox.strategies.networkFirst({
        <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'pages-cache'</span>,
        <span class="hljs-attr">plugins</span>: [
          <span class="hljs-keyword">new</span> workbox.expiration.ExpirationPlugin({
            <span class="hljs-attr">maxEntries</span>: <span class="hljs-number">50</span>,
          }),
        ],
      }).handle({ event });
      <span class="hljs-keyword">return</span> response || <span class="hljs-keyword">await</span> caches.match(<span class="hljs-string">'/offline.html'</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> caches.match(<span class="hljs-string">'/offline.html'</span>);
    }
  }
);
} <span class="hljs-keyword">else</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Workbox failed to load'</span>);
}

<span class="hljs-comment">// Clean up old/unused caches during activation</span>
self.addEventListener(<span class="hljs-string">'activate'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> currentCaches = [
    workbox.core.cacheNames.precache,
    <span class="hljs-string">'weather-api-cache'</span>,
    <span class="hljs-string">'image-cache'</span>,
    <span class="hljs-string">'pages-cache'</span>,
    <span class="hljs-string">'static-cache'</span>
  ];

  event.waitUntil(
    caches.keys().then(<span class="hljs-function"><span class="hljs-params">cacheNames</span> =&gt;</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.all(
        cacheNames.map(<span class="hljs-function"><span class="hljs-params">cacheName</span> =&gt;</span> {
          <span class="hljs-keyword">if</span> (!currentCaches.includes(cacheName)) {
            <span class="hljs-keyword">return</span> caches.delete(cacheName);
          }
        })
      );
    })
  );
});
</code></pre>
<h2 id="heading-how-to-set-up-app-installation">How to Set Up App Installation</h2>
<p>The code to install the app will be written in <code>install.js</code> following the steps below:</p>
<h3 id="heading-step-1-register-the-service-worker"><strong>Step 1:</strong> Register the Service Worker</h3>
<p>Register the service worker to activate and run it in your app.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span>(<span class="hljs-string">'serviceWorker'</span> <span class="hljs-keyword">in</span> navigator){
    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'load'</span>, <span class="hljs-function">() =&gt;</span> {
      navigator.serviceWorker.register(<span class="hljs-string">'/service-worker.js'</span>).then(<span class="hljs-function"><span class="hljs-params">reg</span> =&gt;</span> {
        reg.onupdatefound = <span class="hljs-function">() =&gt;</span> {
          <span class="hljs-keyword">const</span> newWorker = reg.installing;
          newWorker.onstatechange = <span class="hljs-function">() =&gt;</span> {
            <span class="hljs-keyword">if</span> (newWorker.state === <span class="hljs-string">'installed'</span> &amp;&amp; navigator.serviceWorker.controller) {
              <span class="hljs-built_in">window</span>.location.reload();
            }
          };
        };
      });
    })
 }
</code></pre>
<h3 id="heading-step-2-enable-custom-install-prompt">Step 2: Enable Custom Install Prompt</h3>
<p>Next, we will allow users to install the weather PWA with a custom button. Inside the <code>install.js</code>file, add the <code>beforeinstallprompt</code> event which intercepts the default prompt and shows your install button instead. When clicked, it triggers the install prompt.</p>
<pre><code class="lang-javascript">
  <span class="hljs-keyword">let</span> deferredPrompt;

<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'DOMContentLoaded'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> installBtn = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'installBtn'</span>);

  <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'beforeinstallprompt'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    e.preventDefault();
    deferredPrompt = e;

    <span class="hljs-comment">// Show the button</span>
    installBtn.style.display = <span class="hljs-string">'block'</span>;

    installBtn.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Directly triggered by user click</span>
      installBtn.style.display = <span class="hljs-string">'none'</span>;

      <span class="hljs-comment">// Show the install prompt</span>
      deferredPrompt.prompt();

      deferredPrompt.userChoice.then(<span class="hljs-function">(<span class="hljs-params">choiceResult</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (choiceResult.outcome === <span class="hljs-string">'accepted'</span>) {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'User accepted the install prompt'</span>);
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'User dismissed the install prompt'</span>);
        }
        deferredPrompt = <span class="hljs-literal">null</span>;
      });
    });
  });
</code></pre>
<p>The <code>appinstalled</code> event confirms successful installation.</p>
<pre><code class="lang-javascript">
<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'appinstalled'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'PWA was installed'</span>);
  });
});
</code></pre>
<h3 id="heading-step-3-add-script-tag-to-import-installjs-in-indexhtml">Step 3: Add script tag to import <code>install.js</code> in <code>index.html</code></h3>
<p>Add the <code>&lt;script&gt;</code> tag for <code>install.js</code> inside the <code>index.html</code> file to include the installation logic.</p>
<pre><code class="lang-xml"> <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/js/install.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<h2 id="heading-how-to-install-the-weather-app">How to Install the Weather App</h2>
<p>You can choose to install the Weatherly app on your phone or desktop. Below is a demonstration on how to install it on your mobile phone:</p>
<p>Open the <a target="_blank" href="https://weatherly-taupe-two.vercel.app/">Weatherly</a> app in your browser. You should see an <strong>“Install App”</strong> button, as shown in the image below. Click on the button to continue.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747272209446/2ade0ad7-eda5-46df-b443-a1efce90003b.png" alt="Weatherly app interface showing Install App button along with city search field, location services, and Tokyo weather history" class="image--center mx-auto" width="720" height="1318" loading="lazy"></p>
<p>After clicking, a preview of the app will appear along with an <strong>“Install”</strong> option, as shown below. Click the Install button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748387183047/b6be3b6c-6550-4c94-a453-eb928cc70dbe.png" alt="Browser PWA installation dialog showing Weatherly app preview with Install button and app description." class="image--center mx-auto" width="720" height="1299" loading="lazy"></p>
<p>Once the installation is complete, the Weatherly app will appear on your home screen, just like a native app. And that’s it! Your weather app is now a Progressive Web App (PWA).</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Progressive Web Apps combine the best of web and native app experiences, and service workers are the backbone of that functionality. With tools like Workbox, you don’t have to worry about manually handling caching, offline support, or background sync. Its simple APIs and built-in strategies make it easier to build fast, reliable, and installable web apps. Whether it’s a small weather app like <a target="_blank" href="https://weatherly-pwa.vercel.app/">Weatherly</a> or a more complex project, Workbox helps you deliver a seamless user experience.</p>
<p>You can check out the full project and assets on <a target="_blank" href="https://github.com/LolaVictoria/weatherly">GitHub</a></p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
