polish(marketing): hero font, pixel-dithered demos, testimonials, CTA#3563
polish(marketing): hero font, pixel-dithered demos, testimonials, CTA#3563
Conversation
- Hero title: both segments at weight 500; "AI Agents." uses lo-res-21-ot-serif with Pixelify Sans fallback. - Subtitle: "Orchestrate 100+ coding agents in parallel" (keep rest). - Remove vertical grid lines from <main>. - Move ProductDemo pills back under the mockup (no scroll transform). - CTA heading matches FAQ styling; copy now "Try Superset now.". - Add `role` field to Iven's testimonial (Engineer at Paraflow). - TypewriterText: optional per-segment `render` for custom glyph rendering. - Simplify DownloadButton classes (unify with buttonClasses).
|
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 2 minutes and 17 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: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (10)
📝 WalkthroughWalkthroughRemoved platform-specific download gating and the VideoSection; added a lazily-loaded DitheredBackground shader component, typography and styling adjustments across CTA/Hero/TrustedBy/WallOfLove, extended TypewriterText rendering, added brand CSS variables and Pixelify_Sans font, and updated testimonial data with optional roles. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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 |
🚀 Preview Deployment🔗 Preview Links
Preview updates automatically with new commits |
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/marketing/src/app/components/HeroSection/HeroSection.tsx">
<violation number="1">
P2: `AI Agents.` now uses Geist Pixel Grid instead of the Lo-Res/Pixelify font stack, so the Adobe kit follow-up won’t change the hero font as intended.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Greptile SummaryThis is a marketing-site polish PR covering five areas: hero typography upgrades, a new canvas-based Key changes:
Confidence Score: 5/5Safe to merge — purely visual/marketing changes with no data-path or auth impact; the two nits in DitheredBackground are non-blocking. All changes are confined to the marketing site's UI layer. The new DitheredBackground component works correctly for its intended use case (6-digit hex colours from hardcoded constants). The two flagged issues — array-reference instability in the useEffect dependency and the silent NaN in parseHex — are edge cases that don't affect current callers. The Adobe Fonts placeholder is intentional and guarded. No logic regressions, no data exposure, no auth changes. apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/DitheredBackground.tsx — minor useEffect dependency and parseHex robustness nits.
|
| Filename | Overview |
|---|---|
| apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/DitheredBackground.tsx | New canvas-based Bayer-dither background component; works correctly but has two minor nits: array-reference instability in the useEffect dependency and the parseHex helper silently returns NaN for non-6-digit inputs. |
| apps/marketing/src/app/components/HeroSection/components/TypewriterText/TypewriterText.tsx | Adds optional per-segment render prop so individual segments can render custom glyphs while the typewriter reveals characters; implementation is clean and backward-compatible. |
| apps/marketing/src/app/components/WallOfLoveSection/constants.ts | Adds optional role field to all testimonials and swaps one testimonial (kihonushi → Vlad Arbatov); straightforward data update. |
| apps/marketing/src/app/layout.tsx | Adds Pixelify Sans as a font variable and conditionally loads Adobe Fonts via a kit ID constant; kit ID is a placeholder (acknowledged in PR) and the conditional guard prevents any real network request until it is wired up. |
| apps/marketing/src/app/components/HeroSection/components/ProductDemo/ProductDemo.tsx | Removes the scroll-driven pill transform (useTransform + motion.div) and replaces with a static div; simpler and eliminates the framer-motion dependency for this element. |
| apps/marketing/src/app/globals.css | Adds brand color tokens (--brand, --brand-light, --brand-dark) exposed as Tailwind @theme inline vars for the updated CTA button styling. |
| apps/marketing/src/app/components/VideoSection/VideoSection.tsx | File deleted; YouTube embed section removed from the marketing page. |
| apps/marketing/src/app/components/CTAButtons/HeaderCTA.tsx | Download button switches from foreground/background token colours to the new brand colour tokens (bg-brand, border-brand-light). |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[FeatureDemo\ncolors prop] --> B[DitheredBackground]
B --> C{useEffect\non colors ref}
C --> D[parseHex x4\nSort by luminance]
D --> E[FNV-1a hash\nangle + phase]
E --> F[96x72 pixel loop\nBayer-8 ordered dither]
F --> G[ctx.putImageData\ncanvas element\nimageRendering: pixelated]
H[layout.tsx] --> I{ADOBE_FONTS_KIT_ID\nnot YOUR_KIT_ID?}
I -- Yes --> J[Load typekit CSS\nlo-res-22 / lo-res-21-ot-serif]
I -- No --> K[Skip Adobe Fonts\nPixelify Sans fallback]
J & K --> L[HeroSection h1\nlo-res-22 to IBM Plex Mono]
L --> M[TypewriterText segments\nsegment.render optional]
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/DitheredBackground.tsx
Line: 54-98
Comment:
**Array reference causes unnecessary canvas redraws**
`colors` is an array, and React compares `useEffect` dependencies by reference. If any ancestor renders and passes a new inline array literal, the effect re-runs every render — redrawing all 6,912 pixels each time.
The computation is cheap, but a stable comparison is simple to achieve by joining the values into a string:
```suggestion
useEffect(() => {
const canvas = ref.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const palette: Rgb[] = colors
.map(parseHex)
.sort((a, b) => luminance(a) - luminance(b));
const n = palette.length - 1;
const seed = hashStr(colors.join("|"));
const angle = ((seed % 360) * Math.PI) / 180;
const dx = Math.cos(angle);
const dy = Math.sin(angle);
const phase = ((seed >>> 8) % 1000) / 100;
const img = ctx.createImageData(W, H);
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
const nx = x / W - 0.5;
const ny = y / H - 0.5;
let v = 0.5 + (nx * dx + ny * dy) * 0.45;
v += Math.sin(nx * 6 + ny * 4 + phase) * 0.07;
v += Math.sin(nx * 11 - ny * 9 + phase * 2) * 0.04;
v = Math.max(0, Math.min(1, v));
const t = (BAYER_8[y % 8]?.[x % 8] ?? 0) / 64;
const level = Math.max(0, Math.min(n, Math.floor(v * n + t)));
const pick = palette[level] ?? palette[0]!;
const i = (y * W + x) * 4;
img.data[i] = pick[0];
img.data[i + 1] = pick[1];
img.data[i + 2] = pick[2];
img.data[i + 3] = 255;
}
}
ctx.putImageData(img, 0, 0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [colors.join("|")]);
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/DitheredBackground.tsx
Line: 21-28
Comment:
**`parseHex` silently produces `NaN` for non-6-digit strings**
`slice(4, 6)` returns `""` for a 3-character hex shorthand, and `Number.parseInt("", 16)` returns `NaN`. Canvas `ImageData` coerces `NaN` to `0`, so affected pixels render fully black with no runtime error.
The prop type constrains inputs to 4 strings but does not enforce 6-digit hex. A clarifying comment on `parseHex` would prevent silent breakage if a caller later passes a CSS shorthand or a Tailwind-resolved token string.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "revert hero 'AI Agents.' styling to main..." | Re-trigger Greptile
| useEffect(() => { | ||
| const canvas = ref.current; | ||
| if (!canvas) return; | ||
| const ctx = canvas.getContext("2d"); | ||
| if (!ctx) return; | ||
|
|
||
| const palette: Rgb[] = colors | ||
| .map(parseHex) | ||
| .sort((a, b) => luminance(a) - luminance(b)); | ||
| const n = palette.length - 1; | ||
|
|
||
| // Per-card variation so each card looks distinct | ||
| const seed = hashStr(colors.join("|")); | ||
| const angle = ((seed % 360) * Math.PI) / 180; | ||
| const dx = Math.cos(angle); | ||
| const dy = Math.sin(angle); | ||
| const phase = ((seed >>> 8) % 1000) / 100; | ||
|
|
||
| const img = ctx.createImageData(W, H); | ||
|
|
||
| for (let y = 0; y < H; y++) { | ||
| for (let x = 0; x < W; x++) { | ||
| const nx = x / W - 0.5; | ||
| const ny = y / H - 0.5; | ||
|
|
||
| // Gentle directional ramp + low-freq wobble for variety | ||
| let v = 0.5 + (nx * dx + ny * dy) * 0.45; | ||
| v += Math.sin(nx * 6 + ny * 4 + phase) * 0.07; | ||
| v += Math.sin(nx * 11 - ny * 9 + phase * 2) * 0.04; | ||
| v = Math.max(0, Math.min(1, v)); | ||
|
|
||
| // Ordered dither across the full palette (4 levels) | ||
| const t = (BAYER_8[y % 8]?.[x % 8] ?? 0) / 64; | ||
| const level = Math.max(0, Math.min(n, Math.floor(v * n + t))); | ||
| const pick = palette[level] ?? palette[0]!; | ||
|
|
||
| const i = (y * W + x) * 4; | ||
| img.data[i] = pick[0]; | ||
| img.data[i + 1] = pick[1]; | ||
| img.data[i + 2] = pick[2]; | ||
| img.data[i + 3] = 255; | ||
| } | ||
| } | ||
| ctx.putImageData(img, 0, 0); | ||
| }, [colors]); |
There was a problem hiding this comment.
Array reference causes unnecessary canvas redraws
colors is an array, and React compares useEffect dependencies by reference. If any ancestor renders and passes a new inline array literal, the effect re-runs every render — redrawing all 6,912 pixels each time.
The computation is cheap, but a stable comparison is simple to achieve by joining the values into a string:
| useEffect(() => { | |
| const canvas = ref.current; | |
| if (!canvas) return; | |
| const ctx = canvas.getContext("2d"); | |
| if (!ctx) return; | |
| const palette: Rgb[] = colors | |
| .map(parseHex) | |
| .sort((a, b) => luminance(a) - luminance(b)); | |
| const n = palette.length - 1; | |
| // Per-card variation so each card looks distinct | |
| const seed = hashStr(colors.join("|")); | |
| const angle = ((seed % 360) * Math.PI) / 180; | |
| const dx = Math.cos(angle); | |
| const dy = Math.sin(angle); | |
| const phase = ((seed >>> 8) % 1000) / 100; | |
| const img = ctx.createImageData(W, H); | |
| for (let y = 0; y < H; y++) { | |
| for (let x = 0; x < W; x++) { | |
| const nx = x / W - 0.5; | |
| const ny = y / H - 0.5; | |
| // Gentle directional ramp + low-freq wobble for variety | |
| let v = 0.5 + (nx * dx + ny * dy) * 0.45; | |
| v += Math.sin(nx * 6 + ny * 4 + phase) * 0.07; | |
| v += Math.sin(nx * 11 - ny * 9 + phase * 2) * 0.04; | |
| v = Math.max(0, Math.min(1, v)); | |
| // Ordered dither across the full palette (4 levels) | |
| const t = (BAYER_8[y % 8]?.[x % 8] ?? 0) / 64; | |
| const level = Math.max(0, Math.min(n, Math.floor(v * n + t))); | |
| const pick = palette[level] ?? palette[0]!; | |
| const i = (y * W + x) * 4; | |
| img.data[i] = pick[0]; | |
| img.data[i + 1] = pick[1]; | |
| img.data[i + 2] = pick[2]; | |
| img.data[i + 3] = 255; | |
| } | |
| } | |
| ctx.putImageData(img, 0, 0); | |
| }, [colors]); | |
| useEffect(() => { | |
| const canvas = ref.current; | |
| if (!canvas) return; | |
| const ctx = canvas.getContext("2d"); | |
| if (!ctx) return; | |
| const palette: Rgb[] = colors | |
| .map(parseHex) | |
| .sort((a, b) => luminance(a) - luminance(b)); | |
| const n = palette.length - 1; | |
| const seed = hashStr(colors.join("|")); | |
| const angle = ((seed % 360) * Math.PI) / 180; | |
| const dx = Math.cos(angle); | |
| const dy = Math.sin(angle); | |
| const phase = ((seed >>> 8) % 1000) / 100; | |
| const img = ctx.createImageData(W, H); | |
| for (let y = 0; y < H; y++) { | |
| for (let x = 0; x < W; x++) { | |
| const nx = x / W - 0.5; | |
| const ny = y / H - 0.5; | |
| let v = 0.5 + (nx * dx + ny * dy) * 0.45; | |
| v += Math.sin(nx * 6 + ny * 4 + phase) * 0.07; | |
| v += Math.sin(nx * 11 - ny * 9 + phase * 2) * 0.04; | |
| v = Math.max(0, Math.min(1, v)); | |
| const t = (BAYER_8[y % 8]?.[x % 8] ?? 0) / 64; | |
| const level = Math.max(0, Math.min(n, Math.floor(v * n + t))); | |
| const pick = palette[level] ?? palette[0]!; | |
| const i = (y * W + x) * 4; | |
| img.data[i] = pick[0]; | |
| img.data[i + 1] = pick[1]; | |
| img.data[i + 2] = pick[2]; | |
| img.data[i + 3] = 255; | |
| } | |
| } | |
| ctx.putImageData(img, 0, 0); | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, [colors.join("|")]); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/DitheredBackground.tsx
Line: 54-98
Comment:
**Array reference causes unnecessary canvas redraws**
`colors` is an array, and React compares `useEffect` dependencies by reference. If any ancestor renders and passes a new inline array literal, the effect re-runs every render — redrawing all 6,912 pixels each time.
The computation is cheap, but a stable comparison is simple to achieve by joining the values into a string:
```suggestion
useEffect(() => {
const canvas = ref.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const palette: Rgb[] = colors
.map(parseHex)
.sort((a, b) => luminance(a) - luminance(b));
const n = palette.length - 1;
const seed = hashStr(colors.join("|"));
const angle = ((seed % 360) * Math.PI) / 180;
const dx = Math.cos(angle);
const dy = Math.sin(angle);
const phase = ((seed >>> 8) % 1000) / 100;
const img = ctx.createImageData(W, H);
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
const nx = x / W - 0.5;
const ny = y / H - 0.5;
let v = 0.5 + (nx * dx + ny * dy) * 0.45;
v += Math.sin(nx * 6 + ny * 4 + phase) * 0.07;
v += Math.sin(nx * 11 - ny * 9 + phase * 2) * 0.04;
v = Math.max(0, Math.min(1, v));
const t = (BAYER_8[y % 8]?.[x % 8] ?? 0) / 64;
const level = Math.max(0, Math.min(n, Math.floor(v * n + t)));
const pick = palette[level] ?? palette[0]!;
const i = (y * W + x) * 4;
img.data[i] = pick[0];
img.data[i + 1] = pick[1];
img.data[i + 2] = pick[2];
img.data[i + 3] = 255;
}
}
ctx.putImageData(img, 0, 0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [colors.join("|")]);
```
How can I resolve this? If you propose a fix, please make it concise.| function parseHex(hex: string): Rgb { | ||
| const s = hex.replace("#", ""); | ||
| return [ | ||
| Number.parseInt(s.slice(0, 2), 16), | ||
| Number.parseInt(s.slice(2, 4), 16), | ||
| Number.parseInt(s.slice(4, 6), 16), | ||
| ]; | ||
| } |
There was a problem hiding this comment.
parseHex silently produces NaN for non-6-digit strings
slice(4, 6) returns "" for a 3-character hex shorthand, and Number.parseInt("", 16) returns NaN. Canvas ImageData coerces NaN to 0, so affected pixels render fully black with no runtime error.
The prop type constrains inputs to 4 strings but does not enforce 6-digit hex. A clarifying comment on parseHex would prevent silent breakage if a caller later passes a CSS shorthand or a Tailwind-resolved token string.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/DitheredBackground.tsx
Line: 21-28
Comment:
**`parseHex` silently produces `NaN` for non-6-digit strings**
`slice(4, 6)` returns `""` for a 3-character hex shorthand, and `Number.parseInt("", 16)` returns `NaN`. Canvas `ImageData` coerces `NaN` to `0`, so affected pixels render fully black with no runtime error.
The prop type constrains inputs to 4 strings but does not enforce 6-digit hex. A clarifying comment on `parseHex` would prevent silent breakage if a caller later passes a CSS shorthand or a Tailwind-resolved token string.
How can I resolve this? If you propose a fix, please make it concise.Replaces the canvas-based Bayer dither with @paper-design/shaders-react's <Dithering> shader (shape="warp", type="4x4"), lazy-loaded via React.lazy. Rendered at opacity 30% with mix-blend-screen over each card's palette.
There was a problem hiding this comment.
1 issue found across 16 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/marketing/src/app/components/HeroSection/components/ProductDemo/ProductDemo.tsx">
<violation number="1" location="apps/marketing/src/app/components/HeroSection/components/ProductDemo/ProductDemo.tsx:80">
P2: The pills row removed horizontal overflow handling, which can overflow on mobile because pills are `whitespace-nowrap` + `shrink-0` and include long labels.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/marketing/src/app/components/HeroSection/HeroSection.tsx (1)
29-51:⚠️ Potential issue | 🟡 Minor
"AI Agents."segment uses wrong font fallback; reconcile version number with layout.tsx documentation.The "AI Agents." segment on line 44 uses
var(--font-geist-pixel-grid)instead ofvar(--font-pixel)(Pixelify Sans). However, there is a conflict in your suggested fix: the comment inlayout.tsxline 53 explicitly states "LoRes 22 OT", yet you suggested changing the family slug to"lo-res-21". This contradicts the documented version. Verify whether the intended version is Lo-Res 21 or 22, then align both the outer<h1>style and the "AI Agents." segment fallback accordingly—either:
- Keep
"lo-res-22"withvar(--font-pixel)as the fallback, or- Change to
"lo-res-21"withvar(--font-pixel)as the fallback (if 21 is correct)Until
ADOBE_FONTS_KIT_IDis configured, the segment will render in Geist Pixel Grid instead of the intended fallback.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/marketing/src/app/components/HeroSection/HeroSection.tsx` around lines 29 - 51, The "AI Agents." TypewriterText segment is using the wrong CSS fallback font and the Lo-Res version is inconsistent with layout.tsx; decide whether the intended Lo-Res version is "lo-res-22" (as documented) or "lo-res-21" and make both the outer h1 style and the TypewriterText segment use the same family and Pixelify fallback: update the h1 fontFamily entry (currently '"lo-res-22", var(--font-ibm-plex-mono), monospace' or change to '"lo-res-21", ...' per your decision) and change the TypewriterText segment style from var(--font-geist-pixel-grid) to var(--font-pixel) so both the outer element and the "AI Agents." segment reference the same Lo-Res slug and the Pixelify fallback; ensure layout.tsx documentation and the chosen Lo-Res slug are reconciled.
🧹 Nitpick comments (2)
apps/marketing/src/app/components/WallOfLoveSection/WallOfLoveSection.tsx (1)
41-57: Consider preserving the handle when a role is shown.With
testimonial.role ?? testimonial.handle, the Twitter/X handle disappears whenever a role is set (which is now nearly every testimonial). Since the card already links to X, losing the handle is probably fine, but some readers use the handle to recognize the author. If intentional per design, ignore; otherwise consider rendering both, e.g.role ·@handle`` or showing the handle as a smaller secondary line.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/marketing/src/app/components/WallOfLoveSection/WallOfLoveSection.tsx` around lines 41 - 57, Replace the single-field fallback expression testimonial.role ?? testimonial.handle in WallOfLoveSection so the handle isn't dropped when a role exists; update the render logic in the WallOfLoveSection component (where Avatar and author are rendered) to display both values (for example: show testimonial.role followed by a separator and testimonial.handle or render the handle as a smaller secondary line) while preserving existing link and styling classes; ensure you reference the same JSX block that currently contains <span className="text-muted-foreground text-sm">{testimonial.role ?? testimonial.handle}</span> and keep accessibility and layout intact.apps/marketing/src/app/components/CTAButtons/HeaderCTA.tsx (1)
64-72: Consider unifying download CTA styling withDownloadButton.
DownloadButton.tsxstill usesbg-foreground text-backgroundfor the same "Download" action, while this macOS link now usesbg-brand text-white border border-brand-light. Users will see two visually distinct primary download CTAs on the marketing site depending on context. If the brand treatment is intentional for the header, consider propagating it toDownloadButton(or vice-versa) so the visual hierarchy stays consistent.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/marketing/src/app/components/CTAButtons/HeaderCTA.tsx` around lines 64 - 72, HeaderCTA renders a macOS download link styled with "bg-brand text-white border border-brand-light" while DownloadButton.tsx uses "bg-foreground text-background", causing inconsistent primary CTA visuals; decide on the correct brand treatment and apply it consistently: update either HeaderCTA's anchor or DownloadButton component to use the chosen class set (referencing HeaderCTA, DOWNLOAD_URL_MAC_ARM64, and DownloadButton) so both download CTAs share the same utility classes and visual hierarchy, and ensure any onClick tracking (track("download_clicked")) remains wired in the unified component.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/DitheredBackground.tsx`:
- Around line 101-108: The canvas in DitheredBackground is currently using
aria-hidden="true" which Biome flags; remove the aria-hidden attribute and mark
the decorative canvas with a presentational role instead (e.g.,
role="presentation" or role="none") on the <canvas> element that uses ref,
width={W}, height={H}, className and style so screen readers ignore it
correctly; after the change run biome check --write --unsafe to format and
ensure lint rules pass.
- Around line 21-27: The parseHex function should validate and normalize
incoming hex strings: accept and normalize 3-digit (`#abc`) to 6-digit (`#aabbcc`),
accept 6-digit (`#aabbcc`), strip a leading "#" and ensure the remaining
characters match /^[0-9a-fA-F]{6}$/, and if not, throw or return a clear
error/default instead of producing NaNs; update parseHex (and reference the Rgb
return type) to perform these checks and normalization before calling
Number.parseInt so malformed inputs (CSS vars, rgb() strings, short hex, or
other strings) are rejected or handled safely.
In `@apps/marketing/src/app/components/WallOfLoveSection/constants.ts`:
- Around line 99-104: The testimonial entry for author "Iven" (handle "@ivenvd")
in the WallOfLoveSection constants has an inferred role "Engineer at Paraflow"
that must be verified; contact Iven or check his current public bio and then
update the role field in the object for author "Iven" (or temporarily
remove/clear the role value and add a TODO comment) so the public-facing
attribution is accurate—locate the object with author: "Iven" / handle:
"@ivenvd" in constants.ts and change the role property accordingly once
confirmed.
In `@apps/marketing/src/app/layout.tsx`:
- Around line 53-55: Replace the hardcoded ADOBE_FONTS_KIT_ID constant with
reading process.env.NEXT_PUBLIC_ADOBE_FONTS_KIT_ID (keep a sensible
no-op/fallback when unset) and update any conditional checks that rely on
ADOBE_FONTS_KIT_ID to treat an empty/unset value as false (e.g., change
ADOBE_FONTS_KIT_ID && (...) logic). Also reconcile the font-family name between
this file and HeroSection.tsx by confirming the intended Adobe Fonts family slug
(Lo-Res 21 OT Serif vs lo-res-22), then update the comment, the kit/env usage,
and the fontFamily string in HeroSection.tsx so they all match the confirmed
font slug.
---
Outside diff comments:
In `@apps/marketing/src/app/components/HeroSection/HeroSection.tsx`:
- Around line 29-51: The "AI Agents." TypewriterText segment is using the wrong
CSS fallback font and the Lo-Res version is inconsistent with layout.tsx; decide
whether the intended Lo-Res version is "lo-res-22" (as documented) or
"lo-res-21" and make both the outer h1 style and the TypewriterText segment use
the same family and Pixelify fallback: update the h1 fontFamily entry (currently
'"lo-res-22", var(--font-ibm-plex-mono), monospace' or change to '"lo-res-21",
...' per your decision) and change the TypewriterText segment style from
var(--font-geist-pixel-grid) to var(--font-pixel) so both the outer element and
the "AI Agents." segment reference the same Lo-Res slug and the Pixelify
fallback; ensure layout.tsx documentation and the chosen Lo-Res slug are
reconciled.
---
Nitpick comments:
In `@apps/marketing/src/app/components/CTAButtons/HeaderCTA.tsx`:
- Around line 64-72: HeaderCTA renders a macOS download link styled with
"bg-brand text-white border border-brand-light" while DownloadButton.tsx uses
"bg-foreground text-background", causing inconsistent primary CTA visuals;
decide on the correct brand treatment and apply it consistently: update either
HeaderCTA's anchor or DownloadButton component to use the chosen class set
(referencing HeaderCTA, DOWNLOAD_URL_MAC_ARM64, and DownloadButton) so both
download CTAs share the same utility classes and visual hierarchy, and ensure
any onClick tracking (track("download_clicked")) remains wired in the unified
component.
In `@apps/marketing/src/app/components/WallOfLoveSection/WallOfLoveSection.tsx`:
- Around line 41-57: Replace the single-field fallback expression
testimonial.role ?? testimonial.handle in WallOfLoveSection so the handle isn't
dropped when a role exists; update the render logic in the WallOfLoveSection
component (where Avatar and author are rendered) to display both values (for
example: show testimonial.role followed by a separator and testimonial.handle or
render the handle as a smaller secondary line) while preserving existing link
and styling classes; ensure you reference the same JSX block that currently
contains <span className="text-muted-foreground text-sm">{testimonial.role ??
testimonial.handle}</span> and keep accessibility and layout intact.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 581baff2-cd0b-481c-a399-fd6eec496c6b
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
apps/marketing/src/app/components/CTAButtons/HeaderCTA.tsxapps/marketing/src/app/components/CTASection/CTASection.tsxapps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/FeatureDemo.tsxapps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/DitheredBackground.tsxapps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/index.tsapps/marketing/src/app/components/HeroSection/HeroSection.tsxapps/marketing/src/app/components/HeroSection/components/ProductDemo/ProductDemo.tsxapps/marketing/src/app/components/HeroSection/components/TypewriterText/TypewriterText.tsxapps/marketing/src/app/components/VideoSection/VideoSection.tsxapps/marketing/src/app/components/VideoSection/index.tsapps/marketing/src/app/components/WallOfLoveSection/WallOfLoveSection.tsxapps/marketing/src/app/components/WallOfLoveSection/constants.tsapps/marketing/src/app/globals.cssapps/marketing/src/app/layout.tsxapps/marketing/src/app/page.tsx
💤 Files with no reviewable changes (3)
- apps/marketing/src/app/components/VideoSection/index.ts
- apps/marketing/src/app/page.tsx
- apps/marketing/src/app/components/VideoSection/VideoSection.tsx
| author: "Iven", | ||
| handle: "@ivenvd", | ||
| role: "Engineer at Paraflow", | ||
| avatar: "https://unavatar.io/twitter/ivenvd", | ||
| url: "https://x.com/ivenvd/status/2011738469610242559", | ||
| }, |
There was a problem hiding this comment.
Verify Iven's role accuracy before publishing.
The PR description notes that "Engineer at Paraflow" for @ivenvd was inferred from his GitHub profile and may be incorrect. Since this is a public-facing testimonial attribution, please confirm directly with Iven (or via his current bio) before shipping to avoid misrepresenting his affiliation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/marketing/src/app/components/WallOfLoveSection/constants.ts` around
lines 99 - 104, The testimonial entry for author "Iven" (handle "@ivenvd") in
the WallOfLoveSection constants has an inferred role "Engineer at Paraflow" that
must be verified; contact Iven or check his current public bio and then update
the role field in the object for author "Iven" (or temporarily remove/clear the
role value and add a TODO comment) so the public-facing attribution is
accurate—locate the object with author: "Iven" / handle: "@ivenvd" in
constants.ts and change the role property accordingly once confirmed.
| // LoRes 22 OT ships via Adobe Fonts — replace `YOUR_KIT_ID` with the Typekit kit | ||
| // ID (the slug in https://use.typekit.net/<kit>.css) once the project is created. | ||
| const ADOBE_FONTS_KIT_ID = "YOUR_KIT_ID"; |
There was a problem hiding this comment.
Prefer an env var over a hardcoded sentinel, and fix the font-name typo.
Two issues here:
ADOBE_FONTS_KIT_IDis a module-level constant set to"YOUR_KIT_ID". The PR description describes this as a value to be "provided" later — usingprocess.env.NEXT_PUBLIC_ADOBE_FONTS_KIT_ID(with a sensible fallback/no-op when unset) would avoid a code change + redeploy each time the kit ID rotates, and keeps the placeholder out of source control.- The comment says "LoRes 22 OT", but the PR objective specifies Lo-Res 21 OT Serif, while
HeroSection.tsxreferences"lo-res-22". One of these is wrong; please confirm the intended Adobe Fonts family slug and align the comment, the kit, and thefontFamilystring.
Suggested refactor
-// LoRes 22 OT ships via Adobe Fonts — replace `YOUR_KIT_ID` with the Typekit kit
-// ID (the slug in https://use.typekit.net/<kit>.css) once the project is created.
-const ADOBE_FONTS_KIT_ID = "YOUR_KIT_ID";
+// Lo-Res 21 OT Serif ships via Adobe Fonts. Set
+// NEXT_PUBLIC_ADOBE_FONTS_KIT_ID to the Typekit kit slug
+// (from https://use.typekit.net/<kit>.css) to enable it.
+const ADOBE_FONTS_KIT_ID = process.env.NEXT_PUBLIC_ADOBE_FONTS_KIT_ID;And update the conditional on line 138 to ADOBE_FONTS_KIT_ID && (...).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/marketing/src/app/layout.tsx` around lines 53 - 55, Replace the
hardcoded ADOBE_FONTS_KIT_ID constant with reading
process.env.NEXT_PUBLIC_ADOBE_FONTS_KIT_ID (keep a sensible no-op/fallback when
unset) and update any conditional checks that rely on ADOBE_FONTS_KIT_ID to
treat an empty/unset value as false (e.g., change ADOBE_FONTS_KIT_ID && (...)
logic). Also reconcile the font-family name between this file and
HeroSection.tsx by confirming the intended Adobe Fonts family slug (Lo-Res 21 OT
Serif vs lo-res-22), then update the comment, the kit/env usage, and the
fontFamily string in HeroSection.tsx so they all match the confirmed font slug.
Summary
AI Agents.styling restored to match main (var(--font-geist-pixel-grid)).MeshGradientwith@paper-design/shaders-react'<Dithering>shader (shape="warp",type="4x4"), lazy-loaded. Rendered atopacity: 0.3withmix-blend-screenover each card's palette. Rounded corners removed on the outer cards.rolefield (e.g. "Co-founder & CTO at Mastra", "Engineer at Paraflow"); swapped kihonushi for Vlad Arbatov; removedrounded-xlon cards.render(visibleText)per segment so individual segments can render custom glyphs while still typing char-by-char.Notes / follow-ups
@paraflow-hq, updated 2026-04-02). Swap to Founding Engineer / other title if you know better.Test plan
@handle; circular avatars unchanged.Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Style
Removed Features