Thanks to visit codestin.com
Credit goes to github.com

Skip to content

added threejs globe#13

Merged
azkriven16 merged 1 commit intomainfrom
feature/hero-3d
Nov 7, 2025
Merged

added threejs globe#13
azkriven16 merged 1 commit intomainfrom
feature/hero-3d

Conversation

@azkriven16
Copy link
Owner

@azkriven16 azkriven16 commented Nov 7, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Launched interactive 3D globe visualization featured on portfolio homepage
    • Added scroll-triggered animations for engaging, dynamic content reveals
    • Introduced animated text effects throughout the site interface
    • Reorganized blog posts into a dedicated section for easier access
  • UI & Components

    • Updated navigation bar styling and improved layout responsiveness
    • Implemented new button component system with multiple design variants

@korbit-ai
Copy link

korbit-ai bot commented Nov 7, 2025

You've used up your 5 PR reviews for this month under the Korbit Starter Plan. You'll get 5 more reviews on November 11th, 2025 or you can upgrade to Pro for unlimited PR reviews and enhanced features in your Korbit Console.

@coderabbitai
Copy link

coderabbitai bot commented Nov 7, 2025

Walkthrough

This PR restructures the app layout by migrating the blog page from app/(pages)/page.tsx to app/(portfolio)/blog/page.tsx, introduces a new portfolio landing page with a 3D globe visualization, adds several new UI components (Button, InView animations, TextEffect, Globe), updates the navbar with animation-aware wrappers, and adds dependencies for 3D graphics and animation frameworks.

Changes

Cohort / File(s) Change Summary
App Structure Reorganization
app/(pages)/page.tsx, app/(portfolio)/blog/page.tsx, app/(portfolio)/page.tsx
Removed old posts index page; added new blog index page under portfolio route with same Sanity GROQ query and post listing; added new portfolio landing page rendering Globe component and text content.
3D Globe Components
components/globe.tsx, components/ui/globe.tsx
Added Globe wrapper component with dynamic loading; added full World globe visualization using React Three Fiber, three-globe, and three.js with configurable colors, arcs, rings, camera, and lighting.
Animation & Viewport Components
components/ui/in-view.tsx, components/ui/text-effect.tsx
Added InView component triggering animations based on viewport visibility; added TextEffect component with preset-based text animations (blur, fade, scale, slide) supporting per-segment (line/word/char) rendering.
UI Component System
components/ui/button.tsx, components/ui/card-nav.tsx, components/shared/navbar.tsx
Added Button component with variant and size system via class-variance-authority; updated card-nav to use Button component for auth actions; wrapped navbar CardNav with InView for animation triggering.
Dependencies
package.json
Added runtime dependencies: @radix-ui/react-slot, @react-three/drei, @react-three/fiber, motion, three, three-globe; added @types/three to devDependencies.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • TextEffect component (components/ui/text-effect.tsx): Dense logic with multiple animation presets, segmentation strategies, variant construction, and lifecycle management requiring careful verification of animation timing and text-splitting logic.
  • Globe visualization (components/ui/globe.tsx): 3D graphics integration with Three.js, camera setup, ring animation loops, and data-driven arc/point rendering; requires understanding of three-fiber patterns and WebGL configuration.
  • InView animation trigger (components/ui/in-view.tsx): Viewport detection state management with once-flag persistence and animation state transitions; verify intersection observer integration.
  • Button variant system (components/ui/button.tsx): Confirm all variant/size combinations and slot-based rendering work correctly.
  • Navbar integration (components/shared/navbar.tsx): Ensure InView wrapping doesn't break existing header behavior and layout shift.

Possibly related PRs

  • reset #5: Both PRs perform app structure migration (old Vite-based app to Next.js app directory with overlapping page and component changes including globe, navbar, and new UI components).
  • hero section #6: Both PRs add/modify overlapping UI component infrastructure (Button variants, shared UI modules, and client-side provider patterns).
  • added navbar #12: Both PRs modify navbar-related files and touch app structure pages, indicating coordinated UI and navigation changes.

Poem

🌍 A globe spins and glows in digital night,
InView components dance in viewport light,
TextEffect whispers as letters arise,
Buttons and cards paint a new portfolio prize.
~CodeRabbit's cartography of change

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'added threejs globe' accurately summarizes the main addition of a Three.js-based globe visualization component, which is the primary change across multiple files in this pull request.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/hero-3d

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
portfolio 111aa1b Commit Preview URL

Branch Preview URL
Nov 07 2025, 12:41 PM

@azkriven16 azkriven16 merged commit 85d7a1a into main Nov 7, 2025
3 of 4 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (7)
components/ui/button.tsx (1)

39-58: Forward refs through Button

