Merge branch 'main' of https://git.csclub.uwaterloo.ca/www/www-new into feat/meet-the-team-page
commit
69743462f3
@ -0,0 +1,44 @@ |
||||
import React from "react"; |
||||
|
||||
import { Link } from "@/components/Link"; |
||||
import { |
||||
ShapesConfig, |
||||
GetShapesConfig, |
||||
defaultGetShapesConfig, |
||||
} from "@/components/ShapesBackground"; |
||||
import { capitalize } from "@/utils"; |
||||
|
||||
import styles from "./ArchivePage.module.css"; |
||||
|
||||
export interface Props { |
||||
type: "news" | "events"; |
||||
items: { |
||||
year: string; |
||||
terms: string[]; |
||||
}[]; |
||||
} |
||||
|
||||
export function ArchivePage({ items, type }: Props) { |
||||
return ( |
||||
<div className={styles.page}> |
||||
<h1>{capitalize(type)} Archive</h1> |
||||
<ul className={styles.list}> |
||||
{items.map(({ year, terms }) => |
||||
terms.map((term) => ( |
||||
<li key={`/${type}/${year}/${term}`}> |
||||
<Link href={`/${type}/${year}/${term}`}> |
||||
{capitalize(term)} {year} |
||||
</Link> |
||||
</li> |
||||
)) |
||||
)} |
||||
</ul> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
ArchivePage.getShapesConfig = ((width, height) => { |
||||
return window.innerWidth <= 768 |
||||
? ({} as ShapesConfig) |
||||
: defaultGetShapesConfig(width, height); |
||||
}) as GetShapesConfig; |
@ -0,0 +1,4 @@ |
||||
.image { |
||||
/* So that image doesn't overflow on mobile screens */ |
||||
max-width: 100%; |
||||
} |
@ -0,0 +1,20 @@ |
||||
.shapesContainer { |
||||
position: absolute; |
||||
overflow: hidden; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
z-index: -10; |
||||
} |
||||
|
||||
.shape { |
||||
--blue: invert(53%) sepia(80%) saturate(4689%) hue-rotate(189deg) |
||||
brightness(92%) contrast(93%); |
||||
--teal: invert(76%) sepia(39%) saturate(578%) hue-rotate(110deg) |
||||
brightness(91%) contrast(88%); |
||||
|
||||
position: absolute; |
||||
filter: var(--blue); |
||||
opacity: 20%; |
||||
} |
@ -0,0 +1,308 @@ |
||||
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<ShapesConfig>({}); |
||||
const [prevWidth, setPrevWidth] = useState<number>(-1); |
||||
const [prevRoute, setPrevRoute] = useState<string>(""); |
||||
const { width, height } = useWindowDimension(); |
||||
const shapesContainerRef = useRef<HTMLDivElement>(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 ( |
||||
<div className={styles.shapesContainer} ref={shapesContainerRef}> |
||||
{Object.entries(config).map(([type, instances]) => |
||||
instances.map((attributes, idx) => ( |
||||
<Shape |
||||
key={idx.toString() + type} |
||||
type={type as ShapeType} |
||||
style={attributes} |
||||
/> |
||||
)) |
||||
)} |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
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" |
||||
| "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)", |
||||
}, |
||||
], |
||||
}; |
@ -0,0 +1,19 @@ |
||||
--- |
||||
name: 'New Website' |
||||
short: 'Check out our new website' |
||||
date: 'Jun 15 2022 00:00:00 GMT-0400 (Eastern Daylight Time)' |
||||
online: true |
||||
location: 'Zoom' |
||||
poster: 'images/events/2021/spring/new-website.png' |
||||
--- |
||||
|
||||
Hey everyone! ๐ Today we are finally releasing the new website internally. Feedback from yall would be really appreciated to iron out bugs before we release it to the public on Wednesday, September 1!! ๐ฅณ |
||||
|
||||
Link to the beta version: https://csclub.uwaterloo.ca/~a3thakra/csc/main/ |
||||
|
||||
## How to give feedback |
||||
|
||||
- Submit this google form: https://forms.gle/YD6MD726KjKwCd1v6 OR |
||||
- Create an issue in our repository https://git.csclub.uwaterloo.ca/www/www-new (use your CSC credentials to sign in) |
||||
|
||||
_**(This is a dummy event, and will be removed before publishing the website)**_ |
@ -0,0 +1,6 @@ |
||||
--- |
||||
author: 'sjdutoit' |
||||
date: 'Mon Sep 16 2002 01:00:00 GMT-0400 (Eastern Daylight Time)' |
||||
--- |
||||
|
||||
The Fall elections have occured and the [results](</about/exec>) are in. |
@ -1,6 +0,0 @@ |
||||
--- |
||||
author: "sjdutoit" |
||||
date: "2002-09-16" |
||||
--- |
||||
|
||||
The Fall elections have occured and the [results](</about/exec>) are in. |
@ -1,6 +1,6 @@ |
||||
--- |
||||
author: "sjdutoit" |
||||
date: "2002-09-18" |
||||
author: 'sjdutoit' |
||||
date: 'Wed Sep 18 2002 01:00:00 GMT-0400 (Eastern Daylight Time)' |
||||
--- |
||||
|
||||
We've changed to the new site! Please send your comments to the [webmaster](<mailto:webmaster@csclub.uwaterloo.ca>). The [old site](</old/>) is still available. A few things may not be working quite right yet, but I'm working on it. |
@ -1,6 +1,6 @@ |
||||
--- |
||||
author: "sjdutoit" |
||||
date: "2002-09-30" |
||||
author: 'sjdutoit' |
||||
date: 'Mon Sep 30 2002 01:00:00 GMT-0400 (Eastern Daylight Time)' |
||||
--- |
||||
|
||||
The business meeting of 2002-09-30 was concluded and the [constitutional change](</about/constitution-change-20020920>) was approved with a 14:2 majority (and one spoiled ballot). See the new [constitution](</about/constitution>). |
@ -1,6 +1,6 @@ |
||||
--- |
||||
author: "sjdutoit" |
||||
date: "2002-10-29" |
||||
author: 'sjdutoit' |
||||
date: 'Tue Oct 29 2002 00:00:00 GMT-0500 (Eastern Standard Time)' |
||||
--- |
||||
|
||||
Due to lack of time for preparation, the Romp Through The Linux Kernel talks were cancelled. Sorry for the inconvenience. Hopefully these talks will happen next term. |
@ -0,0 +1,6 @@ |
||||
--- |
||||
author: 'sjdutoit' |
||||
date: 'Sun Feb 03 2002 00:00:00 GMT-0500 (Eastern Standard Time)' |
||||
--- |
||||
|
||||
XML goodness. |
@ -1,6 +0,0 @@ |
||||
--- |
||||
author: "sjdutoit" |
||||
date: "2002-02-03" |
||||
--- |
||||
|
||||
XML goodness. |
@ -0,0 +1,6 @@ |
||||
--- |
||||
author: 'sjdutoit' |
||||
date: 'Mon Feb 04 2002 00:00:00 GMT-0500 (Eastern Standard Time)' |
||||
--- |
||||
|
||||
About/Memberlist stub up. Made the CSC logo gold. Isn't it nifty? |
@ -1,6 +0,0 @@ |
||||
--- |
||||
author: "sjdutoit" |
||||
date: "2002-02-04" |
||||
--- |
||||
|
||||
About/Memberlist stub up. Made the CSC logo gold. Isn't it nifty? |