Skip to content

feat: redesign homepage with parallax expedition experience - #100

Open
dekanbro wants to merge 14 commits into
mainfrom
feat/venture-beyond-redesign
Open

feat: redesign homepage with parallax expedition experience#100
dekanbro wants to merge 14 commits into
mainfrom
feat/venture-beyond-redesign

Conversation

@dekanbro

@dekanbro dekanbro commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

concept off louchi ideas

Summary by CodeRabbit

  • New Features
    • Introduced a redesigned home experience with responsive navigation, hero artwork, mission content, contact portal, and footer.
    • Added interactive animations, parallax effects, horizontal navigation, image-based content, and a field-notes carousel.
    • Added responsive layouts for mobile and desktop, including reduced-motion support.
    • Added portal interactions for contact navigation, external entry, dismissal, and animated transitions.
  • Improvements
    • Enhanced metadata for richer sharing previews and updated organization information.
    • Improved typography consistency by using bundled local fonts.

@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
website Ready Ready Preview Aug 11, 2026 4:04pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The homepage now renders a client-side HomeExperience component. It adds animated interactions, responsive sections, themed styling, reduced-motion support, contact portal behavior, page metadata, updated organization structured data, and local font loading.

Changes

Homepage experience

Layer / File(s) Summary
Homepage content and interaction foundation
src/components/HomeExperience.tsx
Adds navigation, hero artwork, guild content, disciplines, field notes, creed, contact, and footer sections. It also adds pointer, wheel-pan, parallax, portal, and navigation state handling.
Contact portal interaction
src/components/HomeExperience.tsx, src/components/HomeExperience.module.css, src/components/HireUs.tsx
Adds portal transitions, canvas particles, body scroll locking, Escape and click dismissal, reduced-motion handling, and the hire-us-wizard class.
Responsive visual styling
src/components/HomeExperience.module.css
Adds the visual theme, animations, layouts, portal states, responsive breakpoints, and reduced-motion overrides.
Page integration, metadata, and assets
src/app/page.tsx, src/lib/fonts.ts, package.json
Replaces the manually composed homepage with HomeExperience, updates metadata and organization structured data, uses local variable fonts, and changes the Next.js dependency to a compatible semver range.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant HomeExperience
  participant PortalOverlay
  participant Browser
  Visitor->>HomeExperience: Select contact CTA
  HomeExperience->>PortalOverlay: Open portal
  PortalOverlay->>Browser: Lock scrolling and start portal effects
  Visitor->>PortalOverlay: Close portal or press Escape
  PortalOverlay->>HomeExperience: Complete dismissal
  HomeExperience->>Browser: Restore scrolling
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the homepage redesign and its parallax experience.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/venture-beyond-redesign

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/components/HomeExperience.module.css

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/components/HomeExperience.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

src/lib/fonts.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/HomeExperience.module.css`:
- Line 9: Fix the Stylelint violations in HomeExperience.module.css by adding
the required declaration separator before the background declaration and
changing all reported currentColor values to lowercase currentcolor, including
the occurrences at the referenced locations.
- Around line 162-165: Update the closed-state .nav styling so its links cannot
receive keyboard focus while the mobile navigation is hidden, adding visibility:
hidden alongside the existing opacity and pointer-events behavior. Restore
visibility: visible in .navOpen, preserving the current open and closed
transitions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bccbb5c6-98e6-4b66-b8c5-0231d734a9b6

📥 Commits

Reviewing files that changed from the base of the PR and between b630790 and f48eb23.

⛔ Files ignored due to path filters (4)
  • public/images/neo/hero-foreground.png is excluded by !**/*.png
  • public/images/neo/raidguild-panorama.png is excluded by !**/*.png
  • public/images/neo/raidguild-swords.png is excluded by !**/*.png
  • public/images/neo/sky-citadel.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • src/app/page.tsx
  • src/components/HomeExperience.module.css
  • src/components/HomeExperience.tsx

--coral: #ee3c78;
--rust: #b84031;
--acid: #d7e34d;
background: var(--paper);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the reported Stylelint errors.

Add the required declaration separator before background. Change the reported currentColor values to currentcolor to satisfy the configured Stylelint rule.

Also applies to: 32-33, 37-37, 112-112, 134-134

🧰 Tools
🪛 Stylelint (17.14.0)

[error] 9-9: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.module.css` at line 9, Fix the Stylelint
violations in HomeExperience.module.css by adding the required declaration
separator before the background declaration and changing all reported
currentColor values to lowercase currentcolor, including the occurrences at the
referenced locations.

