diff --git a/src/assets/dept_logo/1.png b/src/assets/dept_logo/1.png
new file mode 100644
index 00000000..bdea1d2c
Binary files /dev/null and b/src/assets/dept_logo/1.png differ
diff --git a/src/assets/dept_logo/10.svg b/src/assets/dept_logo/10.svg
new file mode 100644
index 00000000..3613eaa6
--- /dev/null
+++ b/src/assets/dept_logo/10.svg
@@ -0,0 +1,135 @@
+
+
diff --git a/src/assets/dept_logo/11.svg b/src/assets/dept_logo/11.svg
new file mode 100644
index 00000000..b6c50f9b
--- /dev/null
+++ b/src/assets/dept_logo/11.svg
@@ -0,0 +1,68 @@
+
+
diff --git a/src/assets/dept_logo/12.webp b/src/assets/dept_logo/12.webp
new file mode 100644
index 00000000..8a9a970f
Binary files /dev/null and b/src/assets/dept_logo/12.webp differ
diff --git a/src/assets/dept_logo/14.png b/src/assets/dept_logo/14.png
new file mode 100644
index 00000000..c2eb8ef1
Binary files /dev/null and b/src/assets/dept_logo/14.png differ
diff --git a/src/assets/dept_logo/16.svg b/src/assets/dept_logo/16.svg
new file mode 100644
index 00000000..da26f9d0
--- /dev/null
+++ b/src/assets/dept_logo/16.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/src/assets/dept_logo/18.png b/src/assets/dept_logo/18.png
new file mode 100644
index 00000000..2d03dc05
Binary files /dev/null and b/src/assets/dept_logo/18.png differ
diff --git a/src/assets/dept_logo/2.svg b/src/assets/dept_logo/2.svg
new file mode 100644
index 00000000..de6ce585
--- /dev/null
+++ b/src/assets/dept_logo/2.svg
@@ -0,0 +1,176 @@
+
+
diff --git a/src/assets/dept_logo/20.png b/src/assets/dept_logo/20.png
new file mode 100644
index 00000000..67e99e67
Binary files /dev/null and b/src/assets/dept_logo/20.png differ
diff --git a/src/assets/dept_logo/21A+21H+STS.png b/src/assets/dept_logo/21A+21H+STS.png
new file mode 100644
index 00000000..7c835926
Binary files /dev/null and b/src/assets/dept_logo/21A+21H+STS.png differ
diff --git a/src/assets/dept_logo/21L.png b/src/assets/dept_logo/21L.png
new file mode 100644
index 00000000..b6392348
Binary files /dev/null and b/src/assets/dept_logo/21L.png differ
diff --git a/src/assets/dept_logo/3.svg b/src/assets/dept_logo/3.svg
new file mode 100644
index 00000000..de3c020c
--- /dev/null
+++ b/src/assets/dept_logo/3.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/src/assets/dept_logo/5.png b/src/assets/dept_logo/5.png
new file mode 100644
index 00000000..be2ff652
Binary files /dev/null and b/src/assets/dept_logo/5.png differ
diff --git a/src/assets/dept_logo/6.svg b/src/assets/dept_logo/6.svg
new file mode 100644
index 00000000..cc4479e8
--- /dev/null
+++ b/src/assets/dept_logo/6.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/assets/dept_logo/8.svg b/src/assets/dept_logo/8.svg
new file mode 100644
index 00000000..2be4d7db
--- /dev/null
+++ b/src/assets/dept_logo/8.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/dept_logo/9.png b/src/assets/dept_logo/9.png
new file mode 100644
index 00000000..f6dbeddc
Binary files /dev/null and b/src/assets/dept_logo/9.png differ
diff --git a/src/assets/dept_logo/CC.png b/src/assets/dept_logo/CC.png
new file mode 100644
index 00000000..1638a1fd
Binary files /dev/null and b/src/assets/dept_logo/CC.png differ
diff --git a/src/assets/dept_logo/CMS+21W.png b/src/assets/dept_logo/CMS+21W.png
new file mode 100644
index 00000000..83aa186a
Binary files /dev/null and b/src/assets/dept_logo/CMS+21W.png differ
diff --git a/src/assets/dept_logo/CSB.png b/src/assets/dept_logo/CSB.png
new file mode 100644
index 00000000..2091aedc
Binary files /dev/null and b/src/assets/dept_logo/CSB.png differ
diff --git a/src/assets/dept_logo/CSE.png b/src/assets/dept_logo/CSE.png
new file mode 100644
index 00000000..aa781a88
Binary files /dev/null and b/src/assets/dept_logo/CSE.png differ
diff --git a/src/assets/dept_logo/EC.svg b/src/assets/dept_logo/EC.svg
new file mode 100644
index 00000000..6975200b
--- /dev/null
+++ b/src/assets/dept_logo/EC.svg
@@ -0,0 +1,54 @@
+
+
diff --git a/src/assets/dept_logo/HST.svg b/src/assets/dept_logo/HST.svg
new file mode 100644
index 00000000..5e79e5d6
--- /dev/null
+++ b/src/assets/dept_logo/HST.svg
@@ -0,0 +1,72 @@
+
+
+
+
diff --git a/src/assets/dept_logo/IDS.png b/src/assets/dept_logo/IDS.png
new file mode 100644
index 00000000..2247eb82
Binary files /dev/null and b/src/assets/dept_logo/IDS.png differ
diff --git a/src/assets/dept_logo/MAS.png b/src/assets/dept_logo/MAS.png
new file mode 100644
index 00000000..6deb6aba
Binary files /dev/null and b/src/assets/dept_logo/MAS.png differ
diff --git a/src/assets/dept_logo/SCM.png b/src/assets/dept_logo/SCM.png
new file mode 100644
index 00000000..d7d2372d
Binary files /dev/null and b/src/assets/dept_logo/SCM.png differ
diff --git a/src/assets/dept_logo/STS.png b/src/assets/dept_logo/STS.png
new file mode 100644
index 00000000..7ca1ba2a
Binary files /dev/null and b/src/assets/dept_logo/STS.png differ
diff --git a/src/assets/dept_logo/WGS.png b/src/assets/dept_logo/WGS.png
new file mode 100644
index 00000000..e1f24128
Binary files /dev/null and b/src/assets/dept_logo/WGS.png differ
diff --git a/src/components/ActivityDescription.module.scss b/src/components/ActivityDescription.module.scss
new file mode 100644
index 00000000..01a25d30
--- /dev/null
+++ b/src/components/ActivityDescription.module.scss
@@ -0,0 +1,73 @@
+#class_description {
+ position: relative;
+
+ &::before {
+ content: "";
+ background-image: var(--dept_logo_src);
+ height: 60vh;
+ display: block;
+
+ z-index: -100000;
+ background-repeat: no-repeat;
+ background-size: min(60vw, 70vh);
+ background-position: center;
+
+ @media (min-width: 1024px) {
+ position: fixed;
+ width: max(60vw, 60vh);
+ bottom: -5em;
+ right: -5em;
+ }
+
+ @media (max-width: 1023px) {
+ position: absolute;
+ top: -5em;
+ left: 0;
+ right: 0;
+ }
+ }
+
+ &.sharpen::before {
+ @media (max-width: 1023px) {
+ background-size: 100vw;
+ height: unset;
+ bottom: -10em;
+ }
+
+ @media (min-width: 1024px) {
+ background-size: 110%;
+ background-position: top;
+ position: absolute;
+ left: -2em;
+ right: 0;
+ top: -5em;
+ width: unset;
+ }
+ }
+
+ p {
+ width: fit-content;
+ backdrop-filter: blur(2em);
+ }
+}
+
+
+html:global(.light) #class_description::before {
+ opacity: 0.05;
+ filter: saturate(5) brightness(0.5);
+}
+
+
+html:global(.dark) #class_description:not(.sharpen):not(.lighten)::before {
+ opacity: 0.10;
+ filter: blur(0.5em);
+}
+
+html:global(.dark) #class_description.sharpen::before {
+ opacity: 0.15;
+}
+
+html:global(.dark) #class_description.lighten::before {
+ opacity: 0.15;
+ filter: blur(0.5em) invert(1) brightness(0.8) invert(1);
+}
\ No newline at end of file
diff --git a/src/components/ActivityDescription.tsx b/src/components/ActivityDescription.tsx
index 54148bd6..57521386 100644
--- a/src/components/ActivityDescription.tsx
+++ b/src/components/ActivityDescription.tsx
@@ -13,6 +13,36 @@ import { linkClasses } from "../lib/utils";
import { ClassButtons, NonClassButtons } from "./ActivityButtons";
import { LuExternalLink } from "react-icons/lu";
+import styles from "./ActivityDescription.module.scss";
+import type { CSSProperties } from "react";
+
+import dept_logo_1 from "../assets/dept_logo/1.png";
+import dept_logo_2 from "../assets/dept_logo/2.svg";
+import dept_logo_3 from "../assets/dept_logo/3.svg";
+import dept_logo_5 from "../assets/dept_logo/5.png";
+import dept_logo_6 from "../assets/dept_logo/6.svg";
+import dept_logo_8 from "../assets/dept_logo/8.svg";
+import dept_logo_9 from "../assets/dept_logo/9.png";
+import dept_logo_10 from "../assets/dept_logo/10.svg";
+import dept_logo_11 from "../assets/dept_logo/11.svg";
+import dept_logo_12 from "../assets/dept_logo/12.webp";
+import dept_logo_14 from "../assets/dept_logo/14.png";
+import dept_logo_16 from "../assets/dept_logo/16.svg";
+import dept_logo_18 from "../assets/dept_logo/18.png";
+import dept_logo_20 from "../assets/dept_logo/20.png";
+import dept_logo_21A_21H_STS from "../assets/dept_logo/21A+21H+STS.png";
+import dept_logo_21L from "../assets/dept_logo/21L.png";
+import dept_logo_CC from "../assets/dept_logo/CC.png";
+import dept_logo_CMS_21W from "../assets/dept_logo/CMS+21W.png";
+import dept_logo_CSB from "../assets/dept_logo/CSB.png";
+import dept_logo_CSE from "../assets/dept_logo/CSE.png";
+import dept_logo_EC from "../assets/dept_logo/EC.svg";
+import dept_logo_HST from "../assets/dept_logo/HST.svg";
+import dept_logo_IDS from "../assets/dept_logo/IDS.png";
+import dept_logo_MAS from "../assets/dept_logo/MAS.png";
+import dept_logo_SCM from "../assets/dept_logo/SCM.png";
+import dept_logo_WGS from "../assets/dept_logo/WGS.png";
+
/** A small image indicating a flag, like Spring or CI-H. */
function TypeSpan(props: { flag?: keyof Flags; title: string }) {
const { flag, title } = props;
@@ -204,8 +234,58 @@ function ClassBody(props: { cls: Class; state: State }) {
function ClassDescription(props: { cls: Class; state: State }) {
const { cls, state } = props;
+ const showLogo =
+ typeof state.decoScheme.showDepartmentLogo === "boolean"
+ ? state.decoScheme.showDepartmentLogo
+ : state.decoScheme.showDepartmentLogo[state.colorScheme.id];
+ const departmentLogo = ((file) =>
+ file == null || !showLogo ? "none" : `url("${file}")`)(
+ {
+ "1": dept_logo_1,
+ "2": dept_logo_2,
+ "3": dept_logo_3,
+ "5": dept_logo_5,
+ "6": dept_logo_6,
+ "8": dept_logo_8,
+ "9": dept_logo_9,
+ "10": dept_logo_10,
+ "11": dept_logo_11,
+ "12": dept_logo_12,
+ "14": dept_logo_14,
+ "16": dept_logo_16,
+ "18": dept_logo_18,
+ "20": dept_logo_20,
+ "21A": dept_logo_21A_21H_STS,
+ "21H": dept_logo_21A_21H_STS,
+ STS: dept_logo_21A_21H_STS,
+ "21L": dept_logo_21L,
+ CC: dept_logo_CC,
+ CMS: dept_logo_CMS_21W,
+ "21W": dept_logo_CMS_21W,
+ CSB: dept_logo_CSB,
+ CSE: dept_logo_CSE,
+ EC: dept_logo_EC,
+ HST: dept_logo_HST,
+ IDS: dept_logo_IDS,
+ MAS: dept_logo_MAS,
+ SCM: dept_logo_SCM,
+ WGS: dept_logo_WGS,
+ }[cls.course],
+ );
+ const className = {
+ "18": styles.sharpen,
+ "5": styles.lighten,
+ "11": styles.lighten,
+ }[cls.course];
+
return (
-
+
{cls.number}: {cls.name}
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index 09b5bb4d..a176fbf4 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -25,7 +25,7 @@ import { createListCollection } from "@chakra-ui/react";
import type { State } from "../lib/state";
import { useState, useRef } from "react";
-import { COLOR_SCHEME_PRESETS } from "../lib/colors";
+import { COLOR_SCHEME_PRESETS, DECORATION_SCHEME_PRESETS } from "../lib/colors";
import type { Preferences } from "../lib/schema";
import { DEFAULT_PREFERENCES } from "../lib/schema";
@@ -117,6 +117,34 @@ export function PreferencesDialog(props: {
))}
+ ({
+ label: name,
+ value: name,
+ })),
+ })}
+ value={[preferences.decoScheme.name]}
+ onValueChange={(e) => {
+ const decoScheme = DECORATION_SCHEME_PRESETS.find(
+ ({ name }) => name === e.value[0],
+ );
+ if (!decoScheme) return;
+ previewPreferences({ ...preferences, decoScheme });
+ }}
+ >
+ Decorations:
+
+
+
+
+ {DECORATION_SCHEME_PRESETS.map(({ name }) => (
+
+ {name}
+
+ ))}
+
+
diff --git a/src/components/SelectedActivities.tsx b/src/components/SelectedActivities.tsx
index d24aaf91..7ee3b97c 100644
--- a/src/components/SelectedActivities.tsx
+++ b/src/components/SelectedActivities.tsx
@@ -2,7 +2,7 @@ import { Flex, Text, Button, ButtonGroup } from "@chakra-ui/react";
import type { ComponentPropsWithoutRef } from "react";
import type { Activity } from "../lib/activity";
-import { textColor } from "../lib/colors";
+import { multiplyColor, textColor } from "../lib/colors";
import { Class } from "../lib/class";
import type { State } from "../lib/state";
@@ -18,6 +18,10 @@ export function ColorButton(
backgroundColor={color}
borderColor={color}
color={textColor(color)}
+ _hover={{
+ backgroundColor: multiplyColor(color, 1.1),
+ color: textColor(multiplyColor(color, 1.1)),
+ }}
style={{
...style,
}}
diff --git a/src/lib/colors.ts b/src/lib/colors.ts
index fdfd2164..a201ab02 100644
--- a/src/lib/colors.ts
+++ b/src/lib/colors.ts
@@ -3,12 +3,14 @@ import type { Activity } from "./activity";
/** The type of color schemes. */
export interface ColorScheme {
+ id: string;
name: string;
colorMode: ColorMode;
backgroundColors: string[];
}
const classic: ColorScheme = {
+ id: "light",
name: "Classic",
colorMode: "light",
backgroundColors: [
@@ -26,6 +28,7 @@ const classic: ColorScheme = {
};
const classicDark: ColorScheme = {
+ id: "dark",
name: "Classic (Dark)",
colorMode: "dark",
backgroundColors: [
@@ -43,6 +46,7 @@ const classicDark: ColorScheme = {
};
const highContrast: ColorScheme = {
+ id: "light_hc",
name: "High Contrast",
colorMode: "light",
backgroundColors: [
@@ -58,6 +62,7 @@ const highContrast: ColorScheme = {
};
const highContrastDark: ColorScheme = {
+ id: "dark_hc",
name: "High Contrast (Dark)",
colorMode: "dark",
backgroundColors: [
@@ -80,6 +85,41 @@ export const COLOR_SCHEME_PRESETS: ColorScheme[] = [
highContrastDark,
];
+/** The type of decoration scheme */
+export interface DecorationScheme {
+ id: string;
+ name: string;
+ showDepartmentLogo: boolean | Record;
+}
+
+const decoEnabled: DecorationScheme = {
+ id: "deco_enable",
+ name: "Enabled",
+ showDepartmentLogo: true,
+};
+const decoDisabled: DecorationScheme = {
+ id: "deco_disable",
+ name: "Disabled",
+ showDepartmentLogo: false,
+};
+const decoDefault: DecorationScheme = {
+ id: "deco_default",
+ name: "Auto",
+ showDepartmentLogo: {
+ light: true,
+ dark: true,
+ light_hc: false,
+ dark_hc: false,
+ },
+};
+
+/** The default decoration schemes. */
+export const DECORATION_SCHEME_PRESETS: DecorationScheme[] = [
+ decoDefault,
+ decoEnabled,
+ decoDisabled,
+];
+
/** The default background color for a color scheme. */
export function fallbackColor(colorScheme: ColorScheme): string {
return colorScheme.colorMode === "light" ? "#4A5568" : "#CBD5E0";
@@ -124,14 +164,43 @@ export function chooseColors(
}
}
+export function parseColor(color: string): { r: number; g: number; b: number } {
+ if (color.startsWith("#")) {
+ const r = parseInt(color.substring(1, 3), 16);
+ const g = parseInt(color.substring(3, 5), 16);
+ const b = parseInt(color.substring(5, 7), 16);
+ return { r, g, b };
+ }
+ if (color.startsWith("rgb")) {
+ const [_, r_str, g_str, b_str] =
+ /rgb\(\s*(\d+),\s*(\d+),\s*(\d+)\s*\)/.exec(color) ?? [];
+ const [r, g, b] = [parseInt(r_str), parseInt(g_str), parseInt(b_str)];
+ return { r, g, b };
+ }
+ console.warn("invalid color:", color);
+ return { r: 0, g: 0, b: 0 };
+}
+
/** Choose a text color for a background given by hex code color. */
export function textColor(color: string): string {
- const r = parseInt(color.substring(1, 3), 16);
- const g = parseInt(color.substring(3, 5), 16);
- const b = parseInt(color.substring(5, 7), 16);
+ const { r, g, b } = parseColor(color);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
return brightness > 128 ? "#000000" : "#ffffff";
}
+/** Multiply a given hex code color by a 0-1 factor. */
+export function multiplyColor(color: string, multiplier: number): string {
+ const { r, g, b } = parseColor(color);
+ const to_hex = (v: number) =>
+ Math.max(0, Math.min(255, Math.floor(v)))
+ .toString(16)
+ .padStart(2, "0");
+ return (
+ "#" +
+ to_hex(r * multiplier) +
+ to_hex(g * multiplier) +
+ to_hex(b * multiplier)
+ );
+}
/** Return a standard #AABBCC representation from an input color */
export function canonicalizeColor(code: string): string | undefined {
diff --git a/src/lib/schema.ts b/src/lib/schema.ts
index 43be2db9..552abd41 100644
--- a/src/lib/schema.ts
+++ b/src/lib/schema.ts
@@ -1,6 +1,6 @@
import type { Activity } from "./activity";
-import type { ColorScheme } from "./colors";
-import { COLOR_SCHEME_PRESETS } from "./colors";
+import type { ColorScheme, DecorationScheme } from "./colors";
+import { COLOR_SCHEME_PRESETS, DECORATION_SCHEME_PRESETS } from "./colors";
/** A save has an ID and a name. */
export interface Save {
@@ -11,15 +11,50 @@ export interface Save {
/** Browser-specific user preferences. */
export interface Preferences {
colorScheme: ColorScheme;
+ decoScheme: DecorationScheme;
roundedCorners: boolean;
showEventTimes: boolean;
defaultScheduleId: string | null;
showFeedback: boolean;
}
+export function ensurePreferencesValid(
+ prefs: Partial,
+): Preferences {
+ return {
+ colorScheme:
+ COLOR_SCHEME_PRESETS.find((v) => v.id === prefs.colorScheme?.id) ??
+ COLOR_SCHEME_PRESETS.find((v) => v.name === prefs.colorScheme?.name) ??
+ DEFAULT_PREFERENCES.colorScheme,
+ decoScheme:
+ DECORATION_SCHEME_PRESETS.find((v) => v.id === prefs.decoScheme?.id) ??
+ DECORATION_SCHEME_PRESETS.find(
+ (v) => v.name === prefs.decoScheme?.name,
+ ) ??
+ DEFAULT_PREFERENCES.decoScheme,
+ roundedCorners:
+ typeof prefs.roundedCorners == "boolean"
+ ? prefs.roundedCorners
+ : DEFAULT_PREFERENCES.roundedCorners,
+ showEventTimes:
+ typeof prefs.showEventTimes == "boolean"
+ ? prefs.showEventTimes
+ : DEFAULT_PREFERENCES.showEventTimes,
+ defaultScheduleId:
+ typeof prefs.defaultScheduleId == "number"
+ ? prefs.defaultScheduleId
+ : DEFAULT_PREFERENCES.defaultScheduleId,
+ showFeedback:
+ typeof prefs.showFeedback == "boolean"
+ ? prefs.showFeedback
+ : DEFAULT_PREFERENCES.showFeedback,
+ };
+}
+
/** The default user preferences. */
export const DEFAULT_PREFERENCES: Preferences = {
colorScheme: COLOR_SCHEME_PRESETS[0],
+ decoScheme: DECORATION_SCHEME_PRESETS[0],
roundedCorners: false,
showEventTimes: false,
defaultScheduleId: null,
diff --git a/src/lib/state.ts b/src/lib/state.ts
index b6e023a7..0b0aab78 100644
--- a/src/lib/state.ts
+++ b/src/lib/state.ts
@@ -6,13 +6,13 @@ import { scheduleSlots } from "./calendarSlots";
import type { Section, SectionLockOption, Sections } from "./class";
import { Class } from "./class";
import type { Term } from "./dates";
-import type { ColorScheme } from "./colors";
+import type { ColorScheme, DecorationScheme } from "./colors";
import { chooseColors, fallbackColor } from "./colors";
import type { RawClass, RawTimeslot } from "./rawClass";
import { Store } from "./store";
import { sum, urldecode, urlencode } from "./utils";
import type { HydrantState, Preferences, Save } from "./schema";
-import { DEFAULT_PREFERENCES } from "./schema";
+import { DEFAULT_PREFERENCES, ensurePreferencesValid } from "./schema";
/**
* Global State object. Maintains global program state (selected classes,
@@ -85,6 +85,10 @@ export class State {
get colorScheme(): ColorScheme {
return this.preferences.colorScheme;
}
+ /** The color scheme. */
+ get decoScheme(): DecorationScheme {
+ return this.preferences.decoScheme;
+ }
//========================================================================
// Activity handlers
@@ -453,7 +457,7 @@ export class State {
initState(): void {
const preferences = this.store.globalGet("preferences");
if (preferences) {
- this.preferences = preferences;
+ this.preferences = ensurePreferencesValid(preferences);
}
const url = new URL(window.location.href);
const save = url.searchParams.get("s");