import { useWindowDimension } from "hooks/useWindowDimension"; import { useRouter } from "next/router"; import React, { CSSProperties, useEffect, useRef, useState } from "react"; import { Image } from "./Image"; import styles from "./ShapesBackground.module.css"; const MOBILE_WIDTH = 768; interface Props { getConfig?: GetShapesConfig; } export function ShapesBackground({ getConfig }: Props) { const [config, setConfig] = useState({}); const [prevWidth, setPrevWidth] = useState(-1); const [prevRoute, setPrevRoute] = useState(""); const { width, height } = useWindowDimension(); const shapesContainerRef = useRef(null); const router = useRouter(); useEffect(() => { const containerWidth = shapesContainerRef.current?.offsetWidth; const containerHeight = shapesContainerRef.current?.offsetHeight; // In general, rerun getShapesConfig() if the screen size changes from desktop to mobile (or vice versa) if ( containerWidth == null || containerHeight == null || !( router.asPath === "/" || router.asPath !== prevRoute || prevWidth < 0 || (prevWidth <= MOBILE_WIDTH && MOBILE_WIDTH < width) || (prevWidth > MOBILE_WIDTH && MOBILE_WIDTH >= width) ) ) { return; } setPrevWidth(width); setPrevRoute(router.asPath); setConfig(getConfig?.(containerWidth, containerHeight) ?? {}); }, [getConfig, width, height, prevWidth, prevRoute, router.asPath]); return (
{Object.entries(config).map(([type, instances]) => instances.map((attributes, idx) => ( )) )}
); } function Shape(props: { type: ShapeType; style: CSSProperties }) { return ( ); } export type ShapeType = | "asterisk" | "circle" | "cross" | "dots" | "hash" | "plus" | "ring" | "triangle" | "triangleBig" | "waves" | "wavesBig"; export type ShapesConfig = { [key in ShapeType]?: CSSProperties[]; }; export type GetShapesConfig = (width: number, height: number) => ShapesConfig; type ShapeSize = { [key in ShapeType]: { size: "big" | "small"; width: number; height: number; // 0 <= minAngle, maxAngle <= 180 minAngle?: number; maxAngle?: number; }; }; const shapeTypes: ShapeType[] = [ "asterisk", "circle", "cross", "dots", "hash", "plus", "ring", "triangle", "triangleBig", "waves", "wavesBig", ]; const shapesSizes: ShapeSize = { asterisk: { size: "big", width: 168, height: 168, }, circle: { size: "big", width: 132, height: 132, }, cross: { size: "big", width: 150, height: 150, }, dots: { size: "big", width: 232, height: 250, }, hash: { size: "small", width: 60, height: 60, }, plus: { size: "small", width: 48, height: 48, }, ring: { size: "small", width: 70, height: 70, }, triangle: { size: "small", width: 68, height: 68, minAngle: 15, maxAngle: 26, }, triangleBig: { size: "big", width: 138, height: 138, minAngle: 15, maxAngle: 26, }, waves: { size: "small", width: 102, height: 50, }, wavesBig: { size: "big", width: 252, height: 132, }, }; const shapesBySize = { big: shapeTypes.filter((shape) => shapesSizes[shape]["size"] == "big"), small: shapeTypes.filter((shape) => shapesSizes[shape]["size"] == "small"), }; // Used to generate random shapes in the margins export const defaultGetShapesConfig = ((pageWidth, pageHeight) => { if (window.innerWidth <= MOBILE_WIDTH) { return mobileShapesConfig; } const defaultConfig: ShapesConfig = {}; const gap = 20; const minBoxWidth = 150; const boxWidth = Math.max(minBoxWidth, (pageWidth - 800 - 2 * gap) / 2); const boxHeight = 400; const shapeGenerationProbability = 0.85; for (let y = 0; y + boxHeight <= pageHeight; y += gap + boxHeight) { for (let x = 0; x <= 1; ++x) { if (Math.random() > shapeGenerationProbability) { continue; } const size = boxWidth > minBoxWidth && (y == 0 || y + 2 * boxHeight > pageHeight) ? "big" : "small"; const shape: ShapeType = getRandomShape(size); const verticalOffset = getVerticalOffset(boxHeight, shape); const horizontalOffset = getHorizontalOffset(boxWidth - 2 * gap, shape); const shapeWidth = shapesSizes[shape]["width"]; const shapeHeight = shapesSizes[shape]["height"]; const angle = getRandomAngle(shape); const colour = getRandomColour(); const opacity = getRandomOpacity(colour); defaultConfig[shape] ??= []; defaultConfig[shape]?.push({ top: `${((y + verticalOffset) / 16).toFixed(5)}rem`, left: x == 0 ? `${(((horizontalOffset + gap) / window.innerWidth) * 100).toFixed( 5 )}vw` : "unset", right: x == 1 ? `${(((horizontalOffset + gap) / window.innerWidth) * 100).toFixed( 5 )}vw` : "unset", width: `${(shapeWidth / 16).toFixed(5)}rem`, height: `${(shapeHeight / 16).toFixed(5)}rem`, transform: `rotate(${angle}deg)`, filter: `var(--${colour})`, opacity: `${opacity}%`, }); } } return defaultConfig; }) as GetShapesConfig; function getRandomShape(size: "big" | "small"): ShapeType { const idx = Math.floor(Math.random() * shapesBySize[size].length); return shapesBySize[size][idx]; } function getVerticalOffset(boxHeight: number, shape: ShapeType): number { const padding = shapesSizes[shape]["height"]; return Math.floor(Math.random() * (boxHeight - padding)); } function getHorizontalOffset(boxWidth: number, shape: ShapeType): number { const padding = shapesSizes[shape]["width"]; return shapesSizes[shape]["size"] == "big" ? Math.floor(Math.random() * (boxWidth - padding / 2) - padding / 2) : Math.floor(Math.random() * (boxWidth - padding)); } function getRandomAngle(shape: ShapeType): number { const minAngle = shapesSizes[shape]["minAngle"] ?? 0; const maxAngle = shapesSizes[shape]["maxAngle"] ?? 0; const direction = Math.random() < 0.5 ? 1 : -1; return ( (minAngle + Math.floor(Math.random() * (maxAngle - minAngle + 1))) * direction ); } function getRandomColour(): "blue" | "teal" { return Math.random() < 0.7 ? "blue" : "teal"; } function getRandomOpacity(colour: "blue" | "teal"): number { if (colour === "blue") { return Math.random() < 0.8 ? 20 : 25; } else { return Math.random() < 0.8 ? 25 : 30; } } // Used for most mobile pages export const mobileShapesConfig = { dots: [ { top: "calc(-6rem / 16)", left: "calc(-95rem / 16)", width: "calc(166rem / 16)", height: "calc(150rem / 16)", }, ], hash: [ { top: "calc(88rem / 16)", right: "15vw", width: "calc(40rem / 16)", height: "calc(40rem / 16)", }, ], triangle: [ { top: "calc(20rem / 16)", right: "1vw", width: "calc(45rem / 16)", height: "calc(45rem / 16)", transform: "rotate(17deg)", }, ], };