-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Add animated masonry layout to hero section #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: Add animated masonry layout to hero section #16
Conversation
- Implemented responsive masonry grid layout for hero images - 2 columns on mobile - 3 columns on tablet - 4 columns on desktop - Added smooth scroll animations with varied speeds per column - Duplicated images for seamless infinite scroll effect - Added accessibility support with prefers-reduced-motion - Preserved image aspect ratios using dynamic styles 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
|
Someone is attempting to deploy a commit to the personal Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughAdds a namespaced vertical scroll keyframe ( Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Page as page.tsx
participant Breakpoints as Breakpoint Sections
participant Distributor as Image Distributor
participant Column as Column Renderer
participant NextImg as NextImage
Note right of Breakpoints: Desktop (4 cols) / Tablet (3 cols) / Mobile (2 cols)
User->>Page: Request "/"
Page->>Breakpoints: Select active breakpoint (CSS/viewport)
Breakpoints->>Distributor: Request images for N columns
Distributor-->>Breakpoints: Arrays of images (duplicated for loop)
loop per column
Breakpoints->>Column: Render column with assigned speed class
loop per image
Column->>NextImg: Render image (aspectRatio, priority)
end
end
Page->>Page: Render overlay (title/subtitle) above masonry
alt prefers-reduced-motion
Note over Column: CSS media query disables animations
else animations allowed
Note over Column: `.animate-scroll-*` uses `atw-scroll-up` (linear infinite)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Comment |
There was a problem hiding this comment.
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 (8)
app/src/app/globals.css (3)
190-197: Namespace keyframes to avoid collisions with imported animation lib.
tw-animate-cssmight define similarly named keyframes. Prefix to ensure isolation.- @keyframes scroll-up { + @keyframes atw-scroll-up {And update usages below:
- .animate-scroll-slow { animation: scroll-up 80s linear infinite; } + .animate-scroll-slow { animation: atw-scroll-up 80s linear infinite; } - .animate-scroll-medium { animation: scroll-up 70s linear infinite; } + .animate-scroll-medium { animation: atw-scroll-up 70s linear infinite; } - .animate-scroll-fast { animation: scroll-up 60s linear infinite; } + .animate-scroll-fast { animation: atw-scroll-up 60s linear infinite; } - .animate-scroll-slower { animation: scroll-up 90s linear infinite; } + .animate-scroll-slower { animation: atw-scroll-up 90s linear infinite; } - .animate-scroll-faster { animation: scroll-up 50s linear infinite; } + .animate-scroll-faster { animation: atw-scroll-up 50s linear infinite; }
199-217: Hint the compositor.Add
will-change: transform; backface-visibility: hidden;to animation classes to reduce paint jank..animate-scroll-slow { - animation: atw-scroll-up 80s linear infinite; + animation: atw-scroll-up 80s linear infinite; + will-change: transform; + backface-visibility: hidden; } .animate-scroll-medium { - animation: atw-scroll-up 70s linear infinite; + animation: atw-scroll-up 70s linear infinite; + will-change: transform; + backface-visibility: hidden; } .animate-scroll-fast { - animation: atw-scroll-up 60s linear infinite; + animation: atw-scroll-up 60s linear infinite; + will-change: transform; + backface-visibility: hidden; } .animate-scroll-slower { - animation: atw-scroll-up 90s linear infinite; + animation: atw-scroll-up 90s linear infinite; + will-change: transform; + backface-visibility: hidden; } .animate-scroll-faster { - animation: atw-scroll-up 50s linear infinite; + animation: atw-scroll-up 50s linear infinite; + will-change: transform; + backface-visibility: hidden; }
219-227: Reduced‑motion OK. Consider also stopping on interaction.Optional: pause animation on hover/focus for user control.
@media (prefers-reduced-motion: reduce) { .animate-scroll-slow, .animate-scroll-medium, .animate-scroll-fast, .animate-scroll-slower, .animate-scroll-faster { animation: none; } } + +/* Optional user control */ +.animate-scroll-slow:hover, +.animate-scroll-medium:hover, +.animate-scroll-fast:hover, +.animate-scroll-slower:hover, +.animate-scroll-faster:hover { animation-play-state: paused; }app/src/app/page.tsx (5)
283-328: Mark decorative image grids as hidden from assistive tech.These mosaics are purely decorative; dozens of alt texts will create noise.
- <div className="absolute inset-0 hidden lg:grid grid-cols-4 gap-1"> + <div className="absolute inset-0 hidden lg:grid grid-cols-4 gap-1" aria-hidden="true">Apply same to tablet and mobile grids.
Also applies to: 331-376, 379-424
285-289: Desynchronize column starts to avoid visible sync.Add a small negative
animationDelayper column for a staggered start.- className={`flex flex-col gap-1 ${animationSpeeds[columnIndex]}`} + className={`flex flex-col gap-1 ${animationSpeeds[columnIndex]}`} + style={{ animationDelay: `${-(columnIndex * 5)}s` }}Also applies to: 333-336, 381-384
266-271: Compute columns once; avoid triple DOM for breakpoints (optional).Rendering three absolute layers is heavy. Consider a single responsive grid (
grid-cols-2 md:grid-cols-3 lg:grid-cols-4) and derive per‑breakpoint columns from a single 4‑col distribution (merge columns for smaller breakpoints).
254-265: Function is fine. Add memoization if moved client‑side later.If this becomes a Client Component, wrap distributions in
useMemo(images)to avoid recompute per render.
426-436: Overlay copy OK. Considersr-onlyheading duplication for a11y.Optional: add an offscreen
<h1>before the mosaics to ensure a stable LCP and semantic order regardless of visual overlay.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
app/src/app/globals.css(1 hunks)app/src/app/page.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
app/**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/neon-auth.mdc)
app/**/*.{tsx,ts}: Use pre-built Stack UI components (e.g., , , ) when building auth UI
In Client Components, retrieve the current user via useUser(); use useUser({ or: "redirect" }) for protected pages
In Server Components, use stackServerApp.getUser() and getUser({ or: "redirect" }) for protection
Files:
app/src/app/page.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/tanstack-db.mdc)
**/*.{ts,tsx}: In mutationFn implementations, ensure server writes have synced back before returning (e.g., await collection.refetch() or equivalent) so optimistic state can be safely discarded
When creating a collection, prefer supplying a Standard Schema (e.g., Zod/Effect) for validation and typing
If a schema is provided to createCollection, do not also pass a generic type parameter (e.g., avoid createCollection() when schema is present)
When using queryCollectionOptions, provide a stable getKey function to identify items
When using electricCollectionOptions, provide shapeOptions (including url and params.table) and getKey
When using trailBaseCollectionOptions, provide recordApi and getKey; use parse/serialize for field transformations as needed
When using rxdbCollectionOptions, provide rxCollection (and optionally startSync) to wire RxDB with TanStack DB
When using localStorageCollectionOptions, provide storageKey and getKey (optionally a storage backend like sessionStorage)
When using localOnlyCollectionOptions, provide getKey; optionally use initialData and mutation handlers
Use collection operations (insert, update, delete) with draft updaters instead of mutating items directly
Files:
app/src/app/page.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/tanstack-db.mdc)
In React components, use the useLiveQuery hook to bind live query results to component state
Files:
app/src/app/page.tsx
app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)
Do not use Bun-specific APIs in production code; ensure compatibility with Node.js runtime
Files:
app/src/app/page.tsx
🧬 Code graph analysis (1)
app/src/app/page.tsx (1)
app/src/lib/events.ts (1)
Image(1-7)
🔇 Additional comments (1)
app/src/app/page.tsx (1)
253-281: Approve — image width/height fields verifiedSchema requires width/height and the main image producers populate them (getPastEventImages / signImage flows / image-gen utils; common sizes seen: 1200x630, 400x400). Only intentional exception: app/src/app/api/v1/profile/route.ts sets width/height = 0 for signing.
| priority={index < 5} | ||
| alt={image.alt || `Event image ${index + 1}`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excessive Next/Image priority preloads (up to ~20 on desktop).
This will tank LCP and bandwidth. Only one (or none) should be priority.
- priority={index < 5}
+ priority={columnIndex === 0 && index === 0}Consider making all background images non‑priority if text is LCP.
Also applies to: 350-351, 398-399
🤖 Prompt for AI Agents
In app/src/app/page.tsx around lines 302-303 (and similarly at 350-351,
398-399), multiple Next/Image components are being marked priority causing
excessive preloads; change the logic so only a single image (e.g., the first
hero or index === 0) has priority (or remove priority entirely for background
images when text is expected to be LCP). Update the conditional from index < 5
to something like index === 0 (or false for background images), and apply the
same change at the other listed locations so only one image is priority and
background images are non-priority.
- Created safeAspectRatio() helper function that validates width/height - Returns fallback ratio "1/1" when dimensions are null/undefined - Logs warning once when missing dimensions are detected - Updated all 6 aspect ratio occurrences in masonry layout - Prevents zero-height tiles and layout breakage This addresses PR feedback about potential invalid aspect ratio values. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
CSS improvements: - Namespaced keyframes to atw-scroll-up to avoid conflicts - Added will-change and backface-visibility for GPU optimization - Improved animation performance to reduce paint jank Accessibility improvements: - Added aria-hidden="true" to decorative image grids - Prevents screen readers from announcing decorative images Visual improvements: - Added staggered animation delay per column (0s, -5s, -10s, -15s) - Creates more natural, desynchronized scrolling effect Addresses all CSS-related PR review feedback. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
app/src/app/page.tsx (1)
324-324: Remove Next/Image priority to avoid triple preloads across hidden breakpoints.All three breakpoint sections render server-side; each
priorityemits a<link rel="preload">, causing excessive network/LCP regressions. Make these background images non-priority.Apply this diff:
- priority={index < 5} - alt={image.alt || `Event image ${index + 1}`} + alt={image.alt || `Event image ${index + 1}`}- priority={index < 3} - alt={image.alt || `Event image ${index + 1}`} + alt={image.alt || `Event image ${index + 1}`}- priority={index < 2} - alt={image.alt || `Event image ${index + 1}`} + alt={image.alt || `Event image ${index + 1}`}#!/bin/bash # Verify no priority images remain in the hero rg -nP '\bpriority\s*=' app/src/app/page.tsxAlso applies to: 373-373, 422-422
🧹 Nitpick comments (3)
app/src/app/page.tsx (3)
325-325: Mark masonry images as decorative (a11y).These are purely visual; use empty alt and hide from AT.
Apply this diff:
- alt={image.alt || `Event image ${index + 1}`} + alt="" + role="presentation" + aria-hidden="true"Also applies to: 343-343, 374-374, 392-392, 423-423, 441-441
266-269: Warn once only in dev to avoid noisy server logs.Gate the warn behind NODE_ENV to prevent production log spam; keep the once-only behavior.
- if (!missingDimensionsWarned) { + if (process.env.NODE_ENV !== "production" && !missingDimensionsWarned) { console.warn("Missing or invalid image dimensions detected, using fallback aspect ratio"); missingDimensionsWarned = true; }
308-308: Defensive index for animation speed.Future-proof against column count changes by modding the index.
- className={`flex flex-col gap-1 ${animationSpeeds[columnIndex]}`} + className={`flex flex-col gap-1 ${animationSpeeds[columnIndex % animationSpeeds.length]}`}Also applies to: 357-357, 406-406
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
app/src/app/globals.css(1 hunks)app/src/app/page.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/src/app/globals.css
🧰 Additional context used
📓 Path-based instructions (4)
app/**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/neon-auth.mdc)
app/**/*.{tsx,ts}: Use pre-built Stack UI components (e.g., , , ) when building auth UI
In Client Components, retrieve the current user via useUser(); use useUser({ or: "redirect" }) for protected pages
In Server Components, use stackServerApp.getUser() and getUser({ or: "redirect" }) for protection
Files:
app/src/app/page.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/tanstack-db.mdc)
**/*.{ts,tsx}: In mutationFn implementations, ensure server writes have synced back before returning (e.g., await collection.refetch() or equivalent) so optimistic state can be safely discarded
When creating a collection, prefer supplying a Standard Schema (e.g., Zod/Effect) for validation and typing
If a schema is provided to createCollection, do not also pass a generic type parameter (e.g., avoid createCollection() when schema is present)
When using queryCollectionOptions, provide a stable getKey function to identify items
When using electricCollectionOptions, provide shapeOptions (including url and params.table) and getKey
When using trailBaseCollectionOptions, provide recordApi and getKey; use parse/serialize for field transformations as needed
When using rxdbCollectionOptions, provide rxCollection (and optionally startSync) to wire RxDB with TanStack DB
When using localStorageCollectionOptions, provide storageKey and getKey (optionally a storage backend like sessionStorage)
When using localOnlyCollectionOptions, provide getKey; optionally use initialData and mutation handlers
Use collection operations (insert, update, delete) with draft updaters instead of mutating items directly
Files:
app/src/app/page.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/tanstack-db.mdc)
In React components, use the useLiveQuery hook to bind live query results to component state
Files:
app/src/app/page.tsx
app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)
Do not use Bun-specific APIs in production code; ensure compatibility with Node.js runtime
Files:
app/src/app/page.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-18T00:04:06.390Z
Learnt from: CR
PR: allthingsweb-dev/allthingsweb#0
File: .cursor/rules/s3-image-signing.mdc:0-0
Timestamp: 2025-09-18T00:04:06.390Z
Learning: Applies to {app/src/app/api/**/route.ts,app/src/lib/**/*.{ts,tsx}} : Handle missing images explicitly (check for existence before signing and return null when absent)
Applied to files:
app/src/app/page.tsx
🧬 Code graph analysis (1)
app/src/app/page.tsx (1)
app/src/lib/events.ts (1)
Image(1-7)
🔇 Additional comments (3)
app/src/app/page.tsx (3)
256-272: safeAspectRatio helper LGTM.Solid guard against null/zero dims; good fallback.
275-286: Image distribution helper is clean and deterministic.Even spread via modulo; good for seamless looping.
450-461: Overlay content structure and contrast look good.Readable hierarchy; gradient ensures text legibility over media.

Summary
This PR introduces an animated masonry layout for the hero section images, creating a dynamic and visually engaging landing page experience.
Changes
prefers-reduced-motionsettingFiles Changed
src/app/page.tsx- UpdatedLandingHerocomponent with masonry layout logicsrc/app/globals.css- Added CSS animations and keyframes for scroll effectsVisual Impact
The hero section now features a Pinterest-style masonry grid with continuously scrolling columns at different speeds, creating an engaging visual effect while showcasing event images.
Testing
prefers-reduced-motion🤖 Generated with Claude Code
Summary by CodeRabbit