feat: redesign homepage with parallax expedition experience - #100
feat: redesign homepage with parallax expedition experience#100dekanbro wants to merge 14 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe homepage now renders a client-side ChangesHomepage experience
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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
src/components/HomeExperience.module.cssESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. src/components/HomeExperience.tsxESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox. src/lib/fonts.tsESLint 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. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (4)
public/images/neo/hero-foreground.pngis excluded by!**/*.pngpublic/images/neo/raidguild-panorama.pngis excluded by!**/*.pngpublic/images/neo/raidguild-swords.pngis excluded by!**/*.pngpublic/images/neo/sky-citadel.pngis excluded by!**/*.png
📒 Files selected for processing (3)
src/app/page.tsxsrc/components/HomeExperience.module.csssrc/components/HomeExperience.tsx
| --coral: #ee3c78; | ||
| --rust: #b84031; | ||
| --acid: #d7e34d; | ||
| background: var(--paper); |
There was a problem hiding this comment.
📐 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
| .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); } |
There was a problem hiding this comment.
🎯 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.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
src/components/HomeExperience.module.css (1)
106-106: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider deriving the steward column count instead of hardcoding five.
.stewardTrackfixesrepeat(5, ...)at both breakpoints. The steward list insrc/components/HomeExperience.tsxcan grow or shrink. A sixth steward wraps to a second grid row inside a horizontally scrolling container. Usegrid-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 winConsider scoping the non-passive wheel listener to the hero element.
The listener is registered on
windowwith{ 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 onheroRef.currentkeeps 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 | 🔵 TrivialUse frozen Bun installs in Railway and CI.
The committed
bun.lockresolvesnextto15.5.23, which satisfies^15.5.9. No frozen install command exists in tracked files. Configure deployments to usebun install --frozen-lockfile, or pinnextexactly.🤖 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
⛔ Files ignored due to path filters (7)
bun.lockis excluded by!**/*.lockpublic/images/neo/field-autonomous-treasury.pngis excluded by!**/*.pngpublic/images/neo/field-protocol-garden.pngis excluded by!**/*.pngpublic/images/neo/field-signal-commons.pngis excluded by!**/*.pngpublic/images/neo/raidguild-panorama.pngis excluded by!**/*.pngsrc/app/opengraph-image.jpgis excluded by!**/*.jpgsrc/app/opengraph-image.pngis excluded by!**/*.png
📒 Files selected for processing (4)
package.jsonsrc/app/page.tsxsrc/components/HomeExperience.module.csssrc/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)); } } |
There was a problem hiding this comment.
📐 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 memberDrift → member-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
| @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; } | ||
| } |
There was a problem hiding this comment.
📐 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.
| @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.
| 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> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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); | ||
| }; |
There was a problem hiding this comment.
🎯 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.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
src/components/HomeExperience.tsx (4)
404-424: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winScope the non-passive
wheellistener to the hero element.The listener is attached to
windowwith{ passive: false }. Browsers disable the fast scroll path for any scroll that targets a subtree covered by a non-passivewheellistener. Attaching it towindowapplies 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.currentinstead. The handler already readsheroRef.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.currentis 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 winCoalesce high-frequency DOM work into
requestAnimationFramein 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: guardupdateActiveFieldwith the same rAF pattern so theoffsetLeftandscrollLeftreads run at most once per frame, or replace the measurement with anIntersectionObserveron 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 winPrefer an anchor over
window.openfor the join destination.
joinGuildopens a new tab withwindow.open. If a popup blocker rejects the call, the function returnsnulland the user receives no feedback, butdismissPortalstill 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 thenullreturn 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 valueConsider reusing the
Path2Dbatch objects across frames.The render loop allocates 12
Path2Dobjects on every frame.Path2Dhas 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
📒 Files selected for processing (3)
src/components/HireUs.tsxsrc/components/HomeExperience.module.csssrc/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; } |
There was a problem hiding this comment.
🎯 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.
| .contact { grid-template-columns: 1fr; } | ||
| .contactIntro { position: static; } |
There was a problem hiding this comment.
🎯 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
src/components/HomeExperience.module.csssrc/components/HomeExperience.tsxsrc/lib/fonts.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/HomeExperience.tsx
| .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); } |
There was a problem hiding this comment.
🎯 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.
| .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; } |
There was a problem hiding this comment.
🎯 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.
concept off louchi ideas
Summary by CodeRabbit