GRV-Summit-Site/components/brand/GeometricMessBackground.tsx
Kirubel-Kibru-Yaltopia d261148b35
Some checks are pending
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Waiting to run
Enhance footer and hero with brand backgrounds
2026-05-21 20:35:59 +03:00

109 lines
3.2 KiB
TypeScript

import { buildVoronoiMesh, type VoronoiCell } from "@/lib/voronoi-mesh";
import {
buildFooterMidlineCurve,
midlineCurveToClipPath,
midlineCurveToClipPathPercent,
} from "@/lib/footer-curve-edge";
import { cn } from "@/lib/utils";
const VIEW_W = 160;
const VIEW_H = 100;
const MIDLINE_Y = VIEW_H * 0.5;
const CURVE_SEED = 0xa3f21c;
const MIDLINE_CURVE = buildFooterMidlineCurve(VIEW_W, 20, CURVE_SEED, MIDLINE_Y, 11);
const FACET_CLIP_PATH = midlineCurveToClipPath(MIDLINE_CURVE, VIEW_W, VIEW_H);
const FACET_CLIP_CSS = midlineCurveToClipPathPercent(MIDLINE_CURVE, VIEW_W, VIEW_H);
const FACET_TONES = [
"#f5fbf7",
"#ecf8f0",
"#dff3e6",
"#ccebd8",
"#b5e4c8",
"#9ed9b4",
"#84d09f",
"#6bc98a",
"#52c878",
"#4aad6e",
] as const;
const LOW_POLY_MESH = buildVoronoiMesh(64, VIEW_W, VIEW_H, CURVE_SEED, {
siteGenerator: "poisson",
shape: "sharp",
minDist: 6.5,
});
function hexToRgb(hex: string) {
const n = parseInt(hex.slice(1), 16);
return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
}
function mixHex(a: string, b: string, t: number) {
const c1 = hexToRgb(a);
const c2 = hexToRgb(b);
const u = (v: number) => Math.round(v).toString(16).padStart(2, "0");
return `#${u(c1.r + (c2.r - c1.r) * t)}${u(c1.g + (c2.g - c1.g) * t)}${u(c1.b + (c2.b - c1.b) * t)}`;
}
function facetFill(cell: VoronoiCell, index: number) {
const cx = cell.points.reduce((s, p) => s + p[0], 0) / cell.points.length;
const cy = cell.points.reduce((s, p) => s + p[1], 0) / cell.points.length;
const xNorm = cx / VIEW_W;
const yNorm = Math.max(0, (cy - MIDLINE_Y) / (VIEW_H - MIDLINE_Y));
const tone = FACET_TONES[(index * 9 + Math.floor(cx * 0.55)) % FACET_TONES.length];
const wash = mixHex("#ffffff", "#e8f5ec", 0.35);
const mix = 0.42 + (1 - xNorm) * 0.2 + yNorm * 0.12;
return mixHex(wash, tone, mix);
}
const FACET_CELLS = LOW_POLY_MESH.map((cell, i) => ({
d: cell.d,
fill: facetFill(cell, i),
}));
type Props = {
className?: string;
};
export function GeometricMessBackground({ className }: Props) {
return (
<div
className={cn(
"pointer-events-none absolute inset-0 z-0 overflow-hidden bg-white",
className
)}
aria-hidden
>
<div
className="geometric-mess-layer absolute inset-0"
style={{ clipPath: FACET_CLIP_CSS }}
>
<svg
className="h-full w-full"
viewBox={`0 0 ${VIEW_W} ${VIEW_H}`}
preserveAspectRatio="xMidYMid slice"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<clipPath id="footer-facet-clip" clipPathUnits="userSpaceOnUse">
<path d={FACET_CLIP_PATH} />
</clipPath>
</defs>
<rect width={VIEW_W} height={VIEW_H} fill="#ffffff" />
<g clipPath="url(#footer-facet-clip)">
{FACET_CELLS.map((cell, i) => (
<path key={i} d={cell.d} fill={cell.fill} stroke="none" />
))}
</g>
</svg>
</div>
{/* White wash above the curve only — keeps facet tops along the midline visible */}
<div
className="absolute inset-x-0 top-0 h-[44%] bg-gradient-to-b from-white via-white/75 to-transparent"
aria-hidden
/>
</div>
);
}