Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/app/[locale]/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
--button-primary-hover-bg: var(--color-neutral-50);
--button-primary-hover-border: #121212;
--button-subtle-bg: #9797971e;

/* Background Tile Colors */
--tile-bg: rgba(227, 226, 246);
--tile-stroke: rgba(80, 80, 80, 0.1);
--line-color: rgba(0, 0, 0, 0.05);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid rgba() syntax — missing alpha value.

rgba(227, 226, 246) provides only 3 values but rgba() requires 4 (r, g, b, a). This will cause the property to be ignored or produce unexpected results. Use rgb() for opaque colors or add the alpha channel.

🐛 Proposed fix
   /* Background Tile Colors */
-  --tile-bg: rgba(227, 226, 246);
+  --tile-bg: rgb(227, 226, 246);
   --tile-stroke: rgba(80, 80, 80, 0.1);
   --line-color: rgba(0, 0, 0, 0.05);
📝 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.

Suggested change
/* Background Tile Colors */
--tile-bg: rgba(227, 226, 246);
--tile-stroke: rgba(80, 80, 80, 0.1);
--line-color: rgba(0, 0, 0, 0.05);
/* Background Tile Colors */
--tile-bg: rgb(227, 226, 246);
--tile-stroke: rgba(80, 80, 80, 0.1);
--line-color: rgba(0, 0, 0, 0.05);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/globals.css around lines 40 - 43, The CSS variable
--tile-bg uses invalid rgba() syntax with three components; update the
declaration for --tile-bg to use a valid color function (either change rgba(227,
226, 246) to rgb(227, 226, 246) for an opaque color or add an alpha channel like
rgba(227, 226, 246, 1)). Ensure --tile-bg is corrected alongside the existing
--tile-stroke and --line-color variables so all color values use valid CSS color
syntax.

}

/* DARK MODE */
Expand Down Expand Up @@ -64,6 +69,11 @@
--button-primary-hover-bg: var(--color-neutral-100);
--button-primary-hover-border: var(--color-neutral-100);
--button-subtle-bg: #bbbbbb0e;

/* Background Tile Colors */
--tile-bg: rgba(30, 29, 28);
--tile-stroke: rgba(80, 80, 80, 0.5);
--line-color: rgba(0, 0, 0, 0.2);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Same rgba() syntax issue in dark mode.

Apply the same fix here for consistency.

🐛 Proposed fix
   /* Background Tile Colors */
-  --tile-bg: rgba(30, 29, 28);
+  --tile-bg: rgb(30, 29, 28);
   --tile-stroke: rgba(80, 80, 80, 0.5);
   --line-color: rgba(0, 0, 0, 0.2);
📝 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.

Suggested change
/* Background Tile Colors */
--tile-bg: rgba(30, 29, 28);
--tile-stroke: rgba(80, 80, 80, 0.5);
--line-color: rgba(0, 0, 0, 0.2);
/* Background Tile Colors */
--tile-bg: rgb(30, 29, 28);
--tile-stroke: rgba(80, 80, 80, 0.5);
--line-color: rgba(0, 0, 0, 0.2);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/globals.css around lines 73 - 76, The dark-mode CSS
variables --tile-bg, --tile-stroke, and --line-color use invalid/ambiguous
rgba() syntax; update them to the corrected color-function form used elsewhere
(e.g., include an explicit alpha or use the modern slash syntax) so they parse
consistently in dark mode—for example replace rgba(30, 29, 28) with a proper
value like rgba(30,29,28,1) or rgb(30 29 28 / 1), and similarly ensure
--tile-stroke and --line-color include their alpha channels correctly (e.g.,
rgba(80,80,80,0.5) and rgba(0,0,0,0.2)).

}

/* --- GLOBAL STYLES --- */
Expand Down Expand Up @@ -96,5 +106,4 @@ body {
/* Dark Mode: Icon flips to White */
:is(.dark .theme-icon) {
filter: invert(1) brightness(100);
}

}
187 changes: 187 additions & 0 deletions src/components/ui/BackgroundTile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import { useEffect, useRef } from "react";
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Stable random values generated once at module level — fixes "impure function in render" warning
const PHASES = Array.from({ length: 6 }, () => Math.random() * Math.PI * 2);
const FREQS = Array.from({ length: 6 }, (_, i) => 0.3 + i * 0.13);

interface BackgroundTileProps {
/** Height of the tile in viewport height units. Defaults to 50. */
heightVh?: number;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The heightVh prop is defined but never applied.

The interface documents heightVh as controlling tile height in viewport units (default 50), but the component never uses this value. The container relies on h-full from parent sizing instead.

🐛 Proposed fix to apply the height prop
   return (
     <div
       style={{
         background:   "var(--tile-bg, `#1a1a1a`)",
         border:       "0.8px solid var(--tile-stroke, rgba(255,255,255,0.08))",
         borderRadius: "24px",
-        width:        "100%",
+        height:       `${heightVh}vh`,
         overflow:     "hidden",
         position:     "relative",
       }}
-      className="w-full h-full"
+      className="w-full"
     >

Also applies to: 195-206

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/BackgroundTile.tsx` around lines 9 - 12,
BackgroundTileProps declares heightVh but the BackgroundTile component never
uses it; update the BackgroundTile component to accept heightVh (default 50) and
apply it to the tile container (e.g., via an inline style or computed class) so
the tile height is set to `${heightVh}vh` instead of relying on h-full;
reference the prop name heightVh and the interface BackgroundTileProps when
making the change and ensure the same fix is applied to the other instance noted
(lines ~195-206) so both components honor the heightVh prop.


const BackgroundTile = ({ heightVh = 50 }: BackgroundTileProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const rafRef = useRef<number | null>(null);
const isRunning = useRef(false);
const timeRef = useRef(0);
const ampRef = useRef(0); // target amplitude
const ampSmoothed = useRef(0); // smoothed amplitude (what's actually rendered)
const stopTimer = useRef<ReturnType<typeof setTimeout> | null>(null);

useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;

const ctx = canvas.getContext("2d");
if (!ctx) return;

// ── resize ────────────────────────────────────────────────────────
const setSize = () => {
const dpr = window.devicePixelRatio || 1;
canvas.width = canvas.offsetWidth * dpr;
canvas.height = canvas.offsetHeight * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
};
setSize();
const ro = new ResizeObserver(setSize);
ro.observe(canvas);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

// ── render one frame ──────────────────────────────────────────────
const render = () => {
const w = canvas.offsetWidth;
const h = canvas.offsetHeight;
const amp = ampSmoothed.current;
const t = timeRef.current;

ctx.clearRect(0, 0, w, h);

const N = 6;
const pts = Array.from({ length: N }, (_, i) => {
const w1 = Math.sin(t * FREQS[i] + PHASES[i]) * 0.50;
const w2 = Math.sin(t * FREQS[i] * 0.43 + PHASES[i] * 1.77) * 0.30;
const w3 = Math.cos(t * FREQS[i] * 0.27 + PHASES[i] * 0.85) * 0.20;
return {
x: (i / (N - 1)) * w,
y: h * 0.5 + (w1 + w2 + w3) * h * amp * 0.95,
};
});

ctx.beginPath();
ctx.moveTo(pts[0].x, pts[0].y);

for (let i = 0; i < N - 1; i++) {
const p0 = pts[Math.max(0, i - 1)];
const p1 = pts[i];
const p2 = pts[i + 1];
const p3 = pts[Math.min(N - 1, i + 2)];
const k = 0.5;
ctx.bezierCurveTo(
p1.x + (p2.x - p0.x) * k / 3,
p1.y + (p2.y - p0.y) * k / 3,
p2.x - (p3.x - p1.x) * k / 3,
p2.y - (p3.y - p1.y) * k / 3,
p2.x, p2.y,
);
}

ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue("--line-color").trim();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
ctx.lineWidth = 14;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.stroke();
};

render(); // initial flat line

// ── loop — only alive while scrolling ─────────────────────────────
const tick = () => {
if (!isRunning.current) {
// Smoothly finish lerping to the frozen target, then stop
const target = ampRef.current;
const current = ampSmoothed.current;
ampSmoothed.current += (target - current) * 0.18;

if (Math.abs(ampSmoothed.current - target) > 0.001) {
render();
rafRef.current = requestAnimationFrame(tick);
} else {
ampSmoothed.current = target; // snap to exact target
render();
rafRef.current = null;
}
return;
}

timeRef.current += 0.055;

// Lerp smoothed amp toward target — fast rise
const target = ampRef.current;
const current = ampSmoothed.current;
ampSmoothed.current += (target - current) * 0.18;

render();
rafRef.current = requestAnimationFrame(tick);
};

const startLoop = () => {
if (!isRunning.current) {
isRunning.current = true;
if (rafRef.current === null) {
rafRef.current = requestAnimationFrame(tick);
}
}
};

const stopLoop = () => {
// Stop animating time — amplitude stays frozen at current ampRef value
isRunning.current = false;
};

// ── scroll / wheel ─────────────────────────────────────────────────
const onWheel = (e: WheelEvent) => {
const vel = Math.min(Math.abs(e.deltaY) / 60, 1);
ampRef.current = 0.18 + vel * 0.74;
startLoop();
if (stopTimer.current) clearTimeout(stopTimer.current);
stopTimer.current = setTimeout(stopLoop, 80);
};

const onScroll = () => {
ampRef.current = 0.55;
startLoop();
if (stopTimer.current) clearTimeout(stopTimer.current);
stopTimer.current = setTimeout(stopLoop, 80);
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

window.addEventListener("wheel", onWheel, { passive: true });
window.addEventListener("scroll", onScroll, { passive: true });
canvas.addEventListener("wheel", onWheel, { passive: true });
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

return () => {
stopLoop();
ro.disconnect();
if (rafRef.current) cancelAnimationFrame(rafRef.current);
if (stopTimer.current) clearTimeout(stopTimer.current);
window.removeEventListener("wheel", onWheel);
window.removeEventListener("scroll", onScroll);
canvas.removeEventListener("wheel", onWheel);
};
}, []);

return (
<div
style={{
background: "var(--tile-bg, #1a1a1a)",
border: "0.8px solid var(--tile-stroke, rgba(255,255,255,0.08))",
borderRadius: "24px",
width: "100%",
height: `${heightVh}vh`,
overflow: "hidden",
position: "relative",
}}
>
<canvas
ref={canvasRef}
style={{
position: "absolute",
inset: 0,
width: "100%",
height: "100%",
display: "block",
}}
/>
</div>
);
};

export default BackgroundTile;