Repository: Zchasse63/NomadSauna
Pull request #8: Fix hero section glitches and improve mobile performance
Author: login: Zchasse63
Created: 2025-04-18T23:03:55Z | Merged: 2025-04-18T23:07:17Z

## Changes

This PR addresses the glitchy behavior and shadows in the hero section, particularly on mobile devices, by implementing several optimizations:

### Image Optimizations
- Updated the OptimizedImage component to properly optimize images
- Added image preloading to ensure the background image is loaded before display
- Reduced image quality for the background to improve performance
- Added proper sizing for responsive images

### Animation Improvements
- Added `will-change-transform` to elements with animations to hint the browser to optimize
- Replaced CSS variable animation delays with inline style delays for better browser support
- Removed the negative translate-y that was causing layout shifts
- Updated the fade-in animation to be smoother with a better easing function
- Added a subtle blur effect during animation for smoother transitions

### Loading Experience
- Created a loading state for the hero section with skeleton placeholders
- Added a preload component to ensure critical images are loaded before display
- Updated the global loading component to be more visually appealing
- Added fallback mechanisms in case images fail to load

### Performance Optimizations
- Reduced the blur amount on the background image for better performance
- Used transform-gpu to force GPU acceleration for the background image
- Optimized animation timing to reduce jank and improve perceived performance
- Added proper responsive image sizing to reduce unnecessary downloads

## Testing

Please test on mobile devices to verify that the hero section loads smoothly without glitches or shadows.

