Some checks failed
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Has been cancelled
Centralize primary, secondary, tertiary, and neutral tokens and apply them across theme variables and UI components. Co-authored-by: Cursor <cursoragent@cursor.com>
90 lines
2.7 KiB
TypeScript
90 lines
2.7 KiB
TypeScript
import { BRAND_GREEN_SHADES } from "@/content/brand-colors";
|
|
|
|
/** GRV greens for wavy interlocking footer tessellation. */
|
|
export const WAVY_TESS_PALETTE = BRAND_GREEN_SHADES;
|
|
|
|
export type WavyTessCell = { d: string; fill: string };
|
|
|
|
function hashCell(col: number, row: number, cols: number) {
|
|
return (col * 17 + row * 31 + ((col + row) % cols) * 11) % WAVY_TESS_PALETTE.length;
|
|
}
|
|
|
|
type MeshOptions = {
|
|
/** Tilts wave flow diagonally (higher = more angled, less vertical). */
|
|
diagonalSkew?: number;
|
|
amplitude?: number;
|
|
};
|
|
|
|
/**
|
|
* Diagonal wavy columns + horizontal ripples — interlocking mosaic (not flat vertical stripes).
|
|
*/
|
|
export function buildWavyTessellationMesh(
|
|
cols: number,
|
|
rows: number,
|
|
width = 100,
|
|
height = 100,
|
|
options: MeshOptions = {}
|
|
): WavyTessCell[] {
|
|
const skew = options.diagonalSkew ?? 0.72;
|
|
const amp = (width / cols) * (options.amplitude ?? 0.62);
|
|
const ampRow = amp * 0.48;
|
|
const loops = 3.6;
|
|
|
|
const boundaryX = (col: number, yNorm: number) => {
|
|
const base = (col / cols) * width;
|
|
const phase = col * 1.13 + 0.42;
|
|
const u = yNorm + (col / cols) * skew;
|
|
const v = yNorm - (col / cols) * skew * 0.45;
|
|
const wave =
|
|
Math.sin(u * Math.PI * 2 * loops + phase) +
|
|
0.52 * Math.sin(v * Math.PI * 2 * (loops + 0.85) - col * 0.4) +
|
|
0.28 * Math.cos((u + v) * Math.PI * loops * 0.55 + phase * 0.6);
|
|
return base + amp * wave * 0.42;
|
|
};
|
|
|
|
const boundaryY = (row: number, xNorm: number) => {
|
|
const base = (row / rows) * height;
|
|
const phase = row * 0.91 + 0.18;
|
|
const u = xNorm + (row / rows) * skew;
|
|
const wave =
|
|
Math.sin(u * Math.PI * 2 * (loops * 0.9) + phase) +
|
|
0.4 * Math.cos(u * Math.PI * 2 * (loops * 1.15) - row * 0.55);
|
|
return base + ampRow * wave * 0.38;
|
|
};
|
|
|
|
const cells: WavyTessCell[] = [];
|
|
|
|
const corner = (col: number, row: number) => {
|
|
const yn = row / rows;
|
|
const x = boundaryX(col, yn);
|
|
const y = boundaryY(row, x / width);
|
|
return { x, y };
|
|
};
|
|
|
|
for (let r = 0; r < rows; r++) {
|
|
for (let c = 0; c < cols; c++) {
|
|
const bl = corner(c, r);
|
|
const br = corner(c + 1, r);
|
|
const tl = corner(c, r + 1);
|
|
const tr = corner(c + 1, r + 1);
|
|
const ymL = (bl.y + tl.y) / 2;
|
|
const ymR = (br.y + tr.y) / 2;
|
|
|
|
const d = [
|
|
`M ${bl.x.toFixed(2)} ${bl.y.toFixed(2)}`,
|
|
`Q ${bl.x.toFixed(2)} ${ymL.toFixed(2)} ${tl.x.toFixed(2)} ${tl.y.toFixed(2)}`,
|
|
`L ${tr.x.toFixed(2)} ${tr.y.toFixed(2)}`,
|
|
`Q ${br.x.toFixed(2)} ${ymR.toFixed(2)} ${br.x.toFixed(2)} ${br.y.toFixed(2)}`,
|
|
"Z",
|
|
].join(" ");
|
|
|
|
const layer = (r + Math.floor(c / 2)) % 3;
|
|
const fill = WAVY_TESS_PALETTE[hashCell(c + layer, r, cols)];
|
|
|
|
cells.push({ d, fill });
|
|
}
|
|
}
|
|
|
|
return cells;
|
|
}
|