[URGENT, please approve quickly] START Labs Landing Page#106
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughA new "LABS" landing page feature is introduced with complete styling infrastructure, layout components, and reusable animated UI components. The addition includes CSS design tokens and utilities for the labs page, page routing/metadata setup, a main content component with interactive timeline and FAQ sections, and three new animation-driven UI components supporting visual effects. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches✨ Simplify code
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: 10
🧹 Nitpick comments (11)
app/labs/LabsContent.tsx (7)
120-154: Extract the "Apply Now" CTA into a reusable component.The same bordered CTA markup (border slide-in on hover, identical styles + attrs) is duplicated at least 4 times in this file (L120-135 desktop nav, L139-154 mobile nav, L226-243 hero, L1890-1907 how-to-apply, L1973-1990 final apply). Extract it to a single
<LabsCTAButton href size>component to kill ~100 lines and prevent drift.Also applies to: 226-243, 1890-1907, 1973-1990
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/labs/LabsContent.tsx` around lines 120 - 154, Create a reusable LabsCTAButton component and replace each duplicated CTA anchor with it: implement a new component (e.g., LabsCTAButton) that accepts props href and size (or variant) and renders the same anchor structure and styles (the bordered slide-in hover effect, the inner <span> with group-hover:text-black, and the absolute sliding <div> using --labs-accent) so all occurrences (the desktop nav CTA, mobile CTA, hero CTA, how-to-apply CTA, and final apply CTA) call <LabsCTAButton href="..." size="..." /> instead of duplicating the markup; ensure it preserves className, style attributes, and accessibility (anchor href) and import/use the component inside LabsContent.tsx where the original anchor blocks at lines around the shown diffs were removed.
171-183: Autoplay background video: bandwidth & no fallback pause.
<video autoPlay muted loop>at hero starts downloading/decodingSTART_LABS_trimmed_video.mp4on every visit, including mobile / reduced-data users. Consider:
- Adding
preload='metadata'(or conditional load) to reduce initial bandwidth.- Respecting
prefers-reduced-motion(pause or hide video for users with that preference).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/labs/LabsContent.tsx` around lines 171 - 183, The hero <video> element (src '/labs/videos/START_LABS_trimmed_video.mp4' with autoPlay muted loop playsInline) is forcing full download/decoding; change it to preload='metadata' and guard autoplay/attachment by checking user preferences before rendering the <source>: detect prefers-reduced-motion via window.matchMedia('(prefers-reduced-motion: reduce)') and navigator.connection?.saveData (or equivalent) and if either is true, do not render the <source> (or render a static poster only) and ensure the element does not autoPlay; keep the poster attribute for fallback and make these checks inside the LabsContent component so the video is conditionally loaded only when allowed.
80-119: InlineonMouseEnter/onMouseLeavefor hover color — use Tailwind.Three nav links each attach imperative mouse handlers to toggle
#888888↔#ffffff. This re-creates functions on every render and bypasses Tailwind. Ahover:text-white text-[#888888](or a utility class with a CSS transition) does the same thing declaratively.♻️ Example
- <a - href='#about' - className='font-mono uppercase tracking-[0.15em] transition-all duration-200' - style={{ color: '#888888', fontSize: '10px' }} - onMouseEnter={(e) => - (e.currentTarget.style.color = '#ffffff') - } - onMouseLeave={(e) => - (e.currentTarget.style.color = '#888888') - } - > - About - </a> + <a + href='#about' + className='font-mono uppercase tracking-[0.15em] text-[10px] text-[`#888`] hover:text-white transition-colors duration-200' + > + About + </a>As per coding guidelines: "Use Tailwind CSS for styling".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/labs/LabsContent.tsx` around lines 80 - 119, The three anchor elements in LabsContent (the nav links "About", "How it works", "FAQ") use inline style props and onMouseEnter/onMouseLeave handlers to toggle colors; remove the inline style and event handlers and replace them with Tailwind utility classes on the <a> elements (e.g. add text-[`#888888`] hover:text-white transition-colors duration-200 and text-[10px] or an appropriate text size utility) while keeping the existing font-mono uppercase tracking classes so the hover color and transition are handled declaratively by Tailwind.
254-255: Hard-coded deadline duplicated — single-source it."30 April 2026" is hard-coded in the hero (L254-255) and the how-to-apply copy (L1885). Since the How-to-Apply section is currently commented out in the render tree, the user-visible copy is only in the hero today, but any future change will be missed in the other spot. Hoist to a module-level const (
const APPLICATION_DEADLINE = '30 April 2026') so both strings stay in sync.Also applies to: 1883-1886
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/labs/LabsContent.tsx` around lines 254 - 255, The hero and how-to-apply copy duplicate the hard-coded deadline string; create a module-level constant like const APPLICATION_DEADLINE = '30 April 2026' at the top of LabsContent.tsx and replace the literal "30 April 2026" occurrences in the hero JSX and the commented how-to-apply JSX with APPLICATION_DEADLINE (use string interpolation/concatenation where needed, e.g. `Deadline: ${APPLICATION_DEADLINE} · ...`) so both places share the same single source of truth.
588-589: Dead anchor<div>.The empty
<div className='absolute -top-20'></div>below theid='manifesto'section appears intended as a scroll anchor, but the parent<section>already hasscroll-mt-20andid='manifesto', making it redundant and semantically noisy.♻️ Proposed removal
- {/* Anchor for navigation */} - <div className='absolute -top-20'></div> - {/* Noise texture */}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/labs/LabsContent.tsx` around lines 588 - 589, Remove the redundant empty anchor div and rely on the existing section anchor: delete the <div className='absolute -top-20'></div> that sits below the section with id='manifesto' (the section already uses scroll-mt-20 and id='manifesto'), so remove the dead DOM node in LabsContent.tsx to reduce semantic noise and keep navigation behavior unchanged.
294-298: Prefernext/imageover raw<img>for perf & LCP.The page ships many
<img>tags — including hero-adjacent decorative images and a remote logo hosted onraw.githubusercontent.com(L296, L309) which is not CDN-optimized. Since the libraries context confirms Next.js 15.3.9, usingnext/imagewith explicitwidth/height(orfill) gives you automatic lazy loading, responsive srcset, and prevents CLS. Remote images will need aremotePatternsentry innext.config.*forraw.githubusercontent.com(or, better, rehost those logos under/public/labs/images/).Also applies to: 307-311, 750-754, 1203-1207, 1327-1344, 1747-1751, 1953-1956, 2165-2169
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/labs/LabsContent.tsx` around lines 294 - 298, Replace raw <img> usages in LabsContent.tsx (e.g., the START Munich logo image and other occurrences around lines listed) with Next.js's Image component: add "import Image from 'next/image'" to the top of the module, replace each <img ...> with <Image src={...} alt="..." width={W} height={H} /> or use layout="fill"/fill with a positioned container to avoid CLS, and ensure responsive props as appropriate; for remote raw.githubusercontent.com sources either move those assets into /public/labs/images/ and reference them with '/labs/images/...' or add a remotePatterns entry for raw.githubusercontent.com in next.config.* so Next Image can optimize them. Ensure each replaced instance preserves original classNames/styles (apply to elements around the referenced ranges: 294-298, 307-311, 750-754, 1203-1207, 1327-1344, 1747-1751, 1953-1956, 2165-2169).
7-7: Remove redundant route segment config export from client component.
export const dynamic = 'force-dynamic'has no effect in client components ('use client'). Route segment configs only work when exported from server components (page.tsx,layout.tsx,route.ts). Sinceapp/labs/page.tsxalready exports this config, the export here is redundant noise.♻️ Proposed removal
import { NoiseTexture } from '@/components/ui/noise-texture'; -export const dynamic = 'force-dynamic'; - // Label Component - Consistent "//" labels throughout the page🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/labs/LabsContent.tsx` at line 7, Remove the redundant route segment config export from the client component: delete the export const dynamic = 'force-dynamic' statement in LabsContent.tsx (the symbol "dynamic") since LabsContent.tsx is a client component ('use client') and the same config is already exported from app/labs/page.tsx; ensure no other code depends on that export before committing the removal.components/ui/dotted-glow-background.tsx (3)
201-203: Misleading name:regenThrottledisn't throttled.
regenThrottledis just an un-throttled call toregenDots. Either add actual throttling (e.g.,requestAnimationFrameor a trailingsetTimeout) to tame the storm of resizes during window drag, or rename it toregento avoid implying behavior that doesn't exist.♻️ Minimal rename
- const regenThrottled = () => { - regenDots(); - }; - - regenDots(); + regenDots();…and call
regenDots()directly fromhandleResize.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/ui/dotted-glow-background.tsx` around lines 201 - 203, The helper named regenThrottled is misleading because it simply calls regenDots without throttling; either implement actual throttling inside regenThrottled (e.g., debounce via requestAnimationFrame or a trailing setTimeout) and use that in handleResize, or rename regenThrottled to regen and call regenDots directly from handleResize; refer to the regenThrottled function, regenDots function, and handleResize to locate and update the logic accordingly.
151-306: Effect tears down and re-seeds dots whenever theme colors change.
resolvedColorandresolvedGlowColorare in the dependency array (L299-300) of the main RAF effect, so any theme toggle cancels the RAF loop, regenerates every dot (new random phases/speeds), and reattaches observers. Users will see a visible "restart" shimmer on every light/dark toggle. Since the colors are only read insidedraw, you can hold them in refs updated by the color-resolution effect and omit them from this effect's deps — keeping the animation state continuous across theme changes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/ui/dotted-glow-background.tsx` around lines 151 - 306, The RAF effect recreates the animation (tearing/regenerating dots) whenever theme colors change because resolvedColor and resolvedGlowColor are in the useEffect deps; move those values into mutable refs (e.g., colorRef/glowColorRef) and update those refs from the color-resolution effect, then remove resolvedColor and resolvedGlowColor from the dependency array of the main useEffect that defines draw (which should read from colorRef.current and glowColorRef.current), so the RAF loop, regenDots, observers, and dot phases/speeds (dots, draw, regenDots, resize, containerRef, canvasRef) persist across theme toggles without restarting.
273-276:window.resize+ResizeObserverboth fire — one is enough.
ro(L174-175) already observescontainerand callsresize()on every size change (including parent layout changes). You then additionally bindwindow.addEventListener('resize', handleResize)(L286), which callsresize()andregenThrottled(). The result: on every window resize both observers fire, causing tworesize()calls and one full dot regeneration. Consolidate into theResizeObservercallback (and callregenDotsfrom there) to remove the redundancy.Also applies to: 286-286
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/ui/dotted-glow-background.tsx` around lines 273 - 276, The ResizeObserver instance ro already calls resize() on container size changes, so remove the redundant window.addEventListener('resize', handleResize) usage and consolidate regeneration into the ResizeObserver callback: update the ro callback (where resize() is invoked) to also call regenThrottled()/regenDots so a single path handles both resize and regen, then delete or simplify handleResize and the window listener registration/cleanup; ensure the cleanup still disconnects ro and no longer tries to remove the removed window listener.components/ui/encrypted-text.tsx (1)
60-66:useReflazy-initializer uses capturedtext/charsetbut never re-inits.
scrambleCharsRefis initialized from the first-render values oftextandcharset. If the component mounts before entering view (soisInViewis false and the effect at L68 hasn't yet reset the ref) andtextchanges during that window, the render at L136-153 will reach into stale scramble chars. In practice the effect re-syncs as soon asisInViewflips true, but consider initializing to[]and doing all seeding inside the effect for clarity:- const scrambleCharsRef = useRef<string[]>( - text ? generateGibberishPreservingSpaces(text, charset).split("") : [], - ); + const scrambleCharsRef = useRef<string[]>([]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/ui/encrypted-text.tsx` around lines 60 - 66, The scrambleCharsRef useRef is seeded with captured text/charset on first render which can become stale; change its initializer to an empty array and move the seeding logic into the existing effect that runs when the component becomes visible (the effect that currently resets on isInView), so that when isInView becomes true or when text/charset change you assign scrambleCharsRef.current = generateGibberishPreservingSpaces(text, charset).split("") (and reset any related state like revealCount/lastFlipTimeRef if needed) to ensure the scramble buffer is always in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/globals.css`:
- Around line 432-442: The current .labs-reveal rule hides content by default
(opacity:0) which breaks when JS/hydration fails; change the CSS so the "hidden"
translate/opacity styles are applied only when a JS-enabled attribute is present
(e.g., [data-reveal-ready="true"]) instead of by default, and keep the no-JS
fallback visible; update selectors so the reveal/active rules target
.labs-reveal when data-reveal-ready="true" (and .labs-reveal.active when that
attribute is present) and then ensure the Labs client sets
data-reveal-ready="true" on the relevant container only after the reveal
observer is installed.
In `@app/labs/LabsContent.tsx`:
- Line 1190: Two sections use the same HTML id "proof" (ProofCalloutSection and
ProofSection), which will produce duplicate anchors when both are rendered; pick
one canonical section to keep and rename or remove the other's id (or delete the
dead component). Locate ProofCalloutSection and ProofSection in LabsContent,
decide which should remain as the canonical "#proof" anchor, remove or change
the id attribute on the other component (or remove the unused component entirely
if dead code), and update any internal links or references in LabsContent that
point to '#proof' to use the chosen canonical section name.
- Around line 1173-1178: The iframe in LabsContent (the Google Drive video
embed) lacks a title which breaks accessibility; update the iframe element in
LabsContent.tsx to include a descriptive title attribute (e.g., title="Demo
video: <short description>") so screen readers have a name, and add
loading="lazy" to defer offscreen loading; make this change on the iframe JSX
inside the LabsContent component where the Google Drive src is used.
- Around line 2321-2336: The inline Script with id 'iframe-height-sender' calls
window.parent.postMessage(..., '*') and never removes listeners; replace this by
moving the sendHeight logic into the LabsContent React component (e.g., inside a
useEffect) so you can register and clean up listeners (remove resize and
DOMContentLoaded handlers on unmount), and change the postMessage target from
'*' to a restricted origin or configurable allow-list (validate
window.parent.origin or accept a prop/ENV value) before posting; reference the
sendHeight function, the window.parent.postMessage call, and the
resize/DOMContentLoaded listeners when making the change.
- Around line 2190-2267: Add proper ARIA attributes to the FAQ accordion buttons
and their panels so assistive tech can announce state and relationships: on the
mapped button inside the faqs.map (components using openIndex, setOpenIndex, and
faq) add aria-expanded={openIndex === i} and aria-controls with a unique id like
`faq-panel-${i}`; then add a matching id="faq-panel-${i}" and role="region" (and
optionally aria-labelledby pointing back to the button id) on the collapsible
div that contains the answer. Also apply the same pattern to the timeline
<button> implementation referenced (the code that uses openIndex/toggle at the
timeline section) to add aria-expanded, aria-controls and a corresponding panel
id/role.
In `@app/labs/page.tsx`:
- Line 4: Remove the explicit runtime override by deleting the export "export
const dynamic = 'force-dynamic'" from the Labs page component so the route can
use Next.js's static optimization; ensure no request-time APIs (cookies(),
headers(), draftMode(), fetch(), searchParams, unstable_noStore(), connection(),
etc.) are introduced so the page remains eligible for static caching.
In `@components/dotted-glow-background-demo.tsx`:
- Around line 6-21: Replace non-standard Tailwind utilities with Tailwind v3
arbitrary-value equivalents: change the container's "md:size-100" to explicit
width/height arbitraries (e.g., replace md:size-100 with md:w-[100px]
md:h-[100px]) and swap the mask utilities "mask-radial-to-90%
mask-radial-at-center" (used on the DottedGlowBackground) for a valid arbitrary
mask like
mask-[radial-gradient(closest-side_at_center,black_0,transparent_90%)]; update
any matching uses (the outer div className and the DottedGlowBackground
className) so Tailwind generates the CSS.
In `@components/ui/dotted-glow-background.tsx`:
- Around line 188-199: The grid seeding loop starts i and j at -1 which produces
negative j values so j % 2 is negative and breaks the staggered offset; fix
either by starting both loops at 0 (change "for (let i = -1..."/"for (let j =
-1..." to start at 0) or keep the -1 bounds but normalize the parity test (use a
normalized modulo like ((j % 2) + 2) % 2) where the offset is computed, and
ensure the dots push logic (dots array population with x, y, phase, speed) uses
the corrected parity so the top row aligns consistently with neighbors.
In `@components/ui/encrypted-text.tsx`:
- Around line 129-155: Remove the non-standard role by deleting role="text" from
the motion.span (the element with ref, className and aria-label={text}) and add
aria-hidden="true" to each per-character <span> (the inner span rendered inside
the text.split().map) so the outer motion.span's aria-label is announced once
instead of each character being read individually.
In `@components/ui/noise-texture.tsx`:
- Around line 43-50: The SVG noise layer is decorative and should be hidden from
assistive tech; update the SVG element in components/ui/noise-texture.tsx (the
<svg> inside the NoiseTexture component) to include aria-hidden="true" and
focusable="false" so screen readers and keyboard focus ignore it (no visible
labels or role changes needed).
---
Nitpick comments:
In `@app/labs/LabsContent.tsx`:
- Around line 120-154: Create a reusable LabsCTAButton component and replace
each duplicated CTA anchor with it: implement a new component (e.g.,
LabsCTAButton) that accepts props href and size (or variant) and renders the
same anchor structure and styles (the bordered slide-in hover effect, the inner
<span> with group-hover:text-black, and the absolute sliding <div> using
--labs-accent) so all occurrences (the desktop nav CTA, mobile CTA, hero CTA,
how-to-apply CTA, and final apply CTA) call <LabsCTAButton href="..." size="..."
/> instead of duplicating the markup; ensure it preserves className, style
attributes, and accessibility (anchor href) and import/use the component inside
LabsContent.tsx where the original anchor blocks at lines around the shown diffs
were removed.
- Around line 171-183: The hero <video> element (src
'/labs/videos/START_LABS_trimmed_video.mp4' with autoPlay muted loop
playsInline) is forcing full download/decoding; change it to preload='metadata'
and guard autoplay/attachment by checking user preferences before rendering the
<source>: detect prefers-reduced-motion via
window.matchMedia('(prefers-reduced-motion: reduce)') and
navigator.connection?.saveData (or equivalent) and if either is true, do not
render the <source> (or render a static poster only) and ensure the element does
not autoPlay; keep the poster attribute for fallback and make these checks
inside the LabsContent component so the video is conditionally loaded only when
allowed.
- Around line 80-119: The three anchor elements in LabsContent (the nav links
"About", "How it works", "FAQ") use inline style props and
onMouseEnter/onMouseLeave handlers to toggle colors; remove the inline style and
event handlers and replace them with Tailwind utility classes on the <a>
elements (e.g. add text-[`#888888`] hover:text-white transition-colors
duration-200 and text-[10px] or an appropriate text size utility) while keeping
the existing font-mono uppercase tracking classes so the hover color and
transition are handled declaratively by Tailwind.
- Around line 254-255: The hero and how-to-apply copy duplicate the hard-coded
deadline string; create a module-level constant like const APPLICATION_DEADLINE
= '30 April 2026' at the top of LabsContent.tsx and replace the literal "30
April 2026" occurrences in the hero JSX and the commented how-to-apply JSX with
APPLICATION_DEADLINE (use string interpolation/concatenation where needed, e.g.
`Deadline: ${APPLICATION_DEADLINE} · ...`) so both places share the same single
source of truth.
- Around line 588-589: Remove the redundant empty anchor div and rely on the
existing section anchor: delete the <div className='absolute -top-20'></div>
that sits below the section with id='manifesto' (the section already uses
scroll-mt-20 and id='manifesto'), so remove the dead DOM node in LabsContent.tsx
to reduce semantic noise and keep navigation behavior unchanged.
- Around line 294-298: Replace raw <img> usages in LabsContent.tsx (e.g., the
START Munich logo image and other occurrences around lines listed) with
Next.js's Image component: add "import Image from 'next/image'" to the top of
the module, replace each <img ...> with <Image src={...} alt="..." width={W}
height={H} /> or use layout="fill"/fill with a positioned container to avoid
CLS, and ensure responsive props as appropriate; for remote
raw.githubusercontent.com sources either move those assets into
/public/labs/images/ and reference them with '/labs/images/...' or add a
remotePatterns entry for raw.githubusercontent.com in next.config.* so Next
Image can optimize them. Ensure each replaced instance preserves original
classNames/styles (apply to elements around the referenced ranges: 294-298,
307-311, 750-754, 1203-1207, 1327-1344, 1747-1751, 1953-1956, 2165-2169).
- Line 7: Remove the redundant route segment config export from the client
component: delete the export const dynamic = 'force-dynamic' statement in
LabsContent.tsx (the symbol "dynamic") since LabsContent.tsx is a client
component ('use client') and the same config is already exported from
app/labs/page.tsx; ensure no other code depends on that export before committing
the removal.
In `@components/ui/dotted-glow-background.tsx`:
- Around line 201-203: The helper named regenThrottled is misleading because it
simply calls regenDots without throttling; either implement actual throttling
inside regenThrottled (e.g., debounce via requestAnimationFrame or a trailing
setTimeout) and use that in handleResize, or rename regenThrottled to regen and
call regenDots directly from handleResize; refer to the regenThrottled function,
regenDots function, and handleResize to locate and update the logic accordingly.
- Around line 151-306: The RAF effect recreates the animation
(tearing/regenerating dots) whenever theme colors change because resolvedColor
and resolvedGlowColor are in the useEffect deps; move those values into mutable
refs (e.g., colorRef/glowColorRef) and update those refs from the
color-resolution effect, then remove resolvedColor and resolvedGlowColor from
the dependency array of the main useEffect that defines draw (which should read
from colorRef.current and glowColorRef.current), so the RAF loop, regenDots,
observers, and dot phases/speeds (dots, draw, regenDots, resize, containerRef,
canvasRef) persist across theme toggles without restarting.
- Around line 273-276: The ResizeObserver instance ro already calls resize() on
container size changes, so remove the redundant
window.addEventListener('resize', handleResize) usage and consolidate
regeneration into the ResizeObserver callback: update the ro callback (where
resize() is invoked) to also call regenThrottled()/regenDots so a single path
handles both resize and regen, then delete or simplify handleResize and the
window listener registration/cleanup; ensure the cleanup still disconnects ro
and no longer tries to remove the removed window listener.
In `@components/ui/encrypted-text.tsx`:
- Around line 60-66: The scrambleCharsRef useRef is seeded with captured
text/charset on first render which can become stale; change its initializer to
an empty array and move the seeding logic into the existing effect that runs
when the component becomes visible (the effect that currently resets on
isInView), so that when isInView becomes true or when text/charset change you
assign scrambleCharsRef.current = generateGibberishPreservingSpaces(text,
charset).split("") (and reset any related state like revealCount/lastFlipTimeRef
if needed) to ensure the scramble buffer is always in sync.
🪄 Autofix (Beta)
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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 532bc1d7-a430-41ef-aebb-2d9d6a9a1db7
⛔ Files ignored due to path filters (15)
package-lock.jsonis excluded by!**/package-lock.jsonpublic/labs/images/demoday.jpgis excluded by!**/*.jpgpublic/labs/images/labsbanner.JPGis excluded by!**/*.jpgpublic/labs/images/letter.JPGis excluded by!**/*.jpgpublic/labs/images/oneaim.pngis excluded by!**/*.pngpublic/labs/images/partner.jpgis excluded by!**/*.jpgpublic/labs/images/rocket.pngis excluded by!**/*.pngpublic/labs/images/startmunich.pngis excluded by!**/*.pngpublic/labs/images/timeline/final.JPGis excluded by!**/*.jpgpublic/labs/images/timeline/midterm.JPGis excluded by!**/*.jpgpublic/labs/images/timeline/onboarding.JPGis excluded by!**/*.jpgpublic/labs/images/timeline/roast-session-1.JPGis excluded by!**/*.jpgpublic/labs/images/timeline/roast-session-2.jpegis excluded by!**/*.jpegpublic/labs/images/winner.jpgis excluded by!**/*.jpgpublic/labs/videos/START_LABS_trimmed_video.mp4is excluded by!**/*.mp4
📒 Files selected for processing (11)
app/globals.cssapp/labs/LabsContent.tsxapp/labs/layout.tsxapp/labs/page.tsxcomponents.jsoncomponents/dotted-glow-background-demo.tsxcomponents/encrypted-text-demo-2.tsxcomponents/ui/dotted-glow-background.tsxcomponents/ui/encrypted-text.tsxcomponents/ui/noise-texture.tsxpackage.json
| /* Reveal animation for scroll - More subtle */ | ||
| .labs-reveal { | ||
| opacity: 0; | ||
| transform: translateY(12px); | ||
| transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1); | ||
| } | ||
|
|
||
| .labs-reveal.active { | ||
| opacity: 1; | ||
| transform: translateY(0); | ||
| } |
There was a problem hiding this comment.
Avoid making page content invisible until client JS runs.
.labs-reveal starts at opacity: 0; if hydration or the reveal observer fails, Labs sections can remain hidden. Gate the hidden state behind a JS-ready attribute/class, and keep the no-JS fallback visible.
🛡️ Proposed CSS direction
.labs-reveal {
- opacity: 0;
- transform: translateY(12px);
- transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);
+ opacity: 1;
+ transform: none;
}
- .labs-reveal.active {
+ .labs-page[data-reveal-ready="true"] .labs-reveal {
+ opacity: 0;
+ transform: translateY(12px);
+ transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);
+ }
+
+ .labs-page[data-reveal-ready="true"] .labs-reveal.active {
opacity: 1;
transform: translateY(0);
}
+
+ `@media` (prefers-reduced-motion: reduce) {
+ .labs-page[data-reveal-ready="true"] .labs-reveal {
+ opacity: 1;
+ transform: none;
+ transition: none;
+ }
+ }Then set data-reveal-ready="true" from the Labs client code only after the reveal observer is installed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/globals.css` around lines 432 - 442, The current .labs-reveal rule hides
content by default (opacity:0) which breaks when JS/hydration fails; change the
CSS so the "hidden" translate/opacity styles are applied only when a JS-enabled
attribute is present (e.g., [data-reveal-ready="true"]) instead of by default,
and keep the no-JS fallback visible; update selectors so the reveal/active rules
target .labs-reveal when data-reveal-ready="true" (and .labs-reveal.active when
that attribute is present) and then ensure the Labs client sets
data-reveal-ready="true" on the relevant container only after the reveal
observer is installed.
| <iframe | ||
| src='https://drive.google.com/file/d/1HQK9_Kry5IDKO2OHlfFuNbhQ2kH8nNGO/preview' | ||
| className='w-full h-full border' | ||
| style={{ borderColor: 'var(--labs-border)' }} | ||
| allow='autoplay' | ||
| ></iframe> |
There was a problem hiding this comment.
Missing iframe title breaks accessibility and fails WCAG 2.4.1.
The Google Drive video iframe has no title, so assistive tech announces it as an unnamed frame. Also consider loading="lazy" since this is below-the-fold.
♻️ Proposed fix
<iframe
src='https://drive.google.com/file/d/1HQK9_Kry5IDKO2OHlfFuNbhQ2kH8nNGO/preview'
className='w-full h-full border'
style={{ borderColor: 'var(--labs-border)' }}
allow='autoplay'
+ loading='lazy'
+ title='START Labs demo day video'
></iframe>📝 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.
| <iframe | |
| src='https://drive.google.com/file/d/1HQK9_Kry5IDKO2OHlfFuNbhQ2kH8nNGO/preview' | |
| className='w-full h-full border' | |
| style={{ borderColor: 'var(--labs-border)' }} | |
| allow='autoplay' | |
| ></iframe> | |
| <iframe | |
| src='https://drive.google.com/file/d/1HQK9_Kry5IDKO2OHlfFuNbhQ2kH8nNGO/preview' | |
| className='w-full h-full border' | |
| style={{ borderColor: 'var(--labs-border)' }} | |
| allow='autoplay' | |
| loading='lazy' | |
| title='START Labs demo day video' | |
| ></iframe> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/labs/LabsContent.tsx` around lines 1173 - 1178, The iframe in LabsContent
(the Google Drive video embed) lacks a title which breaks accessibility; update
the iframe element in LabsContent.tsx to include a descriptive title attribute
(e.g., title="Demo video: <short description>") so screen readers have a name,
and add loading="lazy" to defer offscreen loading; make this change on the
iframe JSX inside the LabsContent component where the Google Drive src is used.
| function ProofCalloutSection() { | ||
| return ( | ||
| <section | ||
| id='proof' |
There was a problem hiding this comment.
Duplicate id='proof' on two different sections.
Both ProofCalloutSection (L1190) and ProofSection (L1668) declare id='proof'. They're currently commented out in LabsContent's render tree (L2350, L2352), so this is latent, but re-enabling either alongside the other produces invalid HTML and non-deterministic anchor jumps for #proof. Pick one canonical section and rename/remove the duplicate — or delete the unused section outright if it is dead code.
Also applies to: 1668-1668
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/labs/LabsContent.tsx` at line 1190, Two sections use the same HTML id
"proof" (ProofCalloutSection and ProofSection), which will produce duplicate
anchors when both are rendered; pick one canonical section to keep and rename or
remove the other's id (or delete the dead component). Locate ProofCalloutSection
and ProofSection in LabsContent, decide which should remain as the canonical
"#proof" anchor, remove or change the id attribute on the other component (or
remove the unused component entirely if dead code), and update any internal
links or references in LabsContent that point to '#proof' to use the chosen
canonical section name.
| <div className='space-y-2'> | ||
| {faqs.map((faq, i) => ( | ||
| <div | ||
| key={i} | ||
| className='labs-reveal border-l-2 transition-all duration-200 bg-white/[0.02] border' | ||
| style={{ | ||
| borderLeftColor: | ||
| openIndex === i | ||
| ? 'var(--labs-accent)' | ||
| : 'transparent', | ||
| borderColor: 'rgba(255,255,255,0.05)', | ||
| transitionDelay: `${i * 50}ms`, | ||
| }} | ||
| > | ||
| <button | ||
| onClick={() => | ||
| setOpenIndex(openIndex === i ? null : i) | ||
| } | ||
| className='w-full text-left p-6 flex justify-between items-start gap-4' | ||
| > | ||
| <div className='flex items-start gap-4 flex-1'> | ||
| <span | ||
| className='font-mono text-[10px] uppercase tracking-[0.15em] mt-1 transition-colors duration-200' | ||
| style={{ | ||
| color: | ||
| openIndex === i | ||
| ? 'var(--labs-accent)' | ||
| : 'var(--labs-text-meta)', | ||
| }} | ||
| > | ||
| {String(i + 1).padStart(2, '0')} | ||
| </span> | ||
| <span | ||
| className='font-mono font-medium transition-colors duration-200' | ||
| style={{ | ||
| fontSize: '13px', | ||
| color: | ||
| openIndex === i | ||
| ? 'var(--labs-text-primary)' | ||
| : 'var(--labs-text-body)', | ||
| }} | ||
| > | ||
| {faq.q} | ||
| </span> | ||
| </div> | ||
| <div | ||
| className='text-xl font-mono transition-all duration-200' | ||
| style={{ | ||
| color: | ||
| openIndex === i | ||
| ? 'var(--labs-accent)' | ||
| : 'var(--labs-text-meta)', | ||
| transform: | ||
| openIndex === i | ||
| ? 'rotate(45deg)' | ||
| : 'rotate(0deg)', | ||
| }} | ||
| > | ||
| + | ||
| </div> | ||
| </button> | ||
| <div | ||
| className={`overflow-hidden transition-all duration-200 ${openIndex === i ? 'max-h-96 pb-6' : 'max-h-0'}`} | ||
| > | ||
| <div className='px-6 pl-[52px]'> | ||
| <p | ||
| className='font-mono leading-relaxed' | ||
| style={{ | ||
| fontSize: '12px', | ||
| color: 'var(--labs-text-body)', | ||
| }} | ||
| > | ||
| {faq.a} | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ))} |
There was a problem hiding this comment.
FAQ accordion buttons are missing ARIA state.
The expand/collapse buttons don't expose aria-expanded or aria-controls, so screen readers can't tell whether a question is open or which panel is revealed. Same issue applies to the timeline <button> at L646-696.
♻️ Proposed fix (FAQ)
<button
onClick={() =>
setOpenIndex(openIndex === i ? null : i)
}
+ aria-expanded={openIndex === i}
+ aria-controls={`faq-panel-${i}`}
className='w-full text-left p-6 flex justify-between items-start gap-4'
>
...
</button>
<div
+ id={`faq-panel-${i}`}
+ role='region'
className={`overflow-hidden transition-all duration-200 ${openIndex === i ? 'max-h-96 pb-6' : 'max-h-0'}`}
>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/labs/LabsContent.tsx` around lines 2190 - 2267, Add proper ARIA
attributes to the FAQ accordion buttons and their panels so assistive tech can
announce state and relationships: on the mapped button inside the faqs.map
(components using openIndex, setOpenIndex, and faq) add aria-expanded={openIndex
=== i} and aria-controls with a unique id like `faq-panel-${i}`; then add a
matching id="faq-panel-${i}" and role="region" (and optionally aria-labelledby
pointing back to the button id) on the collapsible div that contains the answer.
Also apply the same pattern to the timeline <button> implementation referenced
(the code that uses openIndex/toggle at the timeline section) to add
aria-expanded, aria-controls and a corresponding panel id/role.
| <Script id='iframe-height-sender' strategy='afterInteractive'> | ||
| {` | ||
| function sendHeight() { | ||
| if (window.parent !== window) { | ||
| const height = document.documentElement.scrollHeight; | ||
| window.parent.postMessage({ type: 'iframe-height', height: height }, '*'); | ||
| } | ||
| } | ||
| if (document.readyState === 'loading') { | ||
| document.addEventListener('DOMContentLoaded', sendHeight); | ||
| } else { | ||
| sendHeight(); | ||
| } | ||
| window.addEventListener('resize', sendHeight); | ||
| `} | ||
| </Script> |
There was a problem hiding this comment.
postMessage with '*' target origin leaks to any embedder.
The injected script posts iframe-height messages to '*', meaning any page that embeds this route in an iframe receives them. The payload itself is benign (scroll height), but using '*' is a documented anti-pattern and will flag on any security review. Restrict to a known origin (or a configurable allow-list) and consider moving the logic out of next/script's inline payload into a plain useEffect — the current listener is also never removed on unmount.
♻️ Proposed fix sketch
- <Script id='iframe-height-sender' strategy='afterInteractive'>
- {`
- function sendHeight() {
- if (window.parent !== window) {
- const height = document.documentElement.scrollHeight;
- window.parent.postMessage({ type: 'iframe-height', height: height }, '*');
- }
- }
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', sendHeight);
- } else {
- sendHeight();
- }
- window.addEventListener('resize', sendHeight);
- `}
- </Script>
+ {/* Move into a useEffect and restrict the targetOrigin to the known parent origin
+ (e.g. process.env.NEXT_PUBLIC_EMBED_PARENT_ORIGIN) so the message is only
+ delivered to the expected embedder and the resize listener is removed on unmount. */}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/labs/LabsContent.tsx` around lines 2321 - 2336, The inline Script with id
'iframe-height-sender' calls window.parent.postMessage(..., '*') and never
removes listeners; replace this by moving the sendHeight logic into the
LabsContent React component (e.g., inside a useEffect) so you can register and
clean up listeners (remove resize and DOMContentLoaded handlers on unmount), and
change the postMessage target from '*' to a restricted origin or configurable
allow-list (validate window.parent.origin or accept a prop/ENV value) before
posting; reference the sendHeight function, the window.parent.postMessage call,
and the resize/DOMContentLoaded listeners when making the change.
| import type { Metadata } from 'next' | ||
| import LabsContent from './LabsContent' | ||
|
|
||
| export const dynamic = 'force-dynamic' |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Inspect the Labs route for APIs that would require dynamic rendering.
# Expected result before removing `force-dynamic`: no matches for request-time APIs unless dynamic rendering is intentional.
rg -n -C3 '\b(cookies|headers|draftMode|unstable_noStore|connection)\s*\(|\bsearchParams\b|\bfetch\s*\(' app/labsRepository: startmunich/website
Length of output: 45
🏁 Script executed:
cat -n app/labs/page.tsxRepository: startmunich/website
Length of output: 919
🏁 Script executed:
rg -n "LabsContent" app/labs --type tsx --type ts -A 2 -B 2Repository: startmunich/website
Length of output: 90
🏁 Script executed:
fd -type f -name "*LabsContent*"Repository: startmunich/website
Length of output: 233
🏁 Script executed:
fd "LabsContent" app/labsRepository: startmunich/website
Length of output: 87
🏁 Script executed:
rg -l "LabsContent" app/labsRepository: startmunich/website
Length of output: 105
🏁 Script executed:
cat -n app/labs/LabsContent.tsxRepository: startmunich/website
Length of output: 50376
Remove force-dynamic from the page route — it prevents static optimization without benefit.
The page exports only static metadata and renders a client component with no request-time APIs. The shell search confirmed no usage of cookies(), headers(), draftMode(), unstable_noStore(), connection(), searchParams, or fetch() in the Labs route. Removing this line allows the page to be properly cached.
⚡ Proposed change
-export const dynamic = 'force-dynamic'
-
export const metadata: Metadata = {📝 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.
| export const dynamic = 'force-dynamic' | |
| export const metadata: Metadata = { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/labs/page.tsx` at line 4, Remove the explicit runtime override by
deleting the export "export const dynamic = 'force-dynamic'" from the Labs page
component so the route can use Next.js's static optimization; ensure no
request-time APIs (cookies(), headers(), draftMode(), fetch(), searchParams,
unstable_noStore(), connection(), etc.) are introduced so the page remains
eligible for static caching.
| <div className="relative flex size-60 items-end justify-end overflow-hidden rounded-md rounded-tl-3xl rounded-br-3xl rounded-bl-3xl border border-transparent px-4 shadow ring-1 shadow-black/10 ring-black/5 md:size-100 dark:shadow-white/10 dark:ring-white/5"> | ||
| <img | ||
| src="https://assets.aceternity.com/logos/calcom.png" | ||
| alt="Cal.com logo" | ||
| className="absolute inset-0 z-20 m-auto size-10 md:size-20 dark:invert dark:filter" | ||
| /> | ||
| <div className="relative z-20 flex w-full justify-between px-2 py-3 backdrop-blur-[2px] md:px-4"> | ||
| <p className="text-xs font-normal text-neutral-600 md:text-sm dark:text-neutral-400"> | ||
| The modern call scheduling app | ||
| </p> | ||
| <p className="text-xs font-normal text-neutral-600 md:text-sm dark:text-neutral-400"> | ||
| → | ||
| </p> | ||
| </div> | ||
| <DottedGlowBackground | ||
| className="pointer-events-none mask-radial-to-90% mask-radial-at-center" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check whether non-default Tailwind utilities used by the demo are defined in repo config/CSS.
# Expected result: custom definitions for size-100 and mask-radial utilities, or update the classes to Tailwind 3-safe forms.
rg -n -C3 'size-100|mask-radial-to|mask-radial-at|maskImage|mask-image|spacing.*100|extend:\s*\{' .Repository: startmunich/website
Length of output: 1654
🏁 Script executed:
cat -n tailwind.config.tsRepository: startmunich/website
Length of output: 3275
🏁 Script executed:
rg -l 'size-100|mask-radial|maskImage' --type css --type ts --type tsxRepository: startmunich/website
Length of output: 90
🏁 Script executed:
fd -e css -e scss | head -20Repository: startmunich/website
Length of output: 78
🏁 Script executed:
cat -n package.json | head -50Repository: startmunich/website
Length of output: 1249
🏁 Script executed:
cat app/globals.cssRepository: startmunich/website
Length of output: 10248
Replace non-standard Tailwind utilities with Tailwind 3-compatible alternatives.
The utilities md:size-100 and mask-radial-to-90% mask-radial-at-center are not defined in this project's Tailwind configuration and will not generate CSS. Update both to use arbitrary values:
Required changes
- <div className="relative flex size-60 items-end justify-end overflow-hidden rounded-md rounded-tl-3xl rounded-br-3xl rounded-bl-3xl border border-transparent px-4 shadow ring-1 shadow-black/10 ring-black/5 md:size-100 dark:shadow-white/10 dark:ring-white/5">
+ <div className="relative flex size-60 items-end justify-end overflow-hidden rounded-md rounded-tl-3xl rounded-br-3xl rounded-bl-3xl border border-transparent px-4 shadow ring-1 shadow-black/10 ring-black/5 md:size-[25rem] dark:shadow-white/10 dark:ring-white/5">- className="pointer-events-none mask-radial-to-90% mask-radial-at-center"
+ className="pointer-events-none [mask-image:radial-gradient(circle_at_center,black_0%,black_90%,transparent_100%)]"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/dotted-glow-background-demo.tsx` around lines 6 - 21, Replace
non-standard Tailwind utilities with Tailwind v3 arbitrary-value equivalents:
change the container's "md:size-100" to explicit width/height arbitraries (e.g.,
replace md:size-100 with md:w-[100px] md:h-[100px]) and swap the mask utilities
"mask-radial-to-90% mask-radial-at-center" (used on the DottedGlowBackground)
for a valid arbitrary mask like
mask-[radial-gradient(closest-side_at_center,black_0,transparent_90%)]; update
any matching uses (the outer div className and the DottedGlowBackground
className) so Tailwind generates the CSS.
| for (let i = -1; i < cols; i++) { | ||
| for (let j = -1; j < rows; j++) { | ||
| const x = i * gap + (j % 2 === 0 ? 0 : gap * 0.5); // offset every other row | ||
| const y = j * gap; | ||
| // Randomize phase and speed slightly per dot | ||
| const phase = Math.random() * Math.PI * 2; | ||
| const span = Math.max(max - min, 0); | ||
| const speed = min + Math.random() * span; // configurable rad/s | ||
| dots.push({ x, y, phase, speed }); | ||
| } | ||
| } | ||
| }; |
There was a problem hiding this comment.
Grid seeding loop starts at -1 — j goes negative.
The nested loops iterate i, j from -1 to cols/rows, so the first row computes y = -gap (off-canvas) and dots at j = -1 have phase/speed assigned but are effectively invisible. If the intent is to extend one row/column past the edge for smoother offset tiling, fine — but note that j % 2 in JS returns a negative value for negative j (-1 % 2 === -1), making the j === 0 branch asymmetric vs neighbors. Worth either iterating from 0 or using ((j % 2) + 2) % 2 to keep the staggered offset consistent at the top edge.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/ui/dotted-glow-background.tsx` around lines 188 - 199, The grid
seeding loop starts i and j at -1 which produces negative j values so j % 2 is
negative and breaks the staggered offset; fix either by starting both loops at 0
(change "for (let i = -1..."/"for (let j = -1..." to start at 0) or keep the -1
bounds but normalize the parity test (use a normalized modulo like ((j % 2) + 2)
% 2) where the offset is computed, and ensure the dots push logic (dots array
population with x, y, phase, speed) uses the corrected parity so the top row
aligns consistently with neighbors.
| return ( | ||
| <motion.span | ||
| ref={ref} | ||
| className={cn(className)} | ||
| aria-label={text} | ||
| role="text" | ||
| > | ||
| {text.split("").map((char, index) => { | ||
| const isRevealed = index < revealCount; | ||
| const displayChar = isRevealed | ||
| ? char | ||
| : char === " " | ||
| ? " " | ||
| : (scrambleCharsRef.current[index] ?? | ||
| generateRandomCharacter(charset)); | ||
|
|
||
| return ( | ||
| <span | ||
| key={index} | ||
| className={cn(isRevealed ? revealedClassName : encryptedClassName)} | ||
| > | ||
| {displayChar} | ||
| </span> | ||
| ); | ||
| })} | ||
| </motion.span> | ||
| ); |
There was a problem hiding this comment.
role="text" is non-standard ARIA.
role="text" is not defined in ARIA 1.2/1.3 — it's a Safari-only extension and is ignored (and sometimes warned about) by AXE / other tooling. Since you already have aria-label={text} on the span, the role adds no a11y benefit. Drop the role; the per-character child <span>s may also benefit from aria-hidden="true" so the full aria-label is announced once instead of character-by-character.
♻️ Proposed fix
<motion.span
ref={ref}
className={cn(className)}
aria-label={text}
- role="text"
>
- {text.split("").map((char, index) => {
+ {text.split("").map((char, index) => {
const isRevealed = index < revealCount;
const displayChar = isRevealed
? char
: char === " "
? " "
: (scrambleCharsRef.current[index] ??
generateRandomCharacter(charset));
return (
<span
key={index}
+ aria-hidden="true"
className={cn(isRevealed ? revealedClassName : encryptedClassName)}
>
{displayChar}
</span>
);
})}
</motion.span>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/ui/encrypted-text.tsx` around lines 129 - 155, Remove the
non-standard role by deleting role="text" from the motion.span (the element with
ref, className and aria-label={text}) and add aria-hidden="true" to each
per-character <span> (the inner span rendered inside the text.split().map) so
the outer motion.span's aria-label is announced once instead of each character
being read individually.
| <svg | ||
| className={cn( | ||
| "pointer-events-none absolute inset-0 z-0 size-full opacity-50 select-none dark:opacity-[0.75]", | ||
| className | ||
| )} | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| {...props} | ||
| > |
There was a problem hiding this comment.
Hide the decorative SVG from assistive technology.
This noise layer is visual-only, so exposing an unlabeled SVG can add noise to the accessibility tree.
♿ Proposed fix
<svg
className={cn(
"pointer-events-none absolute inset-0 z-0 size-full opacity-50 select-none dark:opacity-[0.75]",
className
)}
+ aria-hidden="true"
+ focusable="false"
xmlns="http://www.w3.org/2000/svg"
{...props}
>📝 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.
| <svg | |
| className={cn( | |
| "pointer-events-none absolute inset-0 z-0 size-full opacity-50 select-none dark:opacity-[0.75]", | |
| className | |
| )} | |
| xmlns="http://www.w3.org/2000/svg" | |
| {...props} | |
| > | |
| <svg | |
| className={cn( | |
| "pointer-events-none absolute inset-0 z-0 size-full opacity-50 select-none dark:opacity-[0.75]", | |
| className | |
| )} | |
| aria-hidden="true" | |
| focusable="false" | |
| xmlns="http://www.w3.org/2000/svg" | |
| {...props} | |
| > |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/ui/noise-texture.tsx` around lines 43 - 50, The SVG noise layer is
decorative and should be hidden from assistive tech; update the SVG element in
components/ui/noise-texture.tsx (the <svg> inside the NoiseTexture component) to
include aria-hidden="true" and focusable="false" so screen readers and keyboard
focus ignore it (no visible labels or role changes needed).
Summary by CodeRabbit
New Features
Chores