Feat/design refresh#9
Conversation
Reshape the link-tree into a "personal platform" inspired by render.com: dark plum-tinted canvas, asymmetric identity rail + deployment stream, editorial Instrument Serif display type paired with Geist UI and mono metadata. Introduce audio-engineering accents that reinforce Keith's dual identity (dev × audio engineer) without fake status indicators: interactive spectrum bars that respond to cursor position like a real EQ, a 432 Hz tuning tag, and a waveform footer divider. Palette locked to the brand plum scale; sharp-cornered cards match the render.com aesthetic. Hover interactions add subtle sophistication throughout: letter-stagger on the display name, avatar halo intensify, left accent bar on project cards, row wash on channel links, and a rotating nav diamond. Dotted backdrop with radial glow and light SVG grain replaces the flickering grid. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 3 minutes and 52 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (9)
📝 WalkthroughWalkthroughThe PR adds documentation, overhauls global styles (new ink/glow tokens, animation tweaks, utilities), updates layout and metadata, replaces the FlickeringGrid with Backdrop and DevGreeting, introduces new visual components (Backdrop, Spectrum, Waveform, ProjectCard, Waveform), refactors project/social/profile/footer UIs, and removes the canvas-based FlickeringGrid. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Pull request overview
Design refresh for the link-tree landing page, shifting to a darker “ink/glow” visual system and new interactive/ornamental UI elements while refactoring project and social link presentation.
Changes:
- Replaced the flickering canvas background with a CSS-based backdrop (glow/dots/grain) and introduced waveform/spectrum accents.
- Refactored projects UI into a reusable
ProjectCardand updated the accordion/section layout and motion timing. - Updated page composition (two-column layout, sticky sidebar) and refreshed metadata/manifest copy.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| components/ui/waveform.tsx | Adds a reusable SVG waveform accent used in the footer. |
| components/ui/spectrum.tsx | Adds an interactive spectrum/EQ bar flourish for the profile header. |
| components/ui/flickering-grid.tsx | Removes the previous canvas-based flickering grid background. |
| components/ui/backdrop.tsx | Adds fixed CSS-based backdrop layers (glow/dots/grain + top hairline). |
| components/social-links-section.tsx | Redesigns social links into a divided list with hover treatments and handles. |
| components/projects-section.tsx | Updates section header/layout and swaps inline card markup for ProjectCard. |
| components/projects-accordion.tsx | Updates accordion toggle UI/motion and uses ProjectCard with index offset support. |
| components/project-card.tsx | Introduces a shared project card component used by section + accordion. |
| components/profile-header.tsx | Redesigns header typography and adds the Spectrum accent row. |
| components/footer.tsx | Updates footer styling and adds Waveform accent. |
| components/dev-greeting.tsx | Adds a client-side console greeting/easter egg. |
| app/page.tsx | Rebuilds the page layout (sticky aside, new nav, removes newsletter section). |
| app/manifest.ts | Updates PWA description copy (removes newsletter mention). |
| app/layout.tsx | Adds Instrument Serif font and forces dark mode on the root <html> class. |
| app/globals.css | Adds ink/glow token system, new backdrop utilities, refined motion, selection styling. |
| AGENTS.md | Adds agent guidance / repo overview documentation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <li key={link.name} className="relative overflow-hidden"> | ||
| <span | ||
| aria-hidden="true" | ||
| className="pointer-events-none absolute inset-0 bg-glow/[0.04] translate-x-[-101%] transition-transform duration-500 ease-[cubic-bezier(0.16,1,0.3,1)] group-hover/channel:translate-x-0" | ||
| /> |
There was a problem hiding this comment.
The hover overlay ( with group-hover/channel:translate-x-0) will never animate because the group/channel class is on the , which is a sibling (not an ancestor) of that . Move group/channel to the
group/channel) so the overlay can respond to the group hover state.| target={isMail ? undefined : "_blank"} | ||
| rel={isMail ? undefined : "noopener noreferrer"} | ||
| className="group/channel relative flex items-center gap-4 py-3 text-sm text-ink-300 transition-colors hover:text-ink-100" | ||
| style={{ animationDelay: `${index * 60}ms` }} |
There was a problem hiding this comment.
style={{ animationDelay: ... }} is set on the , but there’s no animation applied to that element (no animation-* / animate-* class), so the delay has no effect. Either add the intended animation to this element or remove the unused inline style.
| style={{ animationDelay: `${index * 60}ms` }} |
| return ( | ||
| <article | ||
| className="group relative border border-ink-800 bg-ink-950/60 backdrop-blur-sm overflow-hidden transition-colors duration-500 hover:border-ink-700 hover:bg-ink-900/70" | ||
| style={{ animationDelay: `${index * 80}ms` }} |
There was a problem hiding this comment.
animationDelay is set on the
| style={{ animationDelay: `${index * 80}ms` }} |
| console.log(title, titleStyle); | ||
| console.log(body, bodyStyle); |
There was a problem hiding this comment.
This logs to the browser console in production builds. If this is meant as a dev-only easter egg, consider gating it behind process.env.NODE_ENV !== "production" (or a feature flag) to avoid polluting user consoles and potential automated log capture.
| d="M0 12 L20 12 L24 4 L28 20 L32 8 L36 16 L40 10 L44 14 L48 6 L52 18 L56 11 L60 13 L80 12 L84 10 L88 14 L92 7 L96 17 L100 12 L120 12 L124 5 L128 19 L132 9 L136 15 L140 12 L160 12 L164 11 L168 13 L172 8 L176 16 L180 10 L184 14 L188 12 L208 12 L212 4 L216 20 L220 9 L224 15 L228 12 L248 12 L252 10 L256 14 L260 6 L264 18 L268 12 L288 12 L292 11 L296 13 L300 8 L304 16 L308 12 L328 12 L332 5 L336 19 L340 10 L344 14 L348 12 L368 12 L372 9 L376 15 L380 11 L384 13 L400 12" | ||
| stroke="url(#wave-gradient)" | ||
| strokeWidth="1" | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| /> | ||
| <defs> | ||
| <linearGradient id="wave-gradient" x1="0" y1="0" x2="1" y2="0"> | ||
| <stop offset="0%" stopColor="currentColor" stopOpacity="0" /> |
There was a problem hiding this comment.
linearGradient uses a hard-coded id (wave-gradient). If more than one is ever rendered on the same page, the duplicated ids can cause incorrect gradient references. Consider generating a unique id per component instance (e.g., via useId) and using it in both the id and stroke URL reference.
| - **Styling**: Tailwind CSS v4 with custom brand color palette | ||
| - **UI Components**: shadcn/ui (new-york style) | ||
| - **Icons**: Phosphor Icons (@phosphor-icons/react) | ||
| - **Fonts**: Geist Sans & Geist Mono |
There was a problem hiding this comment.
AGENTS.md lists only Geist Sans/Mono under Fonts, but the app now also adds Instrument Serif in app/layout.tsx. Update this section so agent guidance reflects the current typography stack.
| - **Fonts**: Geist Sans & Geist Mono | |
| - **Fonts**: Geist Sans, Geist Mono, & Instrument Serif |
| import { DevGreeting } from "@/components/dev-greeting"; | ||
| import { Footer } from "@/components/footer"; | ||
| import { NewsletterSignup } from "@/components/newsletter-signup"; | ||
| import { ProfileHeader } from "@/components/profile-header"; | ||
| import { ProjectsSection } from "@/components/projects-section"; | ||
| import { SocialLinksSection } from "@/components/social-links-section"; | ||
| import { FlickeringGrid } from "@/components/ui/flickering-grid"; | ||
| import { Backdrop } from "@/components/ui/backdrop"; |
There was a problem hiding this comment.
NewsletterSignup was removed from the page and metadata no longer references the newsletter, but the newsletter UI component and /api/newsletter route still exist in the repo and appear unused. Consider either removing that unused feature (and env var expectations) or reintroducing it in the new layout so the codebase doesn’t accumulate dead code.
There was a problem hiding this comment.
Actionable comments posted: 11
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@AGENTS.md`:
- Around line 19-55: The markdown has lint violations: add a blank line after
each top-level heading ("Tech Stack", "Project Structure", "Brand Color System",
"Path Aliases") to satisfy MD022, ensure the fenced code block that lists the
project tree has an empty blank line before and after it to satisfy MD031, and
declare the fenced block language as text (change ``` to ```text) to satisfy
MD040; update the headings and the block around the component tree in AGENTS.md
accordingly so the linter passes.
In `@app/globals.css`:
- Around line 203-211: .grain is absolutely positioned but assumes a positioned
ancestor; update the stylesheet to either document this contract and/or add a
helper class so callers have a known context. Specifically, add a short comment
explaining that .grain requires a positioned container, and introduce a
companion selector (e.g., .grain-host) that sets positioning and clipping
(position: relative; overflow: hidden; or contain: strict) so .grain will be
contained — reference .grain and the new .grain-host in the comment and
docstring so callers know to wrap grain with .grain-host.
- Around line 132-226: Insert a blank line before the declaration
font-feature-settings (the rule containing font-feature-settings) to satisfy
declaration-empty-line-before, and change any occurrences of currentColor to
lowercase currentcolor in the .underline-grow background-image declaration;
leave the existing keyframes name fadeInUp and its usage in .animate-fade-in-up
unchanged (or add a stylelint disable comment if you want to silence
keyframes-name-pattern).
In `@app/page.tsx`:
- Around line 15-21: The <nav> element in app/page.tsx currently only contains
branding and a version label and should not expose a navigation landmark;
replace the <nav className="..."> wrapper (the element that contains the brand
span with class "group/nav" and the version span) with a <header> (or a
non-landmark container such as <div role="presentation">) to avoid misleading
screen-reader navigation lists, keeping the inner spans and classes unchanged.
In `@components/profile-header.tsx`:
- Around line 31-48: The screen reader may read the per-letter spans of the
surname individually; add an aria-label to the h1 (the element with
className="group/name ...") such as aria-label="Keith Elliott" and mark the
decorative split-letter span (the span wrapping {ELLIOTT.split(...).map(...)})
as aria-hidden="true" so the visual animation is preserved but screen readers
announce the full name once; update the h1 and the inner span accordingly
(references: ELLIOTT constant, the <h1> element and the decorative inline-flex
<span>).
In `@components/project-card.tsx`:
- Around line 15-19: The stagger delay on the article is a no-op because the
entrance animation class (animate-fade-in-up) is on the parent grid; move the
animate-fade-in-up class from the parent grids (projects-section.tsx and
projects-accordion.tsx) onto each card's root element (the <article> in
project-card.tsx) so the per-index style={{ animationDelay: `${index * 80}ms` }}
takes effect, and remove the animate-fade-in-up from the parent containers to
avoid double-animation.
In `@components/social-links-section.tsx`:
- Line 54: The per-row stagger using style={{ animationDelay: `${index * 60}ms`
}} in components/social-links-section.tsx is dead because no animation utility
is applied; either remove that inline animationDelay or add an entrance
animation class (e.g. animate-fade-in-up) to the row anchor so the delay takes
effect. Locate the anchor/map row where index is used (the element with the
inline style) and either delete the animationDelay prop or add the entrance
animation utility to its className (keeping the same index-based delay) so the
stagger becomes meaningful.
- Around line 44-70: The hover wipe fails because the named group
"group/channel" is applied to the <a> (descendant) while the sliding background
<span> is a sibling; move the "group/channel" class from the anchor element (the
<a> that currently has className="group/channel ...") to the parent list item
<li key={link.name} ...> so both the background span and the anchor share the
same group scope, then remove "group/channel" from the anchor's className and
keep the group-hover/channel:* utilities on the background span and anchor
children.
In `@components/ui/spectrum.tsx`:
- Around line 30-40: Touch taps can leave bars stuck because pointerleave may
not fire; update the component so taps reset the cursor: add onPointerUp and
onPointerCancel handlers that call handleLeave (or call setCursor(null)) and/or
restrict onPointerDown to only handle mouse pointers by checking
event.pointerType before invoking handleMove. Modify the element that currently
uses onPointerDown={handleMove} to either gate by event.pointerType or wire up
onPointerUp/onPointerCancel to the existing handleLeave to ensure cursor is
cleared after touch.
- Line 38: Replace the template-literal class concatenation with the cn() class
merger: import cn from 'lib/utils' (or the existing named export) at the top of
components/ui/spectrum.tsx and wrap the base classes and incoming className in
cn(), e.g. change the className assignment that currently uses
`className={\`group relative flex items-end gap-[3px] h-4 ${className ?? ""}\`}`
to use `className={cn("group relative flex items-end gap-[3px] h-4",
className)}` so Tailwind utilities are merged/deduped correctly; ensure you
reference the cn symbol and the className prop in your change.
In `@components/ui/waveform.tsx`:
- Around line 8-31: The SVG gradient id "wave-gradient" and its reference
url(`#wave-gradient`) are hardcoded and will collide when multiple Waveform
components render; update the Waveform component to generate a stable unique id
via React.useId() (or similar) and use that id for both the <linearGradient
id=...> and the path's stroke="url(#...)" so each instance has a distinct
gradient reference (replace literal "wave-gradient" in both places with the
generated id).
🪄 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: ASSERTIVE
Plan: Pro
Run ID: 41335351-b4cd-4a8e-970b-6437c142fab1
📒 Files selected for processing (16)
AGENTS.mdapp/globals.cssapp/layout.tsxapp/manifest.tsapp/page.tsxcomponents/dev-greeting.tsxcomponents/footer.tsxcomponents/profile-header.tsxcomponents/project-card.tsxcomponents/projects-accordion.tsxcomponents/projects-section.tsxcomponents/social-links-section.tsxcomponents/ui/backdrop.tsxcomponents/ui/flickering-grid.tsxcomponents/ui/spectrum.tsxcomponents/ui/waveform.tsx
💤 Files with no reviewable changes (1)
- components/ui/flickering-grid.tsx
| .grain { | ||
| position: absolute; | ||
| inset: -5%; | ||
| pointer-events: none; | ||
| opacity: 0.035; | ||
| mix-blend-mode: overlay; | ||
| background-image: url("data:image/svg+xml;utf8,<svg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1, 0 0 0 0 1, 0 0 0 0 1, 0 0 0 0.6 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>"); | ||
| animation: grain-shift 8s steps(4) infinite; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
.grain assumes a positioned ancestor — document or enforce.
.grain uses position: absolute; inset: -5%; without any self-contained positioning context. Any caller that applies it to a child whose nearest ancestor isn't positioned will see the grain escape to the viewport. Since this is a shared utility, either (a) add a brief comment stating the contract, or (b) add contain: strict / pair with a documented .grain-host { position: relative; overflow: hidden; } helper.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/globals.css` around lines 203 - 211, .grain is absolutely positioned but
assumes a positioned ancestor; update the stylesheet to either document this
contract and/or add a helper class so callers have a known context.
Specifically, add a short comment explaining that .grain requires a positioned
container, and introduce a companion selector (e.g., .grain-host) that sets
positioning and clipping (position: relative; overflow: hidden; or contain:
strict) so .grain will be contained — reference .grain and the new .grain-host
in the comment and docstring so callers know to wrap grain with .grain-host.
| onPointerMove={handleMove} | ||
| onPointerLeave={handleLeave} | ||
| onPointerDown={handleMove} | ||
| className={`group relative flex items-end gap-[3px] h-4 ${className ?? ""}`} |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Use cn() for class merging.
Template-literal concatenation works but will not de-duplicate conflicting utilities when callers pass overriding classes. As per coding guidelines, components/**/*.{tsx,ts} should use the cn() utility from lib/utils.ts for Tailwind class merging.
♻️ Proposed refactor
-import { type PointerEvent, useCallback, useRef, useState } from "react";
+import { type PointerEvent, useCallback, useRef, useState } from "react";
+import { cn } from "@/lib/utils";
...
- className={`group relative flex items-end gap-[3px] h-4 ${className ?? ""}`}
+ className={cn("group relative flex items-end gap-[3px] h-4", className)}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/ui/spectrum.tsx` at line 38, Replace the template-literal class
concatenation with the cn() class merger: import cn from 'lib/utils' (or the
existing named export) at the top of components/ui/spectrum.tsx and wrap the
base classes and incoming className in cn(), e.g. change the className
assignment that currently uses `className={\`group relative flex items-end
gap-[3px] h-4 ${className ?? ""}\`}` to use `className={cn("group relative flex
items-end gap-[3px] h-4", className)}` so Tailwind utilities are merged/deduped
correctly; ensure you reference the cn symbol and the className prop in your
change.
Remove the "v4.0 · 2026" stamp from the nav and the "432 Hz" label next to the spectrum (the bars stand on their own). Point the X social link at x.com instead of the legacy twitter.com domain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
♻️ Duplicate comments (3)
app/page.tsx (1)
15-20:⚠️ Potential issue | 🟡 MinorUse a non-navigation wrapper for the brand row.
This
<nav>exposes a navigation landmark but contains no links or navigation controls. Use<header>or a plain container instead.Proposed fix
- <nav className="mb-10 md:mb-16 flex items-center font-mono text-[10px] tracking-[0.25em] uppercase text-ink-500"> + <header className="mb-10 md:mb-16 flex items-center font-mono text-[10px] tracking-[0.25em] uppercase text-ink-500"> <span className="group/nav flex items-center gap-2"> <span className="inline-block h-2.5 w-2.5 rotate-45 bg-glow transition-transform duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] group-hover/nav:rotate-[135deg]" /> <span>Resonant / Home</span> </span> - </nav> + </header>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/page.tsx` around lines 15 - 20, The brand row is wrapped in a <nav> but contains no links, so replace that navigation landmark with a non-navigation container: change the <nav className="mb-10 md:mb-16 flex items-center font-mono text-[10px] tracking-[0.25em] uppercase text-ink-500"> element to a semantic <header> or a plain <div> (keeping the same className and child spans, including the "group/nav" span and its inline-block indicator) so the markup no longer exposes an empty navigation landmark while preserving styling and structure.components/social-links-section.tsx (1)
44-54:⚠️ Potential issue | 🟠 MajorFix the row group scope and make the staggered delay active.
The hover wipe still cannot trigger because
group/channelis on the<a>, while the wipe<span>is its sibling. The inlineanimationDelayis also still a no-op without an animation class.Proposed fix
- <li key={link.name} className="relative overflow-hidden"> + <li key={link.name} className="group/channel relative overflow-hidden"> <span aria-hidden="true" className="pointer-events-none absolute inset-0 bg-glow/[0.04] translate-x-[-101%] transition-transform duration-500 ease-[cubic-bezier(0.16,1,0.3,1)] group-hover/channel:translate-x-0" /> <a href={link.url} target={isMail ? undefined : "_blank"} rel={isMail ? undefined : "noopener noreferrer"} - className="group/channel relative flex items-center gap-4 py-3 text-sm text-ink-300 transition-colors hover:text-ink-100" + className="relative flex animate-fade-in-up items-center gap-4 py-3 text-sm text-ink-300 transition-colors hover:text-ink-100" style={{ animationDelay: `${index * 60}ms` }} >In Tailwind CSS v4, do named group-hover variants such as group-hover/channel apply to sibling elements, or only descendants of the element with group/channel?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/social-links-section.tsx` around lines 44 - 54, The hover wipe fails because the named group class ("group/channel") is placed on the <a> while the wipe <span> is its sibling, and the inline animationDelay is no-op without an animation class; move the group/channel class to the parent element that contains both the <span> and <a> (the <li> or another wrapper) so the span can respond to group-hover/channel, and add a real animation class to the element being delayed (e.g., the wipe <span> or the <a>) so the style animationDelay on the element (style={{ animationDelay: `${index * 60}ms` }}) takes effect; update references for group/channel, the wipe <span>, and the inline animationDelay accordingly.components/profile-header.tsx (1)
31-47: 🧹 Nitpick | 🔵 TrivialAdd an accessible name for the split-letter heading.
The animated per-letter surname can be announced awkwardly by some assistive tech. Give the
<h1>a single accessible name and hide the decorative split span.Proposed refactor
- <h1 className="group/name font-display text-5xl md:text-6xl leading-[0.95] tracking-tight text-ink-100"> + <h1 + aria-label="Keith Elliott" + className="group/name font-display text-5xl md:text-6xl leading-[0.95] tracking-tight text-ink-100" + > Keith <br /> - <span className="italic text-glow inline-flex"> + <span aria-hidden="true" className="italic text-glow inline-flex">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/profile-header.tsx` around lines 31 - 47, The H1 currently renders the surname as individual animated letters which may be announced awkwardly; give the <h1 className="group/name"> a single accessible name (e.g., set an aria-label with the full name like "Keith Elliott" or include a visually-hidden full-name node inside the H1) and mark the animated per-letter span (the <span className="italic text-glow inline-flex"> that maps ELLIOTT) as decorative by adding aria-hidden="true" and role="presentation" (and remove it from keyboard flow if necessary), ensuring screen readers only announce the single accessible name.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@app/page.tsx`:
- Around line 15-20: The brand row is wrapped in a <nav> but contains no links,
so replace that navigation landmark with a non-navigation container: change the
<nav className="mb-10 md:mb-16 flex items-center font-mono text-[10px]
tracking-[0.25em] uppercase text-ink-500"> element to a semantic <header> or a
plain <div> (keeping the same className and child spans, including the
"group/nav" span and its inline-block indicator) so the markup no longer exposes
an empty navigation landmark while preserving styling and structure.
In `@components/profile-header.tsx`:
- Around line 31-47: The H1 currently renders the surname as individual animated
letters which may be announced awkwardly; give the <h1 className="group/name"> a
single accessible name (e.g., set an aria-label with the full name like "Keith
Elliott" or include a visually-hidden full-name node inside the H1) and mark the
animated per-letter span (the <span className="italic text-glow inline-flex">
that maps ELLIOTT) as decorative by adding aria-hidden="true" and
role="presentation" (and remove it from keyboard flow if necessary), ensuring
screen readers only announce the single accessible name.
In `@components/social-links-section.tsx`:
- Around line 44-54: The hover wipe fails because the named group class
("group/channel") is placed on the <a> while the wipe <span> is its sibling, and
the inline animationDelay is no-op without an animation class; move the
group/channel class to the parent element that contains both the <span> and <a>
(the <li> or another wrapper) so the span can respond to group-hover/channel,
and add a real animation class to the element being delayed (e.g., the wipe
<span> or the <a>) so the style animationDelay on the element (style={{
animationDelay: `${index * 60}ms` }}) takes effect; update references for
group/channel, the wipe <span>, and the inline animationDelay accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c43fa086-875b-49c9-b88d-e99302af1cc7
📒 Files selected for processing (3)
app/page.tsxcomponents/profile-header.tsxcomponents/social-links-section.tsx
- social-links: move group/channel to <li> so hover wipe actually triggers - spectrum: reset bars on pointerup/cancel so touch taps don't stick - waveform: use useId() for gradient id to avoid collisions - project-card, social-links: drop no-op animationDelay inline styles - page: <nav> → <header> (brand row has no links) - profile-header: aria-label on h1, aria-hidden on split-letter span - dev-greeting: gate console output to non-production builds - agents.md: list Instrument Serif, fix markdownlint nits - globals.css: stylelint fixes (blank line, lowercase currentcolor) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts: # app/layout.tsx # app/page.tsx # components/footer.tsx # components/profile-header.tsx # components/projects-accordion.tsx # components/projects-section.tsx # components/social-links-section.tsx # components/ui/flickering-grid.tsx
Summary by CodeRabbit
New Features
Style
Refactor
Documentation