[#562] Rebuild the KHIX homepage rendering pipeline - #563
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: QUIET Plan: Advanced Run ID: 📒 Files selected for processing (19)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe homepage now uses viewport-aware media and animation lifecycles. Hero artwork is consolidated into static compositions with transparent ambient video. Waterfall droplets use an adaptive canvas renderer. FAQ audio reuses one Priority: ➖ Normal Severity of issue fixed: Medium Merge Risk: 🔵 Low · up to Some homepage media can still consume bandwidth earlier than needed, and reduced-motion users still receive portrait hover movement. These are bounded issues, but should be tracked before relying on the optimization results. 🚥 Pre-merge checks | ✅ 7 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (7 passed)
Full details: Out of Scope Changes checkExplanation The pull request includes visual-only changes that do not implement issue
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Some tools did not complete. Review the errors below. 🔧 ESLint
apps/2026/src/app/_components/AmbientVideo.tsxESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. apps/2026/src/app/_components/ambientVideoSources.tsESLint skipped: the matched ESLint configuration already failed (missing-dependency). apps/2026/src/app/_components/footer/CaveBugs.tsxESLint skipped: the matched ESLint configuration already failed (missing-dependency).
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: 3
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
🟡 Other comments (1)
apps/2026/src/app/_components/sections/speakers/SpeakerShowcase.module.css-134-137 (1)
134-137: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDisable portrait motion for reduced motion.
Add
.portraitShellto the reduced-motion transition reset. Also reset its hover and focus transform tonone.
🧹 Nitpick comments (1)
apps/2026/src/app/_components/sections/sponsor-team/WaterfallAtmosphere.tsx (1)
138-138: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRemove the per-frame size read.
The initial
resize()call andResizeObserverupdate the canvas dimensions. Keep theresize()call after pixel-ratio changes. This component has no fallback for an unavailableResizeObserver, so removing the render-loop call does not remove one.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: QUIET
Plan: Advanced
Run ID: c62dc9c5-f04b-4fad-9eb9-9eeeda3ec803
⛔ Files ignored due to path filters (16)
apps/2026/public/media/homepage/hero-ambient-combined.movis excluded by!**/*.movapps/2026/public/media/homepage/hero-ambient-combined.webmis excluded by!**/*.webmapps/2026/public/media/homepage/lenny-blink.movis excluded by!**/*.movapps/2026/public/media/homepage/lenny-blink.webmis excluded by!**/*.webmapps/2026/public/media/homepage/waterfall-one-balanced.movis excluded by!**/*.movapps/2026/public/media/homepage/waterfall-one-balanced.webmis excluded by!**/*.webmapps/2026/public/media/homepage/waterfall-one-mobile.movis excluded by!**/*.movapps/2026/public/media/homepage/waterfall-one-mobile.webmis excluded by!**/*.webmapps/2026/public/media/homepage/waterfall-three-balanced.movis excluded by!**/*.movapps/2026/public/media/homepage/waterfall-three-balanced.webmis excluded by!**/*.webmapps/2026/public/media/homepage/waterfall-three-mobile.movis excluded by!**/*.movapps/2026/public/media/homepage/waterfall-three-mobile.webmis excluded by!**/*.webmapps/2026/public/media/homepage/waterfall-two-balanced.movis excluded by!**/*.movapps/2026/public/media/homepage/waterfall-two-balanced.webmis excluded by!**/*.webmapps/2026/public/media/homepage/waterfall-two-mobile.movis excluded by!**/*.movapps/2026/public/media/homepage/waterfall-two-mobile.webmis excluded by!**/*.webm
📒 Files selected for processing (44)
.forge/features/homepage-memory/spec.md.forge/features/homepage-memory/srd.md.forge/features/homepage-memory/status.md.forge/features/homepage-memory/test-cases.mdapps/2026/public/media/homepage/extended-front-mobile.webpapps/2026/public/media/homepage/extended-front.webpapps/2026/public/media/homepage/faq-static/cave-ceiling-primary.webpapps/2026/public/media/homepage/faq-static/cave-ceiling-secondary.webpapps/2026/public/media/homepage/hero-static/hero-desktop-composite.avifapps/2026/public/media/homepage/hero-static/hero-mobile-7bg.webpapps/2026/public/media/homepage/hero-static/hero-mobile-composite.webpapps/2026/public/media/homepage/lenny-blink-poster.webpapps/2026/public/media/homepage/sponsor-impressink.webpapps/2026/src/app/_components/AmbientVideo.tsxapps/2026/src/app/_components/ambientVideoSources.tsapps/2026/src/app/_components/footer/CaveBugs.module.cssapps/2026/src/app/_components/footer/CaveBugs.tsxapps/2026/src/app/_components/navbar/Navbar.module.cssapps/2026/src/app/_components/sections/about/AboutCorruption.module.cssapps/2026/src/app/_components/sections/about/AboutGallery.tsxapps/2026/src/app/_components/sections/about/AboutSection.module.cssapps/2026/src/app/_components/sections/about/AboutSection.tsxapps/2026/src/app/_components/sections/faq/LennyBlink.module.cssapps/2026/src/app/_components/sections/faq/LennyBlink.tsxapps/2026/src/app/_components/sections/faq/faq.module.cssapps/2026/src/app/_components/sections/faq/faq.tsxapps/2026/src/app/_components/sections/hero/Hero.module.cssapps/2026/src/app/_components/sections/hero/Hero.tsxapps/2026/src/app/_components/sections/hero/HeroLayerImage.tsxapps/2026/src/app/_components/sections/hero/layers.tsapps/2026/src/app/_components/sections/speakers/PondAtmosphere.module.cssapps/2026/src/app/_components/sections/speakers/PondAtmosphere.tsxapps/2026/src/app/_components/sections/speakers/SpeakerShowcase.module.cssapps/2026/src/app/_components/sections/speakers/SpeakerShowcase.tsxapps/2026/src/app/_components/sections/sponsor-team/DeferredWaterfallArt.tsxapps/2026/src/app/_components/sections/sponsor-team/SponsorTeamSection.module.cssapps/2026/src/app/_components/sections/sponsor-team/SponsorTeamSection.tsxapps/2026/src/app/_components/sections/sponsor-team/WaterfallAtmosphere.tsxapps/2026/src/app/_components/sections/tracks/TracksSection.module.cssapps/2026/src/app/_components/sections/tracks/TracksSection.tsxapps/2026/src/app/_components/sponsor-showcase/SponsorShowcase.tsxapps/2026/src/app/_components/team-cascade/TeamCascadeClient.tsxapps/2026/src/app/_components/useViewportActivity.tsapps/2026/src/app/page.module.css
💤 Files with no reviewable changes (3)
- apps/2026/src/app/_components/sections/tracks/TracksSection.tsx
- apps/2026/src/app/_components/sections/hero/layers.ts
- apps/2026/src/app/_components/sections/hero/HeroLayerImage.tsx
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.
| fetchPriority={index === activeImageIndex ? "high" : "low"} | ||
| loading="eager" |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
#!/bin/bash
set -eu
file="apps/2026/src/app/_components/sections/about/AboutGallery.tsx"
printf '%s\n' '--- component ---'
cat -n "$file"
printf '%s\n' '--- Image imports and related usage ---'
rg -n -C 3 'next/image|<Image|loadedImageIndexes|activeImageIndex|AboutGallery' apps/2026/src/app/_components/sections/about apps/2026/package.json package.json 2>/dev/null || trueRepository: KnightHacks/forge
Length of output: 16965
🤖 get_repo_knowledge executed:
get_repo_knowledge KnightHacks/forge /tmp/coderabbit-repo-knowledge/knighthacks-forge-24c72cfe/conventions /tmp/coderabbit-repo-knowledge/knighthacks-forge-24c72cfe/learnings
Length of output: 19278
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- parent section ---'
cat -n apps/2026/src/app/_components/sections/about/AboutSection.tsx
printf '%s\n' '--- viewport hook ---'
cat -n apps/2026/src/app/_components/useViewportActivity.ts 2>/dev/null || rg -l 'function useViewportActivity|const useViewportActivity' apps/2026/src/app/_components | head -1 | xargs -r cat -n
printf '%s\n' '--- relevant CSS ---'
cat -n apps/2026/src/app/_components/sections/about/AboutSection.module.css | sed -n '1,260p'
printf '%s\n' '--- Next version ---'
rg -n '"next"|next@' apps/2026/package.json package.json pnpm-lock.yaml | head -20Repository: KnightHacks/forge
Length of output: 17525
🌐 Web query:
Next.js Image component loading eager lazy fetchPriority official documentation
💡 Result:
In Next.js, the next/image component manages image loading behavior through several props. As of September 2026, there have been updates to how these are defined [1]. The loading attribute The loading prop accepts 'lazy' or 'eager' [2][3]. By default, next/image uses loading="lazy" [2][4]. - 'lazy': The browser waits to load the image until it nears the viewport [4]. - 'eager': The image is loaded immediately upon discovery [2]. Using loading="eager" is generally recommended for images above the fold to improve Largest Contentful Paint (LCP) performance; using it for images outside the viewport can negatively impact performance [2]. The priority and preload props The priority prop has been the standard way to opt-in to eager loading and browser preloading for critical images [2]. However, recent updates have introduced a preload prop as a more explicit alternative, with the priority prop now deprecated [1]. When priority (or preload) is used, the image is automatically treated as loading="eager" and is preloaded by the browser, rendering the loading prop redundant or invalid if both are provided [2][3]. The fetchPriority attribute The fetchPriority prop (which maps to the standard HTML fetchpriority attribute) can be used to provide a hint to the browser regarding the relative priority of the image download [4]. While loading determines if an image should be fetched based on viewport position, fetchPriority influences the urgency of the download once the browser has decided to fetch it [4]. For critical images where you wish to signal higher importance, you can set fetchPriority="high" [2][4]. Summary of recommendations: - For LCP/Above-the-fold images: Use the preload prop (or priority if on an older version) and consider fetchPriority="high" to optimize loading speed [4][1]. - For below-the-fold images: Allow the default loading="lazy" behavior to persist to save bandwidth and improve page performance [2][4].
Citations:
- 1: GitHub pull request 83351 in vercel/next.js (link omitted to avoid creating a cross-reference)
- 2: https://nextjs.org/docs/14/app/api-reference/components/image
- 3: https://github.com/vercel/next.js/blob/65c22167c8b797ae8282c9c5301499301a54ff74/packages/next/client/image.tsx
- 4: GitHub discussion 90175 in vercel/next.js (link omitted to avoid creating a cross-reference)
Defer gallery image requests.
loadedImageIndexes renders images 0 and 1 on the initial render, before isGalleryActive becomes true. Both use loading="eager", and image 0 receives fetchPriority="high". Use loading="lazy" or viewport-gated rendering, and reserve high priority for the hero image.
Source: Coding guidelines
| alt="" | ||
| fill | ||
| sizes="100vw" | ||
| unoptimized |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- Hero imports and image context ---'
sed -n '1,130p' apps/2026/src/app/_components/sections/hero/Hero.tsx
printf '%s\n' '--- FAQ imports and image context ---'
sed -n '1,45p' apps/2026/src/app/_components/sections/faq/faq.tsx
sed -n '720,780p' apps/2026/src/app/_components/sections/faq/faq.tsx
printf '%s\n' '--- package/version bindings ---'
rg -n '"next"|from ["'\'']next/image["'\'']|<Image|unoptimized' apps/2026/package.json apps/2026/src/app/_components/sections/hero/Hero.tsx apps/2026/src/app/_components/sections/faq/faq.tsxRepository: KnightHacks/forge
Length of output: 8206
🤖 get_repo_knowledge executed:
get_repo_knowledge KnightHacks/forge /tmp/coderabbit-repo-knowledge/knighthacks-forge-24c72cfe/conventions
Length of output: 7088
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- FAQ asset definitions ---'
rg -n -A24 -B6 'type FloatingAsset|const .*assets|src:|sizes:' apps/2026/src/app/_components/sections/faq/faq.tsx
printf '%s\n' '--- image-related CSS ---'
rg -n -A12 -B6 'mobileHeroCompositeLayer|floatingAsset|img|object-fit|width:|height:' apps/2026/src/app/_components/sections/hero/Hero.module.css apps/2026/src/app/_components/sections/faq/faq.module.css
printf '%s\n' '--- local image files and metadata ---'
find apps/2026/public/media -type f \\( -iname '*.webp' -o -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \\) -print | sort | head -80
find apps/2026/public/media -type f \\( -iname '*.webp' -o -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \\) -print0 | xargs -0 file | rg 'hero|faq|cave|gem' | head -80Repository: KnightHacks/forge
Length of output: 50373
🌐 Web query:
Next.js 16 Image unoptimized prop official documentation
💡 Result:
In Next.js 16, the unoptimized prop remains a standard feature of the next/image component, used to disable automatic image optimization [1][2]. When the unoptimized prop is set to true on an Image component, the source image is served as-is, bypassing any transformation of quality, size, or format [1][3][2]. This is particularly useful for: - Vector images (SVG): Next.js typically recommends using unoptimized for SVGs to ensure they retain their lossless vector properties [1][2]. - Small images: Images that do not benefit significantly from optimization (e.g., those under 1KB) [1][2]. - Animated images: While Next.js makes a best-effort attempt to automatically bypass optimization for animated files (such as GIF, APNG, and WebP), the unoptimized prop can be used to explicitly force this behavior for a specific image [1][2]. - Authenticated images: The default Image Optimization API does not forward request headers when fetching a source image. If an image requires authentication to be accessed, using the unoptimized prop allows it to be served directly [4][1]. Global Configuration You can also disable Image Optimization for all images in your application by updating your configuration file (next.config.js or next.config.ts) [5][1][2]: module.exports = { images: { unoptimized: true, }, } If both the component-level prop and the global configuration are used, the global configuration acts as a master setting that overrides individual component props [5].
Citations:
- 1: https://nextjs.org/docs/app/api-reference/components/image?v=1.1.1
- 2: https://unpkg.com/next@16.3.1/dist/docs/02-pages/04-api-reference/01-components/image-legacy.md
- 3: https://nextjs.im/docs/14/app/api-reference/components/image/
- 4: https://vercel-next-js.mintlify.app/api-reference/components/image
- 5: https://vercel.com/docs/image-optimization
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- FAQ local asset declarations ---'
sed -n '249,340p' apps/2026/src/app/_components/sections/faq/faq.tsx
printf '%s\n' '--- FAQ floating asset layout ---'
rg -n -A30 -B5 '^\.floatingAsset|floatingAsset' apps/2026/src/app/_components/sections/faq/faq.module.css
printf '%s\n' '--- Hero and FAQ local image metadata ---'
for f in \
apps/2026/public/media/homepage/hero-static/hero-mobile-composite.webp \
apps/2026/public/media/homepage/faq-static/cave-ceiling-primary.webp \
apps/2026/public/media/homepage/faq-static/cave-ceiling-secondary.webp
do
if [ -f "$f" ]; then
file "$f"
wc -c "$f"
else
printf 'missing: %s\n' "$f"
fi
doneRepository: KnightHacks/forge
Length of output: 5704
Remove unoptimized from both local next/image usages. These responsive images use fill/sizes or responsive CSS widths, but unoptimized serves the original files without size or format optimization. Let next/image optimize them, or provide explicit responsive sources.
📍 Affects 2 files
apps/2026/src/app/_components/sections/hero/Hero.tsx#L94-L94(this comment)apps/2026/src/app/_components/sections/faq/faq.tsx#L759-L759
|
@coderabbitai review |
|
…memory # Conflicts: # apps/2026/src/app/_components/sections/sponsor-team/SponsorTeamSection.module.css # apps/2026/src/app/_components/sections/sponsor-team/SponsorTeamSection.tsx # apps/2026/src/app/_components/sponsor-showcase/SponsorShowcase.tsx

Why
The KHIX homepage was visually rich, but its rendering model asked the browser to keep nearly the entire experience alive at once: oversized decoded images, animated WebPs, permanent compositor hints, hundreds of animated DOM nodes, offscreen timers, and multiple concurrent transparent animations.
That pushed observed Chrome memory to 1.3 GB, made scrolling visibly choppy, and could leave major artwork missing for tens of seconds.
This PR rebuilds the homepage rendering pipeline so visual quality is preserved—and in several transitions improved—while expensive work exists only where and when it can be seen.
Closes: #562
What
Render less, without showing less
Make animation follow visibility
AudioContextand disconnects completed note graphs.Replace compositor pressure with bounded drawing
will-change, broad backdrop blur, oversized shadows, redundant glow animations, and dead layered-hero code.Preserve and polish the experience
Results
Measured on the optimized production build:
The Chrome RAM result was observed on the same machine and full-page test flow before and after this change. Exact process memory varies by platform and viewport; the remaining rows provide repeatable loading and rendering-pressure signals alongside it.
Test Plan
pnpm --filter @forge/2026 test— 14 tests passedNODE_OPTIONS=--max-old-space-size=4096 pnpm --filter @forge/2026 typecheck@forge/2026build with local non-secret environment placeholderspnpm --filter @forge/2026 formatpnpm analyze:react:changed— 0 failuresChecklist