Merge branch 'main' of https://git.csclub.uwaterloo.ca/www/www-new into feat/meet-the-team-page
commit
b14e196a4f
@ -1,10 +1,10 @@ |
||||
.link { |
||||
color: var(--blue-2); |
||||
color: var(--primary-accent); |
||||
transition-duration: 0.3s; |
||||
text-decoration: none; |
||||
white-space: normal; |
||||
} |
||||
|
||||
.link:hover { |
||||
color: var(--teal-2); |
||||
color: var(--secondary-accent); |
||||
} |
||||
|
@ -0,0 +1,40 @@ |
||||
.card { |
||||
display: flex; |
||||
flex-direction: row; |
||||
box-sizing: border-box; |
||||
padding: calc(16rem / 16); |
||||
color: var(--purple-2); |
||||
font-size: 1rem; |
||||
} |
||||
|
||||
.card aside { |
||||
max-width: calc(142rem / 16); |
||||
margin-right: calc(45rem / 16); |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
|
||||
.card aside img { |
||||
max-width: calc(142rem / 16); |
||||
max-height: 100%; |
||||
object-fit: cover; |
||||
} |
||||
|
||||
.content { |
||||
padding: calc(4rem / 16); |
||||
} |
||||
|
||||
.content h1 { |
||||
margin: 0; |
||||
margin-top: calc(4rem / 16); |
||||
font-size: calc(18rem / 16); |
||||
} |
||||
|
||||
.card section { |
||||
padding-bottom: 0; |
||||
} |
||||
|
||||
.spacer { |
||||
margin-top: calc(76rem / 16); |
||||
} |
@ -0,0 +1,22 @@ |
||||
import { DEFAULT_MIN_VERSION } from "node:tls"; |
||||
import React from "react"; |
||||
import { Image } from "./Image"; |
||||
import styles from "./MiniTechTalkCard.module.css"; |
||||
|
||||
interface MiniTechTalkProps { |
||||
name: string; |
||||
short: string; |
||||
poster?: string; |
||||
} |
||||
|
||||
export function MiniTechTalkCard({ name, poster, short }: MiniTechTalkProps) { |
||||
return ( |
||||
<article className={styles.card}> |
||||
<aside>{poster && <Image alt={name} src={poster} />}</aside> |
||||
<div className={styles.content}> |
||||
<h1>{name}</h1> |
||||
<p>{short}</p> |
||||
</div> |
||||
</article> |
||||
); |
||||
} |
@ -0,0 +1,34 @@ |
||||
.card { |
||||
display: flex; |
||||
flex-direction: row; |
||||
box-sizing: border-box; |
||||
} |
||||
|
||||
.card aside { |
||||
flex: 0 0 calc(287rem / 16); |
||||
margin-right: calc(24rem / 16); |
||||
} |
||||
|
||||
.card aside img { |
||||
width: 100%; |
||||
margin-bottom: 1rem; |
||||
} |
||||
|
||||
.spacer { |
||||
margin-top: calc(76rem / 16); |
||||
} |
||||
|
||||
.card h1 { |
||||
font-size: calc(24rem / 16); |
||||
font-weight: 700; |
||||
font-style: normal; |
||||
margin-top: 0; |
||||
margin-bottom: 0; |
||||
color: var(--blue-2); |
||||
} |
||||
|
||||
@media only screen and (max-width: calc(768rem / 16)) { |
||||
.card { |
||||
flex-direction: column; |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
import React, { ReactNode } from "react"; |
||||
import { Image } from "./Image"; |
||||
import styles from "./TechTalkCard.module.css"; |
||||
|
||||
interface TechTalkProps { |
||||
name: string; |
||||
poster?: string; |
||||
children: ReactNode; |
||||
} |
||||
|
||||
export function TechTalkCard({ name, poster, children }: TechTalkProps) { |
||||
return ( |
||||
<article className={styles.card}> |
||||
<aside> |
||||
{poster && <Image alt={name} src={poster} />} |
||||
{!poster && <div className={styles.spacer}></div>} |
||||
</aside> |
||||
<section className={styles.content}> |
||||
<h1>{name}</h1> |
||||
<div>{children}</div> |
||||
</section> |
||||
</article> |
||||
); |
||||
} |
@ -0,0 +1,178 @@ |
||||
import React, { |
||||
createContext, |
||||
ReactElement, |
||||
useContext, |
||||
useEffect, |
||||
useState, |
||||
} from "react"; |
||||
|
||||
type BuiltInThemes = "light" | "dark"; |
||||
|
||||
export interface Theme { |
||||
name: BuiltInThemes | "custom"; |
||||
palette: Palette; |
||||
} |
||||
|
||||
export type SetThemeInput = BuiltInThemes | Partial<Palette>; |
||||
|
||||
export const PALETTE_NAMES = [ |
||||
"--primary-background", |
||||
"--secondary-background", |
||||
|
||||
"--primary-accent", |
||||
"--primary-accent-soft", |
||||
"--primary-accent-light", |
||||
"--primary-accent-dim", |
||||
|
||||
"--secondary-accent", |
||||
"--secondary-accent-light", |
||||
|
||||
"--primary-heading", |
||||
"--secondary-heading", |
||||
|
||||
"--text", |
||||
|
||||
"--form-invalid", |
||||
|
||||
"--input-background", |
||||
"--input-placeholder-text", |
||||
"--input-text", |
||||
|
||||
"--navbar-page-overlay", |
||||
] as const; |
||||
|
||||
export const emptyPalette = PALETTE_NAMES.reduce( |
||||
(partial, varName) => ({ |
||||
...partial, |
||||
[varName]: "#c4e0f8", |
||||
}), |
||||
{} as Palette |
||||
); |
||||
|
||||
const ThemeContext = |
||||
createContext< |
||||
| { |
||||
theme: Theme; |
||||
setTheme(input: SetThemeInput): void; |
||||
save(): void; |
||||
clearSaved(): void; |
||||
} |
||||
| undefined |
||||
>(undefined); |
||||
|
||||
export interface Props { |
||||
children?: ReactElement; |
||||
} |
||||
|
||||
export function ThemeProvider(props: Props) { |
||||
const update = useForcedUpdate(); |
||||
const [themeName, setThemeName] = |
||||
useState<Theme["name"] | undefined>(undefined); |
||||
|
||||
const setTheme = (input: SetThemeInput) => { |
||||
if (typeof input === "string") { |
||||
PALETTE_NAMES.forEach((name) => |
||||
document.body.style.setProperty(name, "") |
||||
); |
||||
|
||||
if (input === "light") { |
||||
document.body.classList.remove("dark"); |
||||
} else if (input === "dark") { |
||||
document.body.classList.add("dark"); |
||||
} |
||||
|
||||
setThemeName(input); |
||||
} else { |
||||
const properties = Object.keys(input) as PaletteNames[]; |
||||
|
||||
properties.forEach((property) => |
||||
document.body.style.setProperty( |
||||
property, |
||||
input[property]?.trim() ?? null |
||||
) |
||||
); |
||||
|
||||
setThemeName("custom"); |
||||
} |
||||
|
||||
update(); |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
const customPalette = getSavedPalette(); |
||||
|
||||
if (customPalette == null) { |
||||
setThemeName("light"); |
||||
} else { |
||||
setTheme(customPalette); |
||||
setThemeName("custom"); |
||||
} |
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []); |
||||
|
||||
return ( |
||||
<ThemeContext.Provider |
||||
value={ |
||||
themeName == null |
||||
? undefined |
||||
: { |
||||
theme: { |
||||
name: themeName, |
||||
get palette() { |
||||
return getCurrentPalette(); |
||||
}, |
||||
}, |
||||
setTheme, |
||||
save: () => savePalette(getCurrentPalette()), |
||||
clearSaved: clearSavedPalette, |
||||
} |
||||
} |
||||
> |
||||
{props.children} |
||||
</ThemeContext.Provider> |
||||
); |
||||
} |
||||
|
||||
export function useThemeContext() { |
||||
return useContext(ThemeContext); |
||||
} |
||||
|
||||
export type PaletteNames = typeof PALETTE_NAMES[number]; |
||||
|
||||
export type Palette = { |
||||
[key in PaletteNames]: string; |
||||
}; |
||||
|
||||
function getCurrentPalette() { |
||||
const styles = getComputedStyle(document.body); |
||||
|
||||
return PALETTE_NAMES.reduce( |
||||
(partial, varName) => ({ |
||||
...partial, |
||||
[varName]: styles.getPropertyValue(varName).trim(), |
||||
}), |
||||
{} as Palette |
||||
); |
||||
} |
||||
|
||||
function useForcedUpdate() { |
||||
const [fakeState, setFakeState] = useState(true); |
||||
|
||||
return () => setFakeState(!fakeState); |
||||
} |
||||
|
||||
const STORAGE_KEY = "csc-theme-palette"; |
||||
|
||||
function getSavedPalette() { |
||||
const raw = localStorage.getItem(STORAGE_KEY); |
||||
|
||||
return raw == null ? undefined : (JSON.parse(raw) as Palette); |
||||
} |
||||
|
||||
function savePalette(palette: Palette) { |
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(palette)); |
||||
} |
||||
|
||||
function clearSavedPalette() { |
||||
localStorage.removeItem(STORAGE_KEY); |
||||
} |
@ -1,36 +0,0 @@ |
||||
import React, { createContext, ReactElement, useEffect, useState } from "react"; |
||||
|
||||
type Theme = "light" | "dark"; |
||||
|
||||
const ThemeContext = createContext<{ |
||||
theme: Theme; |
||||
setTheme(newTheme: Theme): void; |
||||
}>({ |
||||
theme: "light", |
||||
setTheme: () => { |
||||
throw new Error("Use ThemeProvider instead."); |
||||
}, |
||||
}); |
||||
|
||||
interface Props { |
||||
theme: Theme; |
||||
children?: ReactElement; |
||||
} |
||||
|
||||
export function ThemeProvider({ children, theme }: Props) { |
||||
const [currentTheme, setTheme] = useState(theme); |
||||
|
||||
useEffect(() => { |
||||
if (currentTheme === "light") { |
||||
document.body.classList.remove("dark"); |
||||
} else if (currentTheme === "dark") { |
||||
document.body.classList.add("dark"); |
||||
} |
||||
}, [currentTheme]); |
||||
|
||||
return ( |
||||
<ThemeContext.Provider value={{ theme: currentTheme, setTheme }}> |
||||
{children} |
||||
</ThemeContext.Provider> |
||||
); |
||||
} |
@ -0,0 +1,19 @@ |
||||
export const metadata = { |
||||
name: "Tech Talk Title", |
||||
short: "Learn how React works and make your own version!", |
||||
poster: "/images/playground/alt-tab.jpg", |
||||
}; |
||||
|
||||
You've got a game, but you didn't write it. You're running it by emulating the machine it was meant to run on, and the machine it was |
||||
meant to run on never had support for networking. Now, you want to play with your friend, over the Internet. Oh, and it's not |
||||
acceptable to incur any latency between your controller and the game while we're at it. Surely that can't be possible, right? |
||||
Wrong. This talk will discuss the re-emulation technique for netplay used commercially by a system called GGPO and freely in |
||||
an emulator frontend called RetroArch, and how similar techniques can be applied to make networking work in other scenarios |
||||
it was never meant for. This will be an unprepared, impromptu talk with no slides, so it should either be a fascinating dive |
||||
into a little-heard-of technique, or an impenetrable mess of jargon and algorithms. Either way, it should be fun. Professor |
||||
Richards is the maintainer of the netplay infrastructure for RetroArch, a popular emulator frontend for multiple platforms. |
||||
|
||||
# Download |
||||
|
||||
- BitTorrent:[Netplay in Emulators (mp4)] |
||||
- HTTP (web browser):[Netplay in Emulators (mp4)] |
@ -0,0 +1,7 @@ |
||||
#!/bin/bash |
||||
export NEXT_PUBLIC_BASE_PATH="/~$USER/website-demo" |
||||
rm -rf ~/www/website-demo |
||||
npm install |
||||
npm run build |
||||
npm run export |
||||
mv out ~/www/website-demo |
@ -1,72 +1,3 @@ |
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
|
||||
declare module "*.event.mdx" { |
||||
import { ComponentType } from "react"; |
||||
|
||||
interface EventMetadata { |
||||
name: string; |
||||
short: string; |
||||
date: Date; |
||||
online: boolean; |
||||
location: string; |
||||
poster?: string; |
||||
registerLink?: string; |
||||
} |
||||
|
||||
const ReactComponent: ComponentType; |
||||
|
||||
export const metadata: EventMetadata; |
||||
export default ReactComponent; |
||||
} |
||||
|
||||
declare module "*.news.mdx" { |
||||
import { ComponentType } from "react"; |
||||
|
||||
interface NewsMetadata { |
||||
author: string; |
||||
date: Date; |
||||
} |
||||
|
||||
const ReactComponent: ComponentType; |
||||
|
||||
export const metadata: NewsMetadata; |
||||
export default ReactComponent; |
||||
} |
||||
|
||||
declare module "*.team-member.mdx" { |
||||
import { ComponentType } from "react"; |
||||
|
||||
interface TeamMemberMetadata { |
||||
name: string; |
||||
role: string; |
||||
image?: string; |
||||
} |
||||
|
||||
const ReactComponent: ComponentType; |
||||
|
||||
export const metadata: TeamMemberMetadata; |
||||
export default ReactComponent; |
||||
} |
||||
|
||||
declare module "*.section.mdx" { |
||||
import { ComponentType } from "react"; |
||||
|
||||
interface SectionMetadata { |
||||
title: string; |
||||
id: string; |
||||
} |
||||
|
||||
const ReactComponent: ComponentType; |
||||
|
||||
export const metadata: SectionMetadata; |
||||
export default ReactComponent; |
||||
} |
||||
|
||||
declare module "*.mdx" { |
||||
import { ComponentType } from "react"; |
||||
|
||||
const ReactComponent: ComponentType; |
||||
|
||||
export default ReactComponent; |
||||
} |
||||
/// <reference types="next/image-types/global" />
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,23 +1,20 @@ |
||||
.page { |
||||
margin-bottom: calc(20rem / 16); |
||||
} |
||||
|
||||
.title { |
||||
color: var(--purple-2); |
||||
font-size: calc(48rem / 16); |
||||
height: calc(80rem / 16); |
||||
margin-top: auto; |
||||
padding-left: calc(20rem / 16); |
||||
} |
||||
|
||||
.content { |
||||
color: black; |
||||
background-color: white; |
||||
} |
||||