Right now Button is a plain function component, so any consumer passing a ref (for focus, measurement, etc.) gets null, unlike a native <button>. Wrap it in React.forwardRef and forward the ref through to preserve accessibility ergonomics.

-function Button({
-  className,
-  variant,
-  size,
-  asChild = false,
-  ...props
-}: React.ComponentProps<"button"> &
-  VariantProps<typeof buttonVariants> & {
-    asChild?: boolean;
-  }) {
-  const Comp = asChild ? Slot : "button";
-
-  return (
-    <Comp
-      data-slot="button"
-      className={cn(buttonVariants({ variant, size, className }))}
-      {...props}
-    />
-  );
-}
-
-export { Button, buttonVariants };
+const Button = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentPropsWithoutRef<"button"> &
+    VariantProps<typeof buttonVariants> & {
+      asChild?: boolean;
+    }
+>(({ className, variant, size, asChild = false, ...props }, ref) => {
+  const Comp = asChild ? Slot : "button";
+
+  return (
+    <Comp
+      ref={ref}
+      data-slot="button"
+      className={cn(buttonVariants({ variant, size, className }))}
+      {...props}
+    />
+  );
+});
+Button.displayName = "Button";
+
+export { Button, buttonVariants };
components/globe.tsx (1)

23-23: Clean up redundant Tailwind classes.

The className has a double space and includes both dark:invert and invert-0, which is redundant since the default is no invert.

Apply this diff:

-    <div className="relative w-[400px] max-w-5xl mx-auto aspect-square  dark:invert invert-0 saturate-100 brightness-100">
+    <div className="relative w-[400px] max-w-5xl mx-auto aspect-square dark:invert saturate-100 brightness-100">
app/(portfolio)/page.tsx (3)

2-2: Remove unused import.

TextEffect is imported but never used in this component.

Apply this diff:

 import { Globe } from "@/components/globe";
-import { TextEffect } from "@/components/ui/text-effect";

4-4: Remove unnecessary async keyword.

The function is marked async but contains no await statements or asynchronous operations.

Apply this diff:

