|
|
|
@ -1,5 +1,5 @@ |
|
|
|
|
import { useWindowDimension } from "hooks/useWindowDimension"; |
|
|
|
|
import React, { CSSProperties, useEffect, useState } from "react"; |
|
|
|
|
import React, { CSSProperties, useEffect, useRef, useState } from "react"; |
|
|
|
|
|
|
|
|
|
import { Image } from "./Image"; |
|
|
|
|
|
|
|
|
@ -12,13 +12,21 @@ interface Props { |
|
|
|
|
export function ShapesBackground({ getConfig }: Props) { |
|
|
|
|
const [config, setConfig] = useState<ShapesConfig>({}); |
|
|
|
|
const { width, height } = useWindowDimension(); |
|
|
|
|
const shapesContainerRef = useRef<HTMLDivElement>(null); |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
setConfig(getConfig?.() ?? {}); |
|
|
|
|
const width = shapesContainerRef.current?.clientWidth; |
|
|
|
|
const height = shapesContainerRef.current?.clientHeight; |
|
|
|
|
|
|
|
|
|
if (width == null || height == null) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
setConfig(getConfig?.(width, height) ?? {}); |
|
|
|
|
}, [getConfig, width, height]); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div className={styles.shapesContainer}> |
|
|
|
|
<div className={styles.shapesContainer} ref={shapesContainerRef}> |
|
|
|
|
{Object.entries(config).map(([type, instances]) => |
|
|
|
|
instances.map((attributes, idx) => ( |
|
|
|
|
<Shape |
|
|
|
@ -32,6 +40,16 @@ export function ShapesBackground({ getConfig }: Props) { |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function Shape(props: { type: ShapeType; style: CSSProperties }) { |
|
|
|
|
return ( |
|
|
|
|
<Image |
|
|
|
|
src={`/images/shapes/${props.type}.svg`} |
|
|
|
|
className={styles.shape} |
|
|
|
|
style={props.style} |
|
|
|
|
/> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export type ShapeType = |
|
|
|
|
| "asterisk" |
|
|
|
|
| "circle" |
|
|
|
@ -43,24 +61,196 @@ export type ShapeType = |
|
|
|
|
| "triangle" |
|
|
|
|
| "triangleBig" |
|
|
|
|
| "waves" |
|
|
|
|
| "waveBig"; |
|
|
|
|
| "wavesBig"; |
|
|
|
|
|
|
|
|
|
export type ShapesConfig = { |
|
|
|
|
[key in ShapeType]?: CSSProperties[]; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
export type GetShapesConfig = () => ShapesConfig; |
|
|
|
|
export type GetShapesConfig = (width: number, height: number) => ShapesConfig; |
|
|
|
|
|
|
|
|
|
function Shape(props: { type: ShapeType; style: CSSProperties }) { |
|
|
|
|
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: Object.entries(shapesSizes) |
|
|
|
|
.map((shape) => shape[0]) |
|
|
|
|
.filter( |
|
|
|
|
(shape) => shapesSizes[shape as ShapeType]["size"] == "big" |
|
|
|
|
) as ShapeType[], |
|
|
|
|
small: Object.entries(shapesSizes) |
|
|
|
|
.map((shape) => shape[0]) |
|
|
|
|
.filter( |
|
|
|
|
(shape) => shapesSizes[shape as ShapeType]["size"] == "small" |
|
|
|
|
) as ShapeType[], |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// Used to generate random shapes in the margins
|
|
|
|
|
export const defaultGetShapesConfig = ((pageWidth, pageHeight) => { |
|
|
|
|
if (window.innerWidth <= 768) { |
|
|
|
|
return mobileShapesConfig; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const defaultConfig: ShapesConfig = {}; |
|
|
|
|
shapeTypes.forEach((shape) => (defaultConfig[shape] = [])); |
|
|
|
|
|
|
|
|
|
const gap = 20; |
|
|
|
|
const boxWidth = Math.max(300, (pageWidth - 800 - 2 * gap) / 2); |
|
|
|
|
const boxHeight = 400; |
|
|
|
|
|
|
|
|
|
for (let y = 0; y + boxHeight <= pageHeight; y += gap + boxHeight) { |
|
|
|
|
for (let x = 0; x <= 1; ++x) { |
|
|
|
|
if (Math.random() < 0.3) { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const size = y == 0 || y + 2 * boxHeight > pageHeight ? "big" : "small"; |
|
|
|
|
const shape: ShapeType = getRandomShape(size); |
|
|
|
|
const verticalOffset = getVerticalOffset(boxHeight, shape); |
|
|
|
|
const horizontalOffset = getHorizontalOffset(boxWidth, shape); |
|
|
|
|
const shapeWidth = shapesSizes[shape]["width"]; |
|
|
|
|
const shapeHeight = shapesSizes[shape]["height"]; |
|
|
|
|
const angle = getRandomAngle(shape); |
|
|
|
|
const colour = getRandomColour(); |
|
|
|
|
const opacity = getRandomOpacity(colour); |
|
|
|
|
|
|
|
|
|
defaultConfig[shape]?.push({ |
|
|
|
|
top: `${((y + verticalOffset) / 16).toFixed(5)}rem`, |
|
|
|
|
left: x == 0 ? `${(horizontalOffset / 16).toFixed(5)}rem` : "unset", |
|
|
|
|
right: x == 1 ? `${(horizontalOffset / 16).toFixed(5)}rem` : "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 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 ( |
|
|
|
|
<Image |
|
|
|
|
src={`/images/shapes/${props.type}.svg`} |
|
|
|
|
className={styles.shape} |
|
|
|
|
style={props.style} |
|
|
|
|
/> |
|
|
|
|
(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: [ |
|
|
|
|