Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
346 changes: 222 additions & 124 deletions components/Hero/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,140 +1,238 @@
"use client";

import { useState } from "react";
import "atropos/css";
import Atropos from "atropos/react";
import Image from "next/image";
import space from "public/images/home/space.jpg";
import rocketman from "public/images/home/rocketman.png";
import moon from "public/images/home/moon.png";
import React, { useState, useEffect } from "react";

export default function Hero() {
const [rocketLoaded, setRocketLoaded] = useState(false);
const [moonLoaded, setMoonLoaded] = useState(false);
const [starsLoaded, setStarsLoaded] = useState(false);

const isReady = rocketLoaded && moonLoaded && starsLoaded;
const CoduLogo = ({ className }: { className?: string }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="463"
height="150"
fill="none"
viewBox="0 0 463 150"
className={className}
aria-label="Codú logo"
role="img"
>
<path
fill="#fff"
fillRule="evenodd"
d="M187 150a50 50 0 1 0 0-100 50 50 0 0 0 0 100Zm0-18a32 32 0 1 0 0-64 32 32 0 0 0 0 64Z"
clipRule="evenodd"
/>
<path
fill="#fff"
d="M415.75 6.36a9 9 0 0 1 12.73 12.73l-18.39 18.39a9 9 0 0 1-12.73-12.73l18.39-18.39Z"
/>
<path
fill="#fff"
fillRule="evenodd"
d="M341 0a9 9 0 0 0-9 9v52.58A50 50 0 1 0 350 100V9a9 9 0 0 0-9-9Zm-9 100a32 32 0 1 0-64 0 32 32 0 0 0 64 0Z"
clipRule="evenodd"
/>
<path
fill="#fff"
d="M121.46 121.88c3.5 3.53 3.5 9.28-.42 12.33a75 75 0 1 1-.05-118.45c3.93 3.05 3.93 8.8.44 12.33-3.5 3.53-9.17 3.5-13.2.6a57 57 0 1 0 .03 92.6c4.04-2.9 9.7-2.94 13.2.59ZM363 59a9 9 0 1 1 18 0v41a32 32 0 0 0 64 0V59a9 9 0 1 1 18 0v82a9 9 0 1 1-18 0v-2.58A50 50 0 0 1 363 100V59Z"
/>
</svg>
);
};