-export default async function IndexPage() {
+export default function IndexPage() {

9-12: Replace placeholder content.

The lorem ipsum text should be replaced with actual portfolio content.

Would you like me to open an issue to track replacing this placeholder content?

components/ui/globe.tsx (2)

147-150: Optimize duplicate point filtering.

The current implementation uses findIndex inside filter, resulting in O(n²) complexity. For the current dataset (~80 points), this is acceptable, but a Set-based approach would be more efficient and scale better.

Apply this diff for O(n) complexity:

-    const uniquePoints = points.filter(
-      (v, i, a) =>
-        a.findIndex((v2) => v2.lat === v.lat && v2.lng === v.lng) === i
-    );
+    const seen = new Set<string>();
+    const uniquePoints = points.filter((v) => {
+      const key = `${v.lat},${v.lng}`;
+      if (seen.has(key)) return false;
+      seen.add(key);
+      return true;
+    });

227-227: Consider removing non-null assertion.

While the guard on Line 210 ensures globeRef.current exists, using the non-null assertion operator is slightly risky. A more defensive approach would check explicitly.

Apply this diff:

-      globeRef.current!.ringsData(ringsData);
+      if (globeRef.current) {
+        globeRef.current.ringsData(ringsData);
+      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f618c98 and 111aa1b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • app/(pages)/page.tsx (0 hunks)
  • app/(portfolio)/blog/page.tsx (1 hunks)
  • app/(portfolio)/page.tsx (1 hunks)
  • components/globe.tsx (1 hunks)
  • components/shared/navbar.tsx (1 hunks)
  • components/ui/button.tsx (1 hunks)
  • components/ui/card-nav.tsx (3 hunks)
  • components/ui/globe.tsx (1 hunks)
  • components/ui/in-view.tsx (1 hunks)
  • components/ui/text-effect.tsx (1 hunks)
  • package.json (1 hunks)
💤 Files with no reviewable changes (1)
  • app/(pages)/page.tsx
🧰 Additional context used
🧬 Code graph analysis (8)
app/(portfolio)/blog/page.tsx (3)
app/(portfolio)/page.tsx (1)
  • IndexPage (4-16)
sanity/client.ts (1)
  • client (3-8)
sanity/sanity.types.ts (1)
  • Post (16-55)
components/ui/card-nav.tsx (1)
components/shared/mode-toggle.tsx (1)
  • ModeToggle (15-114)
components/ui/button.tsx (1)
lib/utils.ts (1)
  • cn (4-6)
components/globe.tsx (1)
components/ui/globe.tsx (2)
  • World (254-291)
  • Globe (63-234)
app/(portfolio)/page.tsx (2)
components/globe.tsx (1)
  • Globe (21-27)
components/ui/globe.tsx (1)
  • Globe (63-234)
components/ui/globe.tsx (1)
components/globe.tsx (1)
  • Globe (21-27)
components/ui/text-effect.tsx (1)
lib/utils.ts (1)
  • cn (4-6)
components/shared/navbar.tsx (1)
components/ui/in-view.tsx (1)
  • InView (28-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: deploy
  • GitHub Check: Workers Builds: portfolio
🔇 Additional comments (9)
components/globe.tsx (2)

5-11: LGTM!

The Loader component provides a clean fallback UI during the dynamic import phase.


13-19: LGTM!

The dynamic import with ssr: false is appropriate for a WebGL-based component that requires browser APIs.

components/ui/globe.tsx (7)

1-20: LGTM!

The Three.js module setup, type augmentation, and constant definitions are well-structured.


22-58: LGTM!

The type definitions for Position, GlobeConfig, and WorldProps are comprehensive and correctly structured.


86-92: LGTM!

The initialization effect correctly creates the globe instance once on mount with appropriate guards.


95-115: Material update logic is correct.

The effect properly updates the globe material when properties change. The dependency array will work optimally once defaultProps is memoized as suggested above.


239-249: LGTM!

The WebGL renderer configuration properly updates when size changes, with correct dependencies.


254-291: LGTM!

The World wrapper correctly sets up the Three.js Canvas with appropriate scene, camera, lighting, and controls configuration. The OrbitControls settings provide good UX with rotation enabled but pan/zoom disabled.


296-317: LGTM!

The utility functions hexToRgb and genRandomNumbers are well-implemented. The hex conversion handles both shorthand and full notation, and the random number generator ensures uniqueness.

Comment on lines +21 to +23
<h2 className="text-xl font-semibold">{post.title}</h2>
<p>{new Date(post.publishedAt ?? "").toLocaleDateString()}</p>
</Link>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Handle missing publishedAt to prevent runtime crash

Line 22 currently does new Date(post.publishedAt ?? ""), which throws RangeError: Invalid time value whenever a post lacks a publishedAt timestamp (e.g., drafts). That tanks the whole blog index render. Guard the conversion and fall back to a safe date before formatting.

-const POSTS_QUERY = `*[_type == "post" && defined(slug.current)]|order(publishedAt desc)[0...12]{_id, title, slug, publishedAt}`;
+const POSTS_QUERY = `*[_type == "post" && defined(slug.current)]|order(publishedAt desc)[0...12]{_id, title, slug, publishedAt, _createdAt}`;
...
-                  <p>{new Date(post.publishedAt ?? "").toLocaleDateString()}</p>
+                  <p>
+                    {(post.publishedAt ?? post._createdAt)
+                      ? new Date(post.publishedAt ?? post._createdAt).toLocaleDateString()
+                      : "Unpublished"}
+                  </p>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/(portfolio)/blog/page.tsx around lines 21–23, the code calls new
Date(post.publishedAt ?? "") which throws a RangeError when publishedAt is
missing; change this to guard the conversion and render a safe fallback: check
if post.publishedAt is present and a valid date before calling new
Date(...).toLocaleDateString(), otherwise render a safe placeholder (e.g.,
"Unpublished" or an explicit fallback date string like "—") so rendering never
attempts to format an invalid date.

arcLength: 0.9,
rings: 1,
maxRings: 3,
initialPosition: { lat: 22.3193, lng: 114.1694 },
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Unused configuration property.

The initialPosition property is defined but not consumed by the World component. The globe always starts at its default orientation.

Run the following script to verify that initialPosition is not used:

If confirmed unused, either remove this property or implement initial camera positioning in the World component.


🏁 Script executed:

#!/bin/bash
# Search for usage of initialPosition in globe-related files
rg -n 'initialPosition' --type=ts --type=tsx -C3

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for initialPosition usage - corrected syntax
rg -n 'initialPosition' --type=ts -C3

Length of output: 669


🏁 Script executed:

#!/bin/bash
# Search for initialPosition being accessed or used (not just defined)
rg -n '\.initialPosition|initialPosition\s*[,}]|initialPosition\s*:' --type=ts -C2

Length of output: 242


Remove unused initialPosition property or implement camera positioning.

The initialPosition property is defined in globeConfig but never accessed. Search confirms no usage of .initialPosition anywhere in the codebase. Either remove this unused configuration or implement initial camera positioning in the World component to consume it.

🤖 Prompt for AI Agents
components/globe.tsx around line 48: the globeConfig includes initialPosition: {
lat: 22.3193, lng: 114.1694 } which is unused; either remove this property from
the config or wire it into the World component to position the camera on mount.
If removing: delete the initialPosition key from globeConfig. If implementing:
read globeConfig.initialPosition in World, convert lat/lng to the globe/camera
coordinate system used by the globe library, and set the camera position (or
call the globe API to point/animate the camera) during the component mount or
initialization so the globe starts focused on that lat/lng. Ensure removal or
usage is reflected in types/interfaces to avoid unused property lint warnings.

endLat: -22.9068,
endLng: -43.1729,
arcAlt: 0.1,
color: colors[Math.floor(Math.random() * (colors.length - 1))],
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Off-by-one error in color selection.

The expression Math.floor(Math.random() * (colors.length - 1)) will never select the last color in the array. With colors.length = 3, this produces only indices 0 or 1, meaning "#bfdbfe" is never used.

Apply this diff to fix the color selection:

-    color: colors[Math.floor(Math.random() * (colors.length - 1))],
+    color: colors[Math.floor(Math.random() * colors.length)],

This change should be applied to all arc definitions throughout the sampleArcs array.

Also applies to: 73-73, 82-82, 91-91, 100-100, 109-109, 118-118, 127-127, 136-136, 145-145, 154-154, 163-163, 172-172, 181-181, 190-190, 199-199, 208-208, 217-217, 226-226, 235-235, 244-244, 253-253, 262-262, 271-271, 280-280, 289-289, 298-298, 307-307, 316-316, 325-325, 334-334, 343-343, 352-352, 361-361, 370-370, 379-379, 388-388, 397-397, 406-406, 415-415

🤖 Prompt for AI Agents
In components/globe.tsx around line 64 (and similarly at lines
73,82,91,100,109,118,127,136,145,154,163,172,181,190,199,208,217,226,235,244,253,262,271,280,289,298,307,316,325,334,343,352,361,370,379,388,397,406,415)
the random color index uses Math.floor(Math.random() * (colors.length - 1))
which never selects the last color; replace that expression with
Math.floor(Math.random() * colors.length) for each arc definition so the full
range 0..colors.length-1 can be selected, updating every occurrence listed.

Comment on lines +68 to +83
const defaultProps = {
pointSize: 1,
atmosphereColor: "#ffffff",
showAtmosphere: true,
atmosphereAltitude: 0.1,
polygonColor: "rgba(255,255,255,0.7)",
globeColor: "#1d072e",
emissive: "#000000",
emissiveIntensity: 0.1,
shininess: 0.9,
arcTime: 2000,
arcLength: 0.9,
rings: 1,
maxRings: 3,
...globeConfig,
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Memoize defaultProps to prevent unnecessary re-renders.

The defaultProps object is recreated on every render, causing the useEffect hooks that depend on its properties to re-run unnecessarily. This impacts performance, especially for the material updates and data visualization effects.

Apply this diff to memoize the configuration:

+  const defaultProps = useMemo(() => ({
-  const defaultProps = {
     pointSize: 1,
     atmosphereColor: "#ffffff",
     showAtmosphere: true,
     atmosphereAltitude: 0.1,
     polygonColor: "rgba(255,255,255,0.7)",
     globeColor: "#1d072e",
     emissive: "#000000",
     emissiveIntensity: 0.1,
     shininess: 0.9,
     arcTime: 2000,
     arcLength: 0.9,
     rings: 1,
     maxRings: 3,
     ...globeConfig,
-  };
+  }), [globeConfig]);

Don't forget to add the import:

-import { useEffect, useRef, useState } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";
📝 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 defaultProps = {
pointSize: 1,
atmosphereColor: "#ffffff",
showAtmosphere: true,
atmosphereAltitude: 0.1,
polygonColor: "rgba(255,255,255,0.7)",
globeColor: "#1d072e",
emissive: "#000000",
emissiveIntensity: 0.1,
shininess: 0.9,
arcTime: 2000,
arcLength: 0.9,
rings: 1,
maxRings: 3,
...globeConfig,
};
const defaultProps = useMemo(() => ({
pointSize: 1,
atmosphereColor: "#ffffff",
showAtmosphere: true,
atmosphereAltitude: 0.1,
polygonColor: "rgba(255,255,255,0.7)",
globeColor: "#1d072e",
emissive: "#000000",
emissiveIntensity: 0.1,
shininess: 0.9,
arcTime: 2000,
arcLength: 0.9,
rings: 1,
maxRings: 3,
...globeConfig,
}), [globeConfig]);
🤖 Prompt for AI Agents
In components/ui/globe.tsx around lines 68 to 83, defaultProps is recreated on
every render causing dependent useEffect hooks to re-run; wrap the defaultProps
construction in React.useMemo (e.g. const defaultProps = useMemo(() => ({
...defaults, ...globeConfig }), [globeConfig])) and update any useEffect
dependency arrays to depend on the memoized defaultProps or specific properties
instead of reconstructing values; also add the import for useMemo from react at
the top of the file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant