aboutsummaryrefslogtreecommitdiff
path: root/src/components/ui/background-ripple-effect.tsx
diff options
context:
space:
mode:
authorsotech117 <michael_foiani@brown.edu>2025-09-23 01:46:55 -0400
committersotech117 <michael_foiani@brown.edu>2025-09-23 01:46:55 -0400
commit3a712982307391ae1196f50e252cb37ed5f67ccb (patch)
tree279aa2d97198a08aef2eb1c80bc5008763eb98da /src/components/ui/background-ripple-effect.tsx
parentc5547dc1ba827a4256fbe1ed08bda61f8c660815 (diff)
add hero and get-started page
Diffstat (limited to 'src/components/ui/background-ripple-effect.tsx')
-rw-r--r--src/components/ui/background-ripple-effect.tsx132
1 files changed, 132 insertions, 0 deletions
diff --git a/src/components/ui/background-ripple-effect.tsx b/src/components/ui/background-ripple-effect.tsx
new file mode 100644
index 0000000..d9d265b
--- /dev/null
+++ b/src/components/ui/background-ripple-effect.tsx
@@ -0,0 +1,132 @@
+"use client";
+import React, { useMemo, useRef, useState } from "react";
+import { cn } from "@/lib/utils";
+
+export const BackgroundRippleEffect = ({
+ rows = 8,
+ cols = 27,
+ cellSize = 56,
+}: {
+ rows?: number;
+ cols?: number;
+ cellSize?: number;
+}) => {
+ const [clickedCell, setClickedCell] = useState<{
+ row: number;
+ col: number;
+ } | null>(null);
+ const [rippleKey, setRippleKey] = useState(0);
+ const ref = useRef<any>(null);
+
+ return (
+ <div
+ ref={ref}
+ className={cn(
+ "absolute inset-0 h-full w-full",
+ "[--cell-border-color:var(--color-neutral-300)] [--cell-fill-color:var(--color-neutral-100)] [--cell-shadow-color:var(--color-neutral-500)]"
+ )}
+ >
+ <div className="relative h-auto w-auto overflow-hidden">
+ <div className="pointer-events-none absolute inset-0 z-[2] h-full w-full overflow-hidden" />
+ <DivGrid
+ key={`base-${rippleKey}`}
+ className="mask-radial-from-20% mask-radial-at-top opacity-600"
+ rows={rows}
+ cols={cols}
+ cellSize={cellSize}
+ borderColor="var(--cell-border-color)"
+ fillColor="var(--cell-fill-color)"
+ clickedCell={clickedCell}
+ onCellClick={(row, col) => {
+ setClickedCell({ row, col });
+ setRippleKey((k) => k + 1);
+ }}
+ interactive
+ />
+ </div>
+ </div>
+ );
+};
+
+type DivGridProps = {
+ className?: string;
+ rows: number;
+ cols: number;
+ cellSize: number; // in pixels
+ borderColor: string;
+ fillColor: string;
+ clickedCell: { row: number; col: number } | null;
+ onCellClick?: (row: number, col: number) => void;
+ interactive?: boolean;
+};
+
+type CellStyle = React.CSSProperties & {
+ ["--delay"]?: string;
+ ["--duration"]?: string;
+};
+
+const DivGrid = ({
+ className,
+ rows = 7,
+ cols = 30,
+ cellSize = 56,
+ borderColor = "#3f3f46",
+ fillColor = "rgba(14,165,233,0.3)",
+ clickedCell = null,
+ onCellClick = () => {},
+ interactive = true,
+}: DivGridProps) => {
+ const cells = useMemo(
+ () => Array.from({ length: rows * cols }, (_, idx) => idx),
+ [rows, cols]
+ );
+
+ const gridStyle: React.CSSProperties = {
+ display: "grid",
+ gridTemplateColumns: `repeat(${cols}, ${cellSize}px)`,
+ gridTemplateRows: `repeat(${rows}, ${cellSize}px)`,
+ width: cols * cellSize,
+ height: rows * cellSize,
+ marginInline: "auto",
+ };
+
+ return (
+ <div className={cn("relative z-[3]", className)} style={gridStyle}>
+ {cells.map((idx) => {
+ const rowIdx = Math.floor(idx / cols);
+ const colIdx = idx % cols;
+ const distance = clickedCell
+ ? Math.hypot(clickedCell.row - rowIdx, clickedCell.col - colIdx)
+ : 0;
+ const delay = clickedCell ? Math.max(0, distance * 55) : 0; // ms
+ const duration = 200 + distance * 80; // ms
+
+ const style: CellStyle = clickedCell
+ ? {
+ "--delay": `${delay}ms`,
+ "--duration": `${duration}ms`,
+ }
+ : {};
+
+ return (
+ <div
+ key={idx}
+ className={cn(
+ "cell relative border-[0.5px] opacity-40 transition-opacity duration-150 will-change-transform hover:opacity-80",
+ clickedCell && "animate-cell-ripple [animation-fill-mode:none]",
+ !interactive && "pointer-events-none"
+ )}
+ style={{
+ backgroundColor: fillColor,
+ borderColor: borderColor,
+ ...style,
+ }}
+ onClick={
+ interactive ? () => onCellClick?.(rowIdx, colIdx) : undefined
+ }
+ />
+ );
+ })}
+ </div>
+ );
+};