Source: Linters/SAST tools

Comment on lines +162 to +165
.nav { align-items: flex-start; background: var(--deep); flex-direction: column; inset: 0; justify-content: center; opacity: 0; padding: 90px 30px; pointer-events: none; position: fixed; transform: translateY(-15px); transition: opacity .25s, transform .25s; }
.nav a { font-family: var(--font-display), serif; font-size: 42px; letter-spacing: -.03em; text-transform: none; }
.navCta { border: 0; padding: 0; }
.navOpen { opacity: 1; pointer-events: auto; transform: translateY(0); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove closed navigation links from the tab order.

At mobile widths, .nav remains rendered while closed. pointer-events: none does not prevent keyboard focus on the links in src/components/HomeExperience.tsx lines 131-134. Add visibility: hidden when closed and restore it in .navOpen, or conditionally render an inert navigation state.

Proposed fix
 .nav {
+  visibility: hidden;
   align-items: flex-start;
   background: var(--deep);
   flex-direction: column;
@@
 .navOpen {
   opacity: 1;
   pointer-events: auto;
   transform: translateY(0);
+  visibility: visible;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.module.css` around lines 162 - 165, Update the
closed-state .nav styling so its links cannot receive keyboard focus while the
mobile navigation is hidden, adding visibility: hidden alongside the existing
opacity and pointer-events behavior. Restore visibility: visible in .navOpen,
preserving the current open and closed transitions.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
src/components/HomeExperience.module.css (1)

106-106: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider deriving the steward column count instead of hardcoding five.

.stewardTrack fixes repeat(5, ...) at both breakpoints. The steward list in src/components/HomeExperience.tsx can grow or shrink. A sixth steward wraps to a second grid row inside a horizontally scrolling container. Use grid-auto-flow: column; grid-auto-columns: minmax(190px, 1fr); so the track follows the data.

Also applies to: 260-260

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.module.css` at line 106, Update the
.stewardTrack grid definition at both breakpoint variants to remove the
hardcoded repeat(5) columns and use column auto-flow with grid-auto-columns:
minmax(190px, 1fr), preserving the existing horizontal scrolling behavior as the
steward list changes size.
src/components/HomeExperience.tsx (1)

354-375: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider scoping the non-passive wheel listener to the hero element.

The listener is registered on window with { passive: false }. The browser then cannot apply passive-scroll optimizations for any wheel event on the page, even though the handler only acts while the hero is in the viewport. Registering the listener on heroRef.current keeps the same behavior and limits the non-passive scope.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.tsx` around lines 354 - 375, Update the
useEffect wheel-listener setup around updateHorizontalPan to attach the
non-passive listener to heroRef.current instead of window, and remove it from
that same element during cleanup. Preserve the existing handler behavior and
safely handle cases where the hero element is unavailable.
package.json (1)

39-39: 🩺 Stability & Availability | 🔵 Trivial

Use frozen Bun installs in Railway and CI.

The committed bun.lock resolves next to 15.5.23, which satisfies ^15.5.9. No frozen install command exists in tracked files. Configure deployments to use bun install --frozen-lockfile, or pin next exactly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 39, Configure the project’s Railway and CI installation
flows to run Bun with the frozen-lockfile option, using the existing package
manager configuration and scripts where applicable. Ensure both deployment and
CI installs use bun install --frozen-lockfile so they honor the committed
bun.lock resolution, including the resolved next version.

Source: MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/HomeExperience.module.css`:
- Line 131: Rename all listed camelCase keyframes in HomeExperience.module.css
to kebab-case, including memberDrift, portalReveal, machineArrive,
atmosphereTurn, rimSkew, haloPulse, portalScan, thresholdBreathe,
thresholdNoise, portalCopyIn, signalBlink, copyDeparture, machineDeparture, and
portalDeparture. Update every corresponding animation reference, including the
override near line 280, while preserving the existing animation behavior.
- Around line 324-328: Extend the prefers-reduced-motion rule to disable
animations on .memberRail, .portalOverlay::before, .portalMachine, and
.portalMessage, covering the memberDrift, portalReveal, machineArrive, and
portalCopyIn effects while preserving the existing reduced-motion declarations.

In `@src/components/HomeExperience.tsx`:
- Around line 302-310: Update the portal-opening flow around dismissPortal and
the nav CTA to clear any pending portalTimerRef timer and reset portalClosing
before reopening. Replace the CTA’s direct setPortalOpen(true) call with the
existing launchPortal() helper so reopening during a close transition cancels
the stale timer.
- Around line 260-290: Update the modal logic in HomeExperience to manage focus:
focus the portal close button when the overlay opens, restore focus to the
trigger when it closes, and extend the existing keydown handling to trap Tab
between the close and enter buttons while the dialog is open. Use refs for the
trigger, close button, and enter button, preserving Escape-to-close behavior.
- Around line 158-256: Reduce the per-particle rendering cost in the portal
useEffect render loop by removing the shadowColor/shadowBlur work from each
particle. Replace it with a reusable radial-gradient glow sprite or a single
composited glow pass; if retaining the current drawing approach, also lower
particleCount while preserving the existing reduced-motion and compact-screen
behavior.

---

Nitpick comments:
In `@package.json`:
- Line 39: Configure the project’s Railway and CI installation flows to run Bun
with the frozen-lockfile option, using the existing package manager
configuration and scripts where applicable. Ensure both deployment and CI
installs use bun install --frozen-lockfile so they honor the committed bun.lock
resolution, including the resolved next version.

In `@src/components/HomeExperience.module.css`:
- Line 106: Update the .stewardTrack grid definition at both breakpoint variants
to remove the hardcoded repeat(5) columns and use column auto-flow with
grid-auto-columns: minmax(190px, 1fr), preserving the existing horizontal
scrolling behavior as the steward list changes size.

In `@src/components/HomeExperience.tsx`:
- Around line 354-375: Update the useEffect wheel-listener setup around
updateHorizontalPan to attach the non-passive listener to heroRef.current
instead of window, and remove it from that same element during cleanup. Preserve
the existing handler behavior and safely handle cases where the hero element is
unavailable.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 15a19e28-740b-47e9-9c47-2973915dc43b

📥 Commits

Reviewing files that changed from the base of the PR and between f48eb23 and abb7d9e.

⛔ Files ignored due to path filters (7)
  • bun.lock is excluded by !**/*.lock
  • public/images/neo/field-autonomous-treasury.png is excluded by !**/*.png
  • public/images/neo/field-protocol-garden.png is excluded by !**/*.png
  • public/images/neo/field-signal-commons.png is excluded by !**/*.png
  • public/images/neo/raidguild-panorama.png is excluded by !**/*.png
  • src/app/opengraph-image.jpg is excluded by !**/*.jpg
  • src/app/opengraph-image.png is excluded by !**/*.png
📒 Files selected for processing (4)
  • package.json
  • src/app/page.tsx
  • src/components/HomeExperience.module.css
  • src/components/HomeExperience.tsx

.memberRail small { font-size: 7px; letter-spacing: .1em; margin-top: 5px; opacity: .65; text-transform: uppercase; }
.memberRail i { font-size: 13px; font-style: normal; margin-left: auto; }
.rosterFootnote { font-size: 7px; letter-spacing: .15em; margin: 12px 0 0; opacity: .58; }
@keyframes memberDrift { to { transform: translateX(calc(-50% - 7px)); } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Rename the keyframes to kebab-case to satisfy Stylelint.

Stylelint reports keyframes-name-pattern errors for memberDrift, portalReveal, machineArrive, atmosphereTurn, rimSkew, haloPulse, portalScan, thresholdBreathe, thresholdNoise, portalCopyIn, signalBlink, copyDeparture, machineDeparture, and portalDeparture. Rename each keyframe and its animation references, for example memberDriftmember-drift. Update the override at line 280 as well.

Also applies to: 234-246, 280-280

🧰 Tools
🪛 Stylelint (17.14.0)

[error] 131-131: Expected keyframe name "memberDrift" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.module.css` at line 131, Rename all listed
camelCase keyframes in HomeExperience.module.css to kebab-case, including
memberDrift, portalReveal, machineArrive, atmosphereTurn, rimSkew, haloPulse,
portalScan, thresholdBreathe, thresholdNoise, portalCopyIn, signalBlink,
copyDeparture, machineDeparture, and portalDeparture. Update every corresponding
animation reference, including the override near line 280, while preserving the
existing animation behavior.

Source: Linters/SAST tools

Comment on lines +324 to +328
@media (prefers-reduced-motion: reduce) {
.heroArt, .heroForeground, .heroCelestial, .heroCopy, .heroSupplement, .coordinates, .heroIndex, .signalBar div, .orbit::after { animation: none; transform: none; transition: none; }
.missionImage { transition: none; }
.portalAtmosphere, .portalMachine::before, .portalMachine::after, .portalHalo, .portalHalo::before, .portalThreshold, .portalThreshold::after, .portalMessage > p span { animation: none; }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Extend the reduced-motion block to the marquee and the portal arrival animations.

The block disables the portal atmosphere, rims, halo, and threshold. It does not disable .memberRail (memberDrift, 68 s infinite), .portalOverlay::before (portalReveal), .portalMachine (machineArrive), or .portalMessage (portalCopyIn). A user who requests reduced motion still sees a continuous marquee and the full portal entrance animation.

♻️ Proposed addition
 `@media` (prefers-reduced-motion: reduce) {
   .heroArt, .heroForeground, .heroCelestial, .heroCopy, .heroSupplement, .coordinates, .heroIndex, .signalBar div, .orbit::after { animation: none; transform: none; transition: none; }
   .missionImage { transition: none; }
   .portalAtmosphere, .portalMachine::before, .portalMachine::after, .portalHalo, .portalHalo::before, .portalThreshold, .portalThreshold::after, .portalMessage > p span { animation: none; }
+  .memberRail, .portalOverlay::before, .portalMachine, .portalMessage { animation: none; }
+  .portalMessage { transform: translateY(-50%); }
 }
📝 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
@media (prefers-reduced-motion: reduce) {
.heroArt, .heroForeground, .heroCelestial, .heroCopy, .heroSupplement, .coordinates, .heroIndex, .signalBar div, .orbit::after { animation: none; transform: none; transition: none; }
.missionImage { transition: none; }
.portalAtmosphere, .portalMachine::before, .portalMachine::after, .portalHalo, .portalHalo::before, .portalThreshold, .portalThreshold::after, .portalMessage > p span { animation: none; }
}
`@media` (prefers-reduced-motion: reduce) {
.heroArt, .heroForeground, .heroCelestial, .heroCopy, .heroSupplement, .coordinates, .heroIndex, .signalBar div, .orbit::after { animation: none; transform: none; transition: none; }
.missionImage { transition: none; }
.portalAtmosphere, .portalMachine::before, .portalMachine::after, .portalHalo, .portalHalo::before, .portalThreshold, .portalThreshold::after, .portalMessage > p span { animation: none; }
.memberRail, .portalOverlay::before, .portalMachine, .portalMessage { animation: none; }
.portalMessage { transform: translateY(-50%); }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.module.css` around lines 324 - 328, Extend the
prefers-reduced-motion rule to disable animations on .memberRail,
.portalOverlay::before, .portalMachine, and .portalMessage, covering the
memberDrift, portalReveal, machineArrive, and portalCopyIn effects while
preserving the existing reduced-motion declarations.

Comment thread src/components/HomeExperience.tsx
Comment on lines +260 to +290
return (
<div
className={`${styles.portalOverlay} ${closing ? styles.portalClosing : ""}`}
role="dialog"
aria-modal="true"
aria-label="RaidGuild transit portal"
>
<canvas className={styles.portalCanvas} ref={canvasRef} aria-hidden="true" />
<div className={styles.portalAtmosphere} aria-hidden="true" />
<div className={styles.portalMachine} aria-hidden="true">
<div className={styles.portalHalo} />
<div className={styles.portalThreshold}><Sigil /></div>
</div>
<button className={styles.portalClose} type="button" onClick={onClose} aria-label="Close portal">
<span>CLOSE</span> ×
</button>
<div className={styles.portalReadout} aria-hidden="true">
<span>RG—TRANSIT / 001</span>
<span>STABILITY 98.7%</span>
</div>
<div className={styles.portalMessage}>
<p><span /> Transit window open</p>
<h2>CROSS THE<br /><em>THRESHOLD.</em></h2>
<button type="button" onClick={onEnter}>
<span>Enter the portal</span><i>↗</i>
</button>
</div>
<p className={styles.portalCoordinates}>39°44′N / 104°59′W<br />DESTINATION: UNMAPPED</p>
</div>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add focus management to the modal dialog.

The overlay declares role="dialog" and aria-modal="true", but focus stays on the element behind the overlay. Keyboard users can tab into the page content under the overlay, and focus is not returned to the trigger after the overlay closes. Escape handling alone does not satisfy modal keyboard behavior.

Move focus to the close button when the overlay opens. Restore focus to the trigger when it closes. Contain Tab within the overlay while it is open.

🐛 Sketch of the fix
 function PortalOverlay({ open, closing, onClose, onEnter }: PortalOverlayProps) {
   const canvasRef = useRef<HTMLCanvasElement>(null);
+  const closeRef = useRef<HTMLButtonElement>(null);
+
+  useEffect(() => {
+    if (!open) return;
+    const previouslyFocused = document.activeElement as HTMLElement | null;
+    closeRef.current?.focus();
+    return () => previouslyFocused?.focus();
+  }, [open]);
@@
-      <button className={styles.portalClose} type="button" onClick={onClose} aria-label="Close portal">
+      <button ref={closeRef} className={styles.portalClose} type="button" onClick={onClose} aria-label="Close portal">

Add a Tab key handler in the existing keydown effect to cycle focus between the close button and the enter button.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.tsx` around lines 260 - 290, Update the modal
logic in HomeExperience to manage focus: focus the portal close button when the
overlay opens, restore focus to the trigger when it closes, and extend the
existing keydown handling to trap Tab between the close and enter buttons while
the dialog is open. Use refs for the trigger, close button, and enter button,
preserving Escape-to-close behavior.

Comment on lines +302 to +310
const dismissPortal = (enter = false) => {
if (portalClosing) return;
setPortalClosing(true);
portalTimerRef.current = window.setTimeout(() => {
setPortalOpen(false);
setPortalClosing(false);
if (enter) document.getElementById("contact")?.scrollIntoView({ behavior: "smooth" });
}, enter ? 920 : 620);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle reopening the portal while a close transition runs.

dismissPortal schedules a timer and returns early if portalClosing is already true. The nav CTA at lines 429-433 sets portalOpen to true without clearing the pending timer. If a user closes the portal and reopens it within 620 ms, the pending timer fires and closes the newly opened portal.

Clear the pending timer and reset portalClosing when the portal opens.

🐛 Proposed fix
+  const launchPortal = () => {
+    if (portalTimerRef.current) window.clearTimeout(portalTimerRef.current);
+    portalTimerRef.current = null;
+    setPortalClosing(false);
+    setPortalOpen(true);
+  };

Then call launchPortal() instead of setPortalOpen(true) in the nav CTA handler.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.tsx` around lines 302 - 310, Update the
portal-opening flow around dismissPortal and the nav CTA to clear any pending
portalTimerRef timer and reset portalClosing before reopening. Replace the CTA’s
direct setPortalOpen(true) call with the existing launchPortal() helper so
reopening during a close transition cancels the stale timer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/components/HomeExperience.tsx (4)

404-424: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Scope the non-passive wheel listener to the hero element.

The listener is attached to window with { passive: false }. Browsers disable the fast scroll path for any scroll that targets a subtree covered by a non-passive wheel listener. Attaching it to window applies that penalty to the entire page, including every section below the hero, even though the handler returns early when the hero is off screen.

Attach the listener to heroRef.current instead. The handler already reads heroRef.current, so the bounds check becomes redundant.

♻️ Proposed refactor
   useEffect(() => {
+    const hero = heroRef.current;
+    if (!hero) return;
+
     const updateHorizontalPan = (event: WheelEvent) => {
-      const hero = heroRef.current;
-      if (!hero || Math.abs(event.deltaX) < Math.abs(event.deltaY) || Math.abs(event.deltaX) < 1) return;
-
-      const bounds = hero.getBoundingClientRect();
-      if (bounds.bottom <= 0 || bounds.top >= window.innerHeight) return;
+      if (Math.abs(event.deltaX) < Math.abs(event.deltaY) || Math.abs(event.deltaX) < 1) return;
 
       event.preventDefault();
@@
-    window.addEventListener("wheel", updateHorizontalPan, { passive: false });
-    return () => window.removeEventListener("wheel", updateHorizontalPan);
+    hero.addEventListener("wheel", updateHorizontalPan, { passive: false });
+    return () => hero.removeEventListener("wheel", updateHorizontalPan);
   }, []);

Note that heroRef.current is populated before effects run, so reading it in the effect body is safe here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.tsx` around lines 404 - 424, Update the
useEffect containing updateHorizontalPan to attach the non-passive wheel
listener directly to heroRef.current instead of window, and remove the
now-redundant hero bounds/visibility check. Preserve the existing delta
filtering, pan updates, and cleanup, guarding appropriately if the hero element
is unavailable.

385-402: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Coalesce high-frequency DOM work into requestAnimationFrame in two handlers. Both handlers run DOM writes or forced layout reads once per input event instead of once per frame. The scroll parallax effect at lines 426-456 already implements the correct guard. Reuse that pattern at both sites.

  • src/components/HomeExperience.tsx#L385-L402: store the latest pointer coordinates, then write the seven custom properties inside a single rAF callback, and cancel the pending frame in the cleanup function.
  • src/components/HomeExperience.tsx#L682-L682: guard updateActiveField with the same rAF pattern so the offsetLeft and scrollLeft reads run at most once per frame, or replace the measurement with an IntersectionObserver on the track cards.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.tsx` around lines 385 - 402, Update
src/components/HomeExperience.tsx lines 385-402 in the updatePointer effect to
retain the latest pointer coordinates, perform all seven custom-property writes
in one requestAnimationFrame callback, and cancel any pending frame during
cleanup. Also update src/components/HomeExperience.tsx line 682 to guard
updateActiveField with the same per-frame requestAnimationFrame pattern so
offsetLeft and scrollLeft are read at most once per frame; alternatively replace
that measurement with an IntersectionObserver on the track cards.

356-359: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Prefer an anchor over window.open for the join destination.

joinGuild opens a new tab with window.open. If a popup blocker rejects the call, the function returns null and the user receives no feedback, but dismissPortal still closes the overlay. An <a href="https://portal.raidguild.org" target="_blank" rel="noopener noreferrer"> styled as the portal machine gives native middle-click and "open in new tab" behavior, and is not blocked.

If you keep window.open, handle the null return before dismissing the portal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.tsx` around lines 356 - 359, Replace the
joinGuild window.open flow with an anchor targeting https://portal.raidguild.org
using target="_blank" and rel="noopener noreferrer", styled as the portal
machine, so native link behavior is preserved; remove the click handler and
dismissPortal call from this navigation path.

222-225: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider reusing the Path2D batch objects across frames.

The render loop allocates 12 Path2D objects on every frame. Path2D has no reset API, so reuse requires recreating them, but you can allocate the outer array structure once and only recreate the paths. The current cost is small. Treat this as optional cleanup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.tsx` around lines 222 - 225, Optionally
optimize the render loop around the `batches` initialization by allocating the
outer batch array structure once and recreating only each batch’s `Path2D`
objects per frame. Preserve the existing batch count and palette mapping
behavior; this cleanup is not required if the allocation cost remains
acceptable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/HomeExperience.module.css`:
- Line 199: Update the reduced-motion rule near the existing rule at line 387 to
include .contactProtocol dd span, ensuring its signalBlink animation is disabled
when prefers-reduced-motion is requested while preserving the default animation
otherwise.
- Around line 322-323: Add a contact-specific media-query breakpoint before the
901px–1128px range that changes .contact to a single-column grid and resets
.contactIntro positioning to static, preventing the minimum columns, gap, and
padding from exceeding the viewport.

---

Nitpick comments:
In `@src/components/HomeExperience.tsx`:
- Around line 404-424: Update the useEffect containing updateHorizontalPan to
attach the non-passive wheel listener directly to heroRef.current instead of
window, and remove the now-redundant hero bounds/visibility check. Preserve the
existing delta filtering, pan updates, and cleanup, guarding appropriately if
the hero element is unavailable.
- Around line 385-402: Update src/components/HomeExperience.tsx lines 385-402 in
the updatePointer effect to retain the latest pointer coordinates, perform all
seven custom-property writes in one requestAnimationFrame callback, and cancel
any pending frame during cleanup. Also update src/components/HomeExperience.tsx
line 682 to guard updateActiveField with the same per-frame
requestAnimationFrame pattern so offsetLeft and scrollLeft are read at most once
per frame; alternatively replace that measurement with an IntersectionObserver
on the track cards.
- Around line 356-359: Replace the joinGuild window.open flow with an anchor
targeting https://portal.raidguild.org using target="_blank" and rel="noopener
noreferrer", styled as the portal machine, so native link behavior is preserved;
remove the click handler and dismissPortal call from this navigation path.
- Around line 222-225: Optionally optimize the render loop around the `batches`
initialization by allocating the outer batch array structure once and recreating
only each batch’s `Path2D` objects per frame. Preserve the existing batch count
and palette mapping behavior; this cleanup is not required if the allocation
cost remains acceptable.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a848fe2-7b03-4513-a43e-acf9cf4550b8

📥 Commits

Reviewing files that changed from the base of the PR and between abb7d9e and 5bece7f.

📒 Files selected for processing (3)
  • src/components/HireUs.tsx
  • src/components/HomeExperience.module.css
  • src/components/HomeExperience.tsx

.contactProtocol div { border-left: 1px solid rgba(16,45,44,.25); padding-left: 12px; }
.contactProtocol dt { font-size: 7px; letter-spacing: .16em; opacity: .55; }
.contactProtocol dd { font-family: var(--font-display), serif; font-size: 16px; margin: 7px 0 0; }
.contactProtocol dd span { animation: signalBlink 1.2s steps(2) infinite; background: var(--coral); border-radius: 50%; display: inline-block; height: 6px; margin-right: 5px; width: 6px; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Disable the contact status pulse for reduced motion.

.contactProtocol dd span continues to run signalBlink when reduced motion is requested. Add it to the rule at Line 387.

Proposed fix
 `@media` (prefers-reduced-motion: reduce) {
+  .contactProtocol dd span { animation: none; }
   .heroArt, .heroForeground, .heroCelestial, .heroCopy, .heroSupplement, .heroIndex, .signalBar div, .orbit::after { animation: none; transform: none; transition: none; }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.module.css` at line 199, Update the
reduced-motion rule near the existing rule at line 387 to include
.contactProtocol dd span, ensuring its signalBlink animation is disabled when
prefers-reduced-motion is requested while preserving the default animation
otherwise.

Comment on lines +322 to +323
.contact { grid-template-columns: 1fr; }
.contactIntro { position: static; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Collapse the contact grid before it overflows.

From approximately 901px through 1128px, the 340px and 540px minimum columns, gap, and horizontal padding exceed the viewport width. Add a contact-specific breakpoint before this range.

Proposed fix
+@media (max-width: 1140px) {
+  .contact { grid-template-columns: 1fr; }
+  .contactIntro { position: static; }
+}
+
 `@media` (max-width: 900px) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.module.css` around lines 322 - 323, Add a
contact-specific media-query breakpoint before the 901px–1128px range that
changes .contact to a single-column grid and resets .contactIntro positioning to
static, preventing the minimum columns, gap, and padding from exceeding the
viewport.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/HomeExperience.module.css`:
- Around line 41-42: Update the portalAttention animation used by .portalTrigger
so it no longer animates transform, allowing the .portalTrigger:hover transform:
translateY(-3px) to take effect while the animation is active or paused.
Preserve the existing attention effect by animating a separate property if
needed.
- Around line 41-46: Update the reduced-motion rule in HomeExperience.module.css
to include .portalTrigger and .portalTriggerMark > i, disabling their
portalAttention and triggerOrbit animations while preserving the existing
reduced-motion behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ce8283f0-2a4a-4e76-bfa6-b3c3c6f7d453

📥 Commits

Reviewing files that changed from the base of the PR and between 5bece7f and 1ad38ef.

📒 Files selected for processing (3)
  • src/components/HomeExperience.module.css
  • src/components/HomeExperience.tsx
  • src/lib/fonts.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/HomeExperience.tsx

Comment on lines +41 to +42
.portalTrigger { align-items: center; animation: portalAttention 6s 2s ease-in-out infinite; backdrop-filter: blur(14px) saturate(1.15); background: rgba(5,34,34,.86); border: 1px solid rgba(239,233,215,.35); border-radius: 100px; box-shadow: 0 10px 35px rgba(2,18,20,.22); color: var(--paper); cursor: pointer; display: flex; font-family: var(--font-mono), monospace; gap: 12px; padding: 7px 17px 7px 7px; position: fixed; right: clamp(24px, 4vw, 72px); top: 20px; transition: background .25s, border-color .25s, transform .25s; -webkit-backdrop-filter: blur(14px) saturate(1.15); z-index: 30; }
.portalTrigger:hover { animation-play-state: paused; background: var(--paper); border-color: var(--paper); color: var(--ink); transform: translateY(-3px); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the hover transform effective.

portalAttention animates transform on .portalTrigger. Pausing the animation does not remove its animated value, so transform: translateY(-3px) is overridden while the animation is active. Remove transform from portalAttention, or animate a separate property.

Proposed fix
-@keyframes portalAttention { 0%, 68%, 100% { box-shadow: 0 10px 35px rgba(2,18,20,.22); transform: translateY(0); } 73% { box-shadow: 0 10px 35px rgba(2,18,20,.22), 0 0 0 0 rgba(238,60,120,.3); transform: translateY(-2px); } 82% { box-shadow: 0 10px 35px rgba(2,18,20,.22), 0 0 0 12px rgba(238,60,120,0); transform: translateY(0); } }
+@keyframes portalAttention { 0%, 68%, 100% { box-shadow: 0 10px 35px rgba(2,18,20,.22); } 73% { box-shadow: 0 10px 35px rgba(2,18,20,.22), 0 0 0 0 rgba(238,60,120,.3); } 82% { box-shadow: 0 10px 35px rgba(2,18,20,.22), 0 0 0 12px rgba(238,60,120,0); } }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.module.css` around lines 41 - 42, Update the
portalAttention animation used by .portalTrigger so it no longer animates
transform, allowing the .portalTrigger:hover transform: translateY(-3px) to take
effect while the animation is active or paused. Preserve the existing attention
effect by animating a separate property if needed.

Comment on lines +41 to +46
.portalTrigger { align-items: center; animation: portalAttention 6s 2s ease-in-out infinite; backdrop-filter: blur(14px) saturate(1.15); background: rgba(5,34,34,.86); border: 1px solid rgba(239,233,215,.35); border-radius: 100px; box-shadow: 0 10px 35px rgba(2,18,20,.22); color: var(--paper); cursor: pointer; display: flex; font-family: var(--font-mono), monospace; gap: 12px; padding: 7px 17px 7px 7px; position: fixed; right: clamp(24px, 4vw, 72px); top: 20px; transition: background .25s, border-color .25s, transform .25s; -webkit-backdrop-filter: blur(14px) saturate(1.15); z-index: 30; }
.portalTrigger:hover { animation-play-state: paused; background: var(--paper); border-color: var(--paper); color: var(--ink); transform: translateY(-3px); }
.portalTriggerMark { display: block; position: relative; }
.portalTriggerMark .sigil { color: var(--coral); height: 48px; margin: 0; width: 48px; }
.portalTriggerMark .swordMark { height: 30px; width: 33px; }
.portalTriggerMark > i { animation: triggerOrbit 3.8s linear infinite; border: 1px solid var(--coral); border-bottom-color: transparent; border-radius: 50%; inset: -3px; pointer-events: none; position: absolute; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="src/components/HomeExperience.module.css"
rg -n -C 8 'prefers-reduced-motion|portalTrigger|portalTriggerMark > i|triggerOrbit|portalAttention' "$file"

Repository: raid-guild/website

Length of output: 6766


Disable portal animations for reduced-motion users.

The reduced-motion rule does not disable portalAttention on .portalTrigger or triggerOrbit on .portalTriggerMark > i. Add both selectors to the rule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HomeExperience.module.css` around lines 41 - 46, Update the
reduced-motion rule in HomeExperience.module.css to include .portalTrigger and
.portalTriggerMark > i, disabling their portalAttention and triggerOrbit
animations while preserving the existing reduced-motion behavior.

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