---
Pull Request opened by [Augment Code](https://www.augmentcode.com/) with guidance from the PR author

## Summary by Sourcery

Optimize hero section performance and loading experience with improved image handling, animations, and loading states

New Features:
- Added a preload images component to ensure critical assets are loaded before display
- Implemented a loading state with skeleton placeholders for smoother initial page render

Enhancements:
- Improved hero section animations with smoother transitions and better browser performance
- Optimized image loading and rendering with responsive sizing and quality adjustments


Diff:
diff --git a/app/layout.tsx b/app/layout.tsx
index 122d4ce..c9dc97d 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,5 +1,6 @@
 import { ThemeProvider } from "@/components/theme-provider";
 import { Toaster } from "@/components/ui/sonner";
+import { PreloadImages } from "@/components/ui/preload-images";
 import { cn } from "@/lib/utils";
 import type { Metadata } from "next";
 import "./globals.css";
@@ -62,6 +63,7 @@ export default function RootLayout({
           defaultTheme="light"
           disableTransitionOnChange
         >
+          <PreloadImages images={["/images/showcase/IMG_7359.jpeg"]} />
           {children}
           <Toaster />
         </ThemeProvider>
diff --git a/app/loading.tsx b/app/loading.tsx
index 68b2ec4..ccea364 100644
--- a/app/loading.tsx
+++ b/app/loading.tsx
@@ -1,35 +1,29 @@
+"use client";
+
 import { Skeleton } from "@/components/ui/skeleton";
+import { cn } from "@/lib/utils";
+import { useEffect, useState } from "react";
 
 export default function Loading() {
+  const [isVisible, setIsVisible] = useState(true);
+
+  useEffect(() => {
+    // Hide the loading indicator after a short delay
+    const timer = setTimeout(() => {
+      setIsVisible(false);
+    }, 1000);
+
+    return () => clearTimeout(timer);
+  }, []);
+
+  if (!isVisible) return null;
+
   return (
-    <div className="container mx-auto px-4 py-24 mt-16">
-      <div className="max-w-4xl mx-auto">
-        {/* Header skeleton */}
-        <Skeleton className="h-12 w-3/4 mb-6" />
-        <Skeleton className="h-6 w-full mb-12" />
-        
-        {/* Content skeleton */}
-        <div className="grid md:grid-cols-2 gap-12 mb-16">
-          <div className="space-y-6">
-            <Skeleton className="h-8 w-1/2 mb-4" />
-            <Skeleton className="h-64 w-full rounded-lg" />
-            <div className="space-y-4">
-              <Skeleton className="h-4 w-full" />
-              <Skeleton className="h-4 w-5/6" />
-              <Skeleton className="h-4 w-4/6" />
-            </div>
-          </div>
-          
-          <div className="space-y-6">
-            <Skeleton className="h-8 w-1/2 mb-4" />
-            <div className="space-y-4">
-              <Skeleton className="h-12 w-full" />
-              <Skeleton className="h-12 w-full" />
-              <Skeleton className="h-12 w-full" />
-              <Skeleton className="h-36 w-full" />
-              <Skeleton className="h-12 w-full" />
-            </div>
-          </div>
+    <div className="fixed inset-0 z-50 flex items-center justify-center bg-background transition-opacity duration-500">
+      <div className="flex flex-col items-center gap-4">
+        <div className="h-12 w-12 animate-spin rounded-full border-4 border-[#D9C4A3] border-t-[#5B402D]" />
+        <div className={cn("text-lg font-serif text-[#5B402D] dark:text-[#D9C4A3]")}>
+          Loading...
         </div>
       </div>
     </div>
diff --git a/app/page.tsx b/app/page.tsx
new file mode 100644
index 0000000..fa77cbc
--- /dev/null
+++ b/app/page.tsx
@@ -0,0 +1,55 @@
+"use client";
+
+import HeroSection from "@/components/landing/hero-section";
+import { useEffect, useState } from "react";
+import { Skeleton } from "@/components/ui/skeleton";
+import ShowcaseSection from "@/components/landing/showcase-section";
+import ServicesSection from "@/components/landing/services-section";
+import TestimonialsSection from "@/components/landing/testimonials-section";
+import CtaSection from "@/components/landing/cta-section";
+import FaqSection from "@/components/landing/faq-section";
+
+export default function Home() {
+  const [isLoading, setIsLoading] = useState(true);
+
+  useEffect(() => {
+    // Preload the hero image
+    const img = new Image();
+    img.src = "/images/showcase/IMG_7359.jpeg";
+    
+    img.onload = () => {
+      // Add a small delay to ensure smooth transition
+      setTimeout(() => {
+        setIsLoading(false);
+      }, 300);
+    };
+
+    // Fallback in case image loading fails
+    const timer = setTimeout(() => {
+      setIsLoading(false);
+    }, 2000);
+
+    return () => clearTimeout(timer);
+  }, []);
+
+  return (
+    <main className="relative">
+      {isLoading ? (
+        <div className="mx-auto mt-32 max-w-[80rem] px-6 text-center md:px-8">
+          <Skeleton className="mx-auto h-7 w-64 rounded-full" />
+          <Skeleton className="mx-auto mt-6 h-20 w-3/4" />
+          <Skeleton className="mx-auto mt-4 h-16 w-2/3" />
+          <Skeleton className="mx-auto mt-4 h-24 w-1/2" />
+          <Skeleton className="mx-auto mt-8 h-10 w-40 rounded-lg" />
+        </div>
+      ) : (
+        <HeroSection />
+      )}
+      <ShowcaseSection />
+      <ServicesSection />
+      <TestimonialsSection />
+      <CtaSection />
+      <FaqSection />
+    </main>
+  );
+}
diff --git a/components/landing/hero-section.tsx b/components/landing/hero-section.tsx
index 41d52de..2bdabb9 100644
--- a/components/landing/hero-section.tsx
+++ b/components/landing/hero-section.tsx
@@ -12,37 +12,38 @@ export default function HeroSection() {
       id="hero"
       className="relative mx-auto mt-32 max-w-[80rem] px-6 text-center md:px-8 overflow-hidden"
     >
-      <div className="absolute inset-0 -z-10 opacity-10 dark:opacity-5">
+      <div className="absolute inset-0 -z-10 opacity-10 dark:opacity-5 will-change-transform">
         <OptimizedImage
           src="/images/showcase/IMG_7359.jpeg"
           alt="Sauna background"
           fill
           priority
-          sizes="100vw"
-          className="object-cover blur-sm"
+          sizes="(max-width: 768px) 100vw, 80rem"
+          quality={60}
+          className="object-cover blur-[2px] transform-gpu"
         />
         <div className="absolute inset-0 bg-gradient-to-b from-background via-transparent to-background" />
       </div>
-      <div className="backdrop-filter-[12px] inline-flex h-7 items-center justify-between rounded-full border border-[#5B402D]/20 bg-[#D9C4A3]/10 px-3 text-xs text-[#1E1A18] transition-all ease-in hover:cursor-pointer hover:bg-[#D9C4A3]/20 group gap-1 translate-y-[-1rem] animate-fade-in opacity-0">
+      <div className="backdrop-filter-[12px] inline-flex h-7 items-center justify-between rounded-full border border-[#5B402D]/20 bg-[#D9C4A3]/10 px-3 text-xs text-[#1E1A18] transition-all ease-in hover:cursor-pointer hover:bg-[#D9C4A3]/20 group gap-1 translate-y-0 animate-fade-in opacity-0 will-change-transform" style={{animationDelay: '100ms'}}>
         <TextShimmer className="inline-flex items-center justify-center">
           <span>🔥 Handcrafted Saunas for Modern Living</span>{" "}
           <ArrowRightIcon className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
         </TextShimmer>
       </div>
-      <h1 className="bg-gradient-to-br dark:from-[#E6D3AF] from-[#1E1A18] from-30% dark:to-[#D9C4A3]/90 to-[#5B402D]/90 bg-clip-text py-6 text-5xl font-serif font-medium leading-none tracking-tighter text-transparent text-balance sm:text-6xl md:text-7xl lg:text-8xl translate-y-[-1rem] animate-fade-in opacity-0 [--animation-delay:200ms]">
+      <h1 className="bg-gradient-to-br dark:from-[#E6D3AF] from-[#1E1A18] from-30% dark:to-[#D9C4A3]/90 to-[#5B402D]/90 bg-clip-text py-6 text-5xl font-serif font-medium leading-none tracking-tighter text-transparent text-balance sm:text-6xl md:text-7xl lg:text-8xl translate-y-0 animate-fade-in opacity-0 will-change-transform" style={{animationDelay: '200ms'}}>
         Nomad Sauna Co.
       </h1>
-      <h2 className="bg-gradient-to-br dark:from-[#D9C4A3] from-[#5B402D] from-30% dark:to-[#E6D3AF]/90 to-[#1E1A18]/90 bg-clip-text py-4 text-3xl font-serif font-medium leading-none tracking-tighter text-transparent text-balance sm:text-4xl md:text-5xl translate-y-[-1rem] animate-fade-in opacity-0 [--animation-delay:300ms]">
+      <h2 className="bg-gradient-to-br dark:from-[#D9C4A3] from-[#5B402D] from-30% dark:to-[#E6D3AF]/90 to-[#1E1A18]/90 bg-clip-text py-4 text-3xl font-serif font-medium leading-none tracking-tighter text-transparent text-balance sm:text-4xl md:text-5xl translate-y-0 animate-fade-in opacity-0 will-change-transform" style={{animationDelay: '300ms'}}>
         Experience the warmth of
         <br className="hidden md:block" /> traditional sauna culture.
       </h2>
-      <p className="mb-16 mt-4 text-lg tracking-tight text-[#5B402D] dark:text-[#D9C4A3] md:text-xl text-balance translate-y-[-1rem] animate-fade-in opacity-0 [--animation-delay:400ms]">
+      <p className="mb-16 mt-4 text-lg tracking-tight text-[#5B402D] dark:text-[#D9C4A3] md:text-xl text-balance translate-y-0 animate-fade-in opacity-0 will-change-transform" style={{animationDelay: '400ms'}}>
         We craft premium barrel and pod saunas using sustainable materials
         <br className="hidden md:block" /> and traditional Finnish techniques for an authentic experience
         anywhere.
       </p>
       <Link href="#showcase">
-        <Button className="translate-y-[-1rem] animate-fade-in gap-1 rounded-lg text-[#E6D3AF] dark:text-[#1E1A18] opacity-0 ease-in-out [--animation-delay:600ms] mb-8 font-work-sans">
+        <Button className="translate-y-0 animate-fade-in gap-1 rounded-lg text-[#E6D3AF] dark:text-[#1E1A18] opacity-0 ease-in-out mb-8 font-work-sans will-change-transform" style={{animationDelay: '500ms'}}>
           <span>Explore Our Saunas </span>
           <ArrowRightIcon className="ml-1 size-4 transition-transform duration-300 ease-in-out group-hover:translate-x-1" />
         </Button>
diff --git a/components/ui/optimized-image.tsx b/components/ui/optimized-image.tsx
index 7a3c1b8..2068f83 100644
--- a/components/ui/optimized-image.tsx
+++ b/components/ui/optimized-image.tsx
@@ -30,7 +30,8 @@ export function OptimizedImage({
       src={imgSrc}
       alt={alt}
       onError={handleError}
-      unoptimized={true}
+      unoptimized={props.unoptimized ?? false}
+      loading={props.priority ? "eager" : "lazy"}
     />
   );
 }
diff --git a/components/ui/preload-images.tsx b/components/ui/preload-images.tsx
new file mode 100644
index 0000000..99b5e0a
--- /dev/null
+++ b/components/ui/preload-images.tsx
@@ -0,0 +1,18 @@
+"use client";
+
+import { useEffect } from "react";
+
+interface PreloadImagesProps {
+  images: string[];
+}
+
+export function PreloadImages({ images }: PreloadImagesProps) {
+  useEffect(() => {
+    images.forEach((src) => {
+      const img = new Image();
+      img.src = src;
+    });
+  }, [images]);
+
+  return null;
+}
diff --git a/tailwind.config.ts b/tailwind.config.ts
index e154627..1c01e99 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -92,8 +92,9 @@ const config = {
           },
         },
         "fade-in": {
-          from: { opacity: "0", transform: "translateY(-10px)" },
-          to: { opacity: "1", transform: "none" },
+          "0%": { opacity: "0", transform: "translateY(8px)", filter: "blur(0.5px)" },
+          "50%": { filter: "blur(0px)" },
+          "100%": { opacity: "1", transform: "translateY(0px)" },
         },
         "fade-up": {
           from: { opacity: "0", transform: "translateY(20px)" },
@@ -121,7 +122,7 @@ const config = {
         "accordion-up": "accordion-up 0.2s ease-out",
         "border-beam": "border-beam calc(var(--duration)*1s) infinite linear",
         "image-glow": "image-glow 4100ms 600ms ease-out forwards",
-        "fade-in": "fade-in 1000ms var(--animation-delay, 0ms) ease forwards",
+        "fade-in": "fade-in 800ms cubic-bezier(0.22, 1, 0.36, 1) forwards",
         "fade-up": "fade-up 1000ms var(--animation-delay, 0ms) ease forwards",
         shimmer: "shimmer 8s infinite",
         marquee: "marquee var(--duration) infinite linear",


Reviews:
user: login: sourcery-ai[bot]
body: Hey @Zchasse63 - I've reviewed your changes - here's some feedback:

**Overall Comments**:
- The changes to tailwind.config.ts seem to have modified the animation easing function - can you confirm this was intentional?
- Consider using a next/image placeholder to further enhance the loading experience.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟡 **General issues**: 1 issue found
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟢 **Complexity**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-18T23:05:15Z

Review comments:
diff_hunk: @@ -0,0 +1,55 @@
+"use client";
+
+import HeroSection from "@/components/landing/hero-section";
+import { useEffect, useState } from "react";
+import { Skeleton } from "@/components/ui/skeleton";
+import ShowcaseSection from "@/components/landing/showcase-section";
+import ServicesSection from "@/components/landing/services-section";
+import TestimonialsSection from "@/components/landing/testimonials-section";
+import CtaSection from "@/components/landing/cta-section";
+import FaqSection from "@/components/landing/faq-section";
+
+export default function Home() {
+  const [isLoading, setIsLoading] = useState(true);
+
+  useEffect(() => {
+    // Preload the hero image
+    const img = new Image();
+    img.src = "/images/showcase/IMG_7359.jpeg";
+    
+    img.onload = () => {
+      // Add a small delay to ensure smooth transition
+      setTimeout(() => {
+        setIsLoading(false);
+      }, 300);
+    };
path: app/page.tsx
user: login: sourcery-ai[bot]
body: **suggestion:** Clear the fallback timer when the hero image loads successfully.

Currently, if the image loads before the fallback timer of 2000ms, both timeouts will eventually trigger. Clearing the fallback timer in the image onload handler can help avoid redundant state updates.

```suggestion
    img.onload = () => {
      // Clear the fallback timer when the hero image loads successfully
      clearTimeout(timer);
      // Add a small delay to ensure smooth transition
      setTimeout(() => {
        setIsLoading(false);
      }, 300);
    };
```
created_at: 2025-04-18T23:05:15Z
author_association: NONE
diff_hunk: @@ -1,35 +1,29 @@
+"use client";
+
 import { Skeleton } from "@/components/ui/skeleton";
+import { cn } from "@/lib/utils";
+import { useEffect, useState } from "react";
 
 export default function Loading() {
+  const [isVisible, setIsVisible] = useState(true);
+
+  useEffect(() => {
+    // Hide the loading indicator after a short delay
+    const timer = setTimeout(() => {
+      setIsVisible(false);
+    }, 1000);
+
+    return () => clearTimeout(timer);
+  }, []);
+
+  if (!isVisible) return null;
path: app/loading.tsx
user: login: sourcery-ai[bot]
body: **suggestion (code-quality):** Use block braces for ifs, whiles, etc. ([`use-braces`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/use-braces))

```suggestion
  if (!isVisible) {
```

<br/><details><summary>Explanation</summary>It is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).
</details>
created_at: 2025-04-18T23:05:15Z
author_association: NONE

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-04-18T23:03:59Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide by Sourcery

This pull request focuses on improving the hero section's performance and visual appeal, especially on mobile devices. It introduces image optimizations, smoother animations, and a loading state with skeleton placeholders. The changes aim to eliminate glitches, reduce layout shifts, and enhance the overall user experience.

#### Updated class diagram for OptimizedImage component

```mermaid
classDiagram
  class OptimizedImage {
    -src: string
    -alt: string
    -fill: boolean
    -priority: boolean
    -sizes: string
    -quality: number
    -className: string
    -unoptimized: boolean
    +OptimizedImage(props)
    +handleError()
  }
  note for OptimizedImage "Added unoptimized and loading properties to the OptimizedImage component"
```

#### New class diagram for PreloadImages component

```mermaid
classDiagram
  class PreloadImages {
    -images: string[]
    +PreloadImages(props)
    +useEffect()
  }
  note for PreloadImages "Added PreloadImages component to preload images"
```

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Implemented a loading state with skeleton placeholders for the hero section to improve the initial loading experience. | <ul><li>Added a loading state to the Home component using `useState`.</li><li>Displayed skeleton placeholders while loading.</li><li>Preloaded the hero image using `new Image()`.</li><li>Added a delay before removing the loading state for a smoother transition.</li><li>Implemented a fallback timeout in case the image fails to load.</li><li>Created a new `PreloadImages` component to preload images.</li><li>Added a loading component with a spinner and text.</li></ul> | `app/loading.tsx`<br/>`app/page.tsx`<br/>`components/ui/preload-images.tsx` |
| Improved hero section animations and transitions for a smoother user experience. | <ul><li>Added `will-change-transform` to elements with animations to hint the browser for optimization.</li><li>Replaced CSS variable animation delays with inline style delays for better browser support.</li><li>Removed the negative `translate-y` that was causing layout shifts.</li><li>Updated the fade-in animation to be smoother with a better easing function.</li><li>Added a subtle blur effect during animation for smoother transitions.</li></ul> | `components/landing/hero-section.tsx`<br/>`tailwind.config.ts` |
| Optimized the background image in the hero section for better performance, especially on mobile devices. | <ul><li>Reduced image quality for the background image.</li><li>Added proper sizing for responsive images using the `sizes` prop.</li><li>Reduced the blur amount on the background image.</li><li>Used `transform-gpu` to force GPU acceleration for the background image.</li><li>Set `unoptimized` to `false` on the `OptimizedImage` component.</li><li>Added a loading property to the `OptimizedImage` component.</li></ul> | `components/landing/hero-section.tsx`<br/>`components/ui/optimized-image.tsx` |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!
- **Generate a plan of action for an issue:** Comment `@sourcery-ai plan` on
  an issue to generate a plan of action for it.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: NONE
user: login: netlify[bot]
created_at: 2025-04-18T23:04:15Z
body: ### <span aria-hidden="true">❌</span> Deploy Preview for *nomadsauna* failed. **[Why did it fail? →](https://app.netlify.com/sites/nomadsauna/deploys/6802db154ef0960008e3869a?deploy_diagnostics=true)**


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | b99ce9b8a627766359f45b0e146050be9d36073c |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/nomadsauna/deploys/6802db154ef0960008e3869a |
author_association: NONE