const handleScroll = (id: string) => {
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({ behavior: "smooth" });
const NightSky = () => {
const styles = `
@keyframes twinkle {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.7; }
}
@keyframes gentleMove1 {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(3px, 2px); }
}
@keyframes gentleMove2 {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(-2px, 4px); }
}
@keyframes gentleMove3 {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(4px, -3px); }
}
@keyframes shootingStar {
0% { transform: translate(0, 0); opacity: 1; }
100% { transform: translate(300px, 300px); opacity: 0; }
}
.twinkle { animation: twinkle 4s ease-in-out infinite; }
.gentle-move1 { animation: gentleMove1 25s ease-in-out infinite; }
.gentle-move2 { animation: gentleMove2 30s ease-in-out infinite; }
.gentle-move3 { animation: gentleMove3 35s ease-in-out infinite; }
.shooting-star {
position: absolute;
width: 4px;
height: 4px;
background: white;
border-radius: 50%;
top: -4px;
left: -4px;
animation: shootingStar 1.5s linear;
animation-iteration-count: 1;
}
`;
Comment on lines +42 to +78
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider Moving Inline Styles to External CSS

The styles variable contains CSS defined as a template literal and is injected into a <style> tag within the component. While this approach works, it can make the code harder to maintain and can lead to duplication if multiple components need similar styles.

Consider moving these styles to an external CSS file, a CSS module, or using a CSS-in-JS solution like styled-components or Emotion. This will improve code organization, enable reuse, and make it easier to manage styles across the project.


useEffect(() => {
const createShootingStar = () => {
const star = document.createElement("div");
star.className = "shooting-star";
document.querySelector(".night-sky-container")!.appendChild(star);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure Safe Access to DOM Elements

At line 84, the code uses document.querySelector(".night-sky-container")! with a non-null assertion operator. If the element with the class .night-sky-container is not found, this will throw an error at runtime.

Consider adding a null check to ensure that the element exists before attempting to append the shooting star:

- document.querySelector(".night-sky-container")!.appendChild(star);
+ const container = document.querySelector(".night-sky-container");
+ if (container) {
+   container.appendChild(star);
+ }

Alternatively, refactoring to avoid direct DOM manipulation (as suggested earlier) will also resolve this issue.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
document.querySelector(".night-sky-container")!.appendChild(star);
const container = document.querySelector(".night-sky-container");
if (container) {
container.appendChild(star);
}


star.addEventListener("animationend", () => {
star.remove();
});
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Avoid Direct DOM Manipulation in React Components

The NightSky component directly manipulates the DOM using document.createElement and document.querySelector to create and append shooting stars. This approach bypasses React's virtual DOM and can lead to unexpected behavior or conflicts.

Consider refactoring this logic to utilize React's state and rendering mechanisms. By managing shooting stars through React state, you maintain the benefits of React's declarative paradigm and avoid potential issues with direct DOM manipulation.

Here's an example of how you might refactor the code:

const NightSky = () => {
+  const [shootingStars, setShootingStars] = useState<number[]>([]);

   useEffect(() => {
     const createShootingStar = () => {
-      const star = document.createElement("div");
-      star.className = "shooting-star";
-      document.querySelector(".night-sky-container")!.appendChild(star);
-
-      star.addEventListener("animationend", () => {
-        star.remove();
-      });
+      const id = Date.now();
+      setShootingStars((prev) => [...prev, id]);
+      setTimeout(() => {
+        setShootingStars((prev) => prev.filter((starId) => starId !== id));
+      }, 1500); // Match the duration of the shooting star animation
     };

     const interval = setInterval(() => {
       if (Math.random() < 0.3) {
         // 30% chance every 3 seconds
         createShootingStar();
       }
     }, 3000);

     return () => clearInterval(interval);
   }, []);

   return (
     <div className="night-sky-container relative h-full w-full">
       <style>{styles}</style>
       <svg
         xmlns="http://www.w3.org/2000/svg"
         viewBox="0 0 100 100"
         preserveAspectRatio="xMidYMid slice"
         style={{ background: "transparent", width: "100%", height: "100%" }}
         className="absolute inset-0"
       >
         {generateStars()}
         {generateAnimatedStars()}
       </svg>
+      {shootingStars.map((id) => (
+        <div key={id} className="shooting-star" />
+      ))}
     </div>
   );
 };

This refactor keeps all DOM manipulations within React's virtual DOM, promoting better maintainability and consistency.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const star = document.createElement("div");
star.className = "shooting-star";
document.querySelector(".night-sky-container")!.appendChild(star);
star.addEventListener("animationend", () => {
star.remove();
});
};
const [shootingStars, setShootingStars] = useState<number[]>([]);
useEffect(() => {
const createShootingStar = () => {
const id = Date.now();
setShootingStars((prev) => [...prev, id]);
setTimeout(() => {
setShootingStars((prev) => prev.filter((starId) => starId !== id));
}, 1500); // Match the duration of the shooting star animation
};
const interval = setInterval(() => {
if (Math.random() < 0.3) {
// 30% chance every 3 seconds
createShootingStar();
}
}, 3000);
return () => clearInterval(interval);
}, []);
return (
<div className="night-sky-container relative h-full w-full">
<style>{styles}</style>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
preserveAspectRatio="xMidYMid slice"
style={{ background: "transparent", width: "100%", height: "100%" }}
className="absolute inset-0"
>
{generateStars()}
{generateAnimatedStars()}
</svg>
{shootingStars.map((id) => (
<div key={id} className="shooting-star" />
))}
</div>
);


const interval = setInterval(() => {
if (Math.random() < 0.3) {
// 30% chance every 3 seconds
createShootingStar();
}
}, 3000);

return () => clearInterval(interval);
}, []);

const starPositions = [
{ x: 10, y: 15 },
{ x: 25, y: 30 },
{ x: 40, y: 10 },
{ x: 60, y: 40 },
{ x: 75, y: 20 },
{ x: 5, y: 50 },
{ x: 30, y: 70 },
{ x: 50, y: 85 },
{ x: 80, y: 60 },
{ x: 90, y: 35 },
{ x: 15, y: 25 },
{ x: 35, y: 45 },
{ x: 55, y: 15 },
{ x: 70, y: 55 },
{ x: 85, y: 30 },
{ x: 20, y: 65 },
{ x: 45, y: 80 },
{ x: 65, y: 5 },
{ x: 95, y: 45 },
{ x: 8, y: 90 },
{ x: 28, y: 18 },
{ x: 48, y: 38 },
{ x: 68, y: 78 },
{ x: 88, y: 22 },
{ x: 12, y: 72 },
{ x: 32, y: 92 },
{ x: 52, y: 62 },
{ x: 72, y: 42 },
{ x: 92, y: 82 },
{ x: 18, y: 52 },
{ x: 38, y: 32 },
{ x: 58, y: 72 },
{ x: 78, y: 12 },
{ x: 98, y: 58 },
{ x: 3, y: 83 },
{ x: 23, y: 3 },
{ x: 43, y: 93 },
{ x: 63, y: 33 },
{ x: 83, y: 73 },
{ x: 7, y: 37 },
];

// Simple pseudo-random number generator
const seededRandom = (function () {
const seed = 12345; // You can change this seed to get a different, but consistent, pattern
let state = seed;
return function () {
state = (state * 1664525 + 1013904223) % 4294967296;
return state / 4294967296;
};
})();
Comment on lines +143 to +150
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Move Seeded Random Function Outside Component to Avoid Re-initialization

The seededRandom function is defined inside the NightSky component, which means it will be re-initialized on every render. This can lead to inconsistent behavior or unnecessary computations.

Consider moving the seededRandom function outside of the component to ensure that the random sequence remains consistent across renders:

- const NightSky = () => {
-   const seededRandom = (function () {
+ // Simple pseudo-random number generator
+ const seededRandom = (function () {
    const seed = 12345; // You can change this seed to get a different, but consistent, pattern
    let state = seed;
    return function () {
      state = (state * 1664525 + 1013904223) % 4294967296;
      return state / 4294967296;
    };
  })();

+ const NightSky = () => {
    // ... rest of the component

This change ensures that the random number generator maintains its state between renders, leading to consistent star patterns.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const seededRandom = (function () {
const seed = 12345; // You can change this seed to get a different, but consistent, pattern
let state = seed;
return function () {
state = (state * 1664525 + 1013904223) % 4294967296;
return state / 4294967296;
};
})();
// Simple pseudo-random number generator
const seededRandom = (function () {
const seed = 12345; // You can change this seed to get a different, but consistent, pattern
let state = seed;
return function () {
state = (state * 1664525 + 1013904223) % 4294967296;
return state / 4294967296;
};
})();
const NightSky = () => {
// ... rest of the component


const generateStars = () => {
return starPositions.map((pos, i) => (
<circle
key={i}
cx={pos.x}
cy={pos.y}
r={seededRandom() * 0.15 + 0.05}
fill="white"
opacity={seededRandom() * 0.5 + 0.3}
className={seededRandom() > 0.7 ? "twinkle" : ""}
/>
));
};

const generateAnimatedStars = () => {
const animatedStarPositions = [
{ x: 20, y: 20 },
{ x: 45, y: 55 },
{ x: 70, y: 30 },
{ x: 85, y: 75 },
{ x: 15, y: 80 },
{ x: 55, y: 25 },
{ x: 35, y: 65 },
{ x: 65, y: 50 },
{ x: 10, y: 40 },
{ x: 90, y: 10 },
];
return animatedStarPositions.map((pos, i) => (
<circle
key={`animated-${i}`}
cx={pos.x}
cy={pos.y}
r={seededRandom() * 0.2 + 0.1}
fill="white"
opacity={seededRandom() * 0.5 + 0.5}
className={`gentle-move${(i % 3) + 1}`}
/>
));
};

return (
<main className="relative">
<Atropos
rotateXMax={0.4}
rotateYMax={0.4}
stretchX={1}
stretchY={0.2}
stretchZ={0.3}
highlight={false}
className="relative h-[calc(100vh_-_100px)] max-h-[calc(100svh_-_100px)] w-full overflow-hidden sm:h-[900px] [&>span.atropos-scale]:pointer-events-none [&_span.atropos-rotate]:pointer-events-auto"
<div className="night-sky-container relative h-full w-full">
<style>{styles}</style>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
preserveAspectRatio="xMidYMid slice"
style={{ background: "transparent", width: "100%", height: "100%" }}
className="absolute inset-0"
>
<Image
placeholder="blur"
className="absolute -z-10 h-full w-full object-cover"
src={space}
data-atropos-offset="-2"
alt="Realistic space sky which is black with stars scattered across."
onLoad={() => {
setStarsLoaded(true);
}}
/>

<div className="absolute -bottom-28 left-0 right-0 -z-10 mx-auto max-h-[480px] max-w-[480px] sm:-bottom-60 sm:max-h-[800px] sm:max-w-[600px] md:-bottom-96 md:max-h-[800px] md:max-w-[800px]">
<div className="relative mx-auto brightness-75">
<Image
src={moon}
data-atropos-offset="1"
alt="Photograph of the moon"
sizes="100vw"
style={{
width: "100%",
height: "auto",
}}
onLoad={() => {
setMoonLoaded(true);
}}
/>
</div>
<div className="absolute right-0 top-10 h-[240px] w-[240px] md:-right-28 md:h-[350px] md:w-[350px]">
<Image
height={350}
width={350}
src={rocketman}
data-atropos-offset="8"
alt="3D claymation style model of a astronaut on a rocket"
sizes="100vw"
style={{
width: "100%",
height: "auto",
}}
onLoad={() => {
setRocketLoaded(true);
}}
/>
</div>
</div>
{generateStars()}
{generateAnimatedStars()}
</svg>
</div>
);
};

<div
data-atropos-offset="0"
className="flex h-full flex-col justify-center"
>
<Image
width={340}
height={200}
src="/images/codu.svg"
alt="Codú logo"
className={`mx-auto w-[240px] object-contain transition duration-500 sm:w-[340px] ${
isReady ? "opacity-100" : "opacity-0"
}`}
/>
export default function Hero() {
return (
<div className="relative w-full bg-neutral-950">
<div className="absolute inset-0 bg-gradient-to-b from-black via-transparent to-black"></div>
<section
className="relative mx-auto h-[500px] max-w-5xl overflow-hidden rounded sm:h-[600px]"
aria-labelledby="hero-heading"
>
<NightSky />
<div className="absolute left-1/2 top-1/2 z-10 w-full max-w-3xl -translate-x-1/2 -translate-y-1/2 transform space-y-4 px-4 text-center sm:px-6 lg:px-8">
<CoduLogo className="mx-auto mb-8 h-16 sm:h-20" aria-hidden="true" />
<h1
className={`mt-8 text-center text-5xl font-extrabold tracking-tight text-white drop-shadow-2xl duration-500 sm:text-7xl ${
isReady ? "opacity-100" : "opacity-0"
}`}
id="hero-heading"
className="text-2xl font-semibold text-white sm:text-4xl md:text-4xl"
>
A{" "}
<span className="bg-gradient-to-r from-orange-400 to-pink-600 bg-clip-text text-transparent">
space
</span>{" "}
for coders
The <span className="font-extrabold text-pink-600">free</span> web
developer community
</h1>
<div className="mt-12 flex justify-center">
<button
aria-label="Scroll to call to action"
className="focus-style-rounded animate-bounce rounded-full border-2 bg-neutral-900 bg-opacity-60 p-4"
onClick={() => handleScroll("cta")}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="#ffffff"
className="h-8 w-8"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M19.5 5.25l-7.5 7.5-7.5-7.5m15 6l-7.5 7.5-7.5-7.5"
/>
</svg>
</button>
</div>
<p className="mx-auto max-w-2xl text-sm text-neutral-300 sm:text-base md:text-lg">
{`Codú's community offers hundreds of tutorials, an online community, and answers
questions on a wide range of web development topics. Sign up for a free account today
and join the community.`}
</p>
</div>
<div
className="absolute bottom-0 h-20 w-full bg-gradient-to-t from-black"
data-atropos-offset="-2"
/>
<div
className="absolute top-0 h-20 w-full bg-gradient-to-b from-black"
data-atropos-offset="-2"
/>
</Atropos>
</main>
</section>
</div>
);
}
9 changes: 0 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@
"@trpc/react-query": "^10.45.1",
"@trpc/server": "^10.45.1",
"algoliasearch": "^5.7.0",
"atropos": "^2.0.2",
"clsx": "^2.1.1",
"copy-to-clipboard": "^3.3.3",
"drizzle-orm": "^0.34.1",
Expand Down
Binary file removed public/images/home/moon.png
Binary file not shown.
Binary file removed public/images/home/rocketman.png
Binary file not shown.
Binary file removed public/images/home/space.jpg
Binary file not shown.