@nu-appdev/northwestern-starlight-theme 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.4.0] - 2026-03-31
11
+
12
+ ### Added
13
+
14
+ - **Open Graph image generation**: builds a branded 1200x630 PNG per docs page. Favicon logo, page title, and description on Northwestern purple with a light purple accent border. Slack, Teams, and social media link previews use these images.
15
+ - Enabled by default. Set `site` in your Astro config. pnpm users: run `pnpm add canvaskit-wasm` to enable (build skips OG gracefully without it).
16
+ - Disable with `ogImage: false`.
17
+ - Adds `og:image`, `og:image:type`, `og:image:width`, `og:image:height`, `og:image:alt`, `og:logo`, `twitter:image`, and `twitter:image:alt` meta tags to every page. Overrides `twitter:card` to `summary_large_image`.
18
+ - New dependency: `astro-og-canvas`.
19
+
20
+ ## [1.3.2] - 2026-03-30
21
+
22
+ ### Fixed
23
+
24
+ - H1/H2 now use Noto Serif (`--nu-font-display`), H3–H6 use Poppins (`--nu-font-heading`). Sizes, weights, and colors match the [Department 4.0 stylesheet](https://common.northwestern.edu/dept/4.0/css/styles.css).
25
+ - Poppins 400 loaded the Light file instead of Regular. Poppins 500 (Medium) added from v4 CDN.
26
+ - Table headers now use `--nu-purple-100` with white text.
27
+ - Table striping moved to odd rows (`#f9f6ff`); dark mode uses `rgb(78 42 132 / 10%)`.
28
+ - `--nu-purple-surface` corrected to `#f9f6ff`, `--nu-purple-surface-subtle` to `#f3f0f7`.
29
+ - Blockquote background uses `#f9f6ff` instead of translucent purple.
30
+ - Horizontal rules use 3px `--nu-purple-10` instead of 1px gray.
31
+ - List markers use `--nu-purple-100` instead of `--nu-purple-40`.
32
+
10
33
  ## [1.3.1] - 2026-03-27
11
34
 
12
35
  ### Fixed
@@ -130,7 +153,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
130
153
  - OpenAPI plugin compatibility with method badge preservation
131
154
  - Reduced motion support for transitions
132
155
 
133
- [Unreleased]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.3.1...HEAD
156
+ [Unreleased]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.4.0...HEAD
157
+ [1.4.0]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.3.2...v1.4.0
158
+ [1.3.2]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.3.1...v1.3.2
134
159
  [1.3.1]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.3.0...v1.3.1
135
160
  [1.3.0]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.2.0...v1.3.0
136
161
  [1.2.0]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.1.1...v1.2.0
package/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { copyFileSync, mkdirSync, readFileSync } from "node:fs";
2
2
  import type { IncomingMessage, ServerResponse } from "node:http";
3
+ import { createRequire } from "node:module";
3
4
  import { dirname, join } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import type { StarlightPlugin } from "@astrojs/starlight/types";
@@ -92,6 +93,18 @@ export interface NorthwesternThemeConfig {
92
93
  * @default true
93
94
  */
94
95
  mermaid?: boolean | Record<string, unknown>;
96
+
97
+ /**
98
+ * Open Graph image generation.
99
+ *
100
+ * Generates branded OG images (1200x630 PNG) for every docs page with the
101
+ * page title and description on a Northwestern purple background.
102
+ *
103
+ * Requires `site` to be set in `astro.config.ts` for absolute image URLs.
104
+ *
105
+ * @default true
106
+ */
107
+ ogImage?: boolean;
95
108
  }
96
109
 
97
110
  /**
@@ -137,19 +150,30 @@ const RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
137
150
  * ```
138
151
  */
139
152
  export default function northwesternTheme(config: NorthwesternThemeConfig = {}): StarlightPlugin {
140
- const { homepage = {}, mermaid = true } = config;
153
+ const { homepage = {}, mermaid = true, ogImage = true } = config;
141
154
  const themeConfig = {
142
155
  homepage: {
143
156
  layout: homepage.layout ?? "centered",
144
157
  showTitle: homepage.showTitle ?? true,
145
158
  imageWidth: homepage.imageWidth ?? "500px",
146
159
  },
160
+ ogImage: {
161
+ enabled: false, // set to true in config:setup when ogImage is enabled
162
+ siteTitle: "",
163
+ logoPath: "",
164
+ },
147
165
  };
148
166
 
149
167
  return {
150
168
  name: "northwestern-starlight-theme",
151
169
  hooks: {
152
- async "config:setup"({ config: starlightConfig, updateConfig, addIntegration, logger }) {
170
+ async "config:setup"({
171
+ config: starlightConfig,
172
+ updateConfig,
173
+ addIntegration,
174
+ addRouteMiddleware,
175
+ logger,
176
+ }) {
153
177
  // Apply default favicon if consumer hasn't set one
154
178
  const consumerSetFavicon =
155
179
  starlightConfig.favicon &&
@@ -244,6 +268,58 @@ export default function northwesternTheme(config: NorthwesternThemeConfig = {}):
244
268
  }
245
269
  }
246
270
 
271
+ // OG image generation (bundled via astro-og-canvas)
272
+ if (ogImage) {
273
+ // canvaskit-wasm uses __dirname to locate its WASM binary, which
274
+ // doesn't exist in ESM. Under pnpm's strict symlinks the binary
275
+ // can't be found unless the consumer installs canvaskit-wasm
276
+ // directly. Probe resolution from the consumer's project root
277
+ // (not the theme package) so a missing install skips OG
278
+ // gracefully instead of crashing the build.
279
+ let canvaskitUsable = true;
280
+ try {
281
+ const require = createRequire(join(process.cwd(), "package.json"));
282
+ require.resolve("canvaskit-wasm");
283
+ } catch {
284
+ canvaskitUsable = false;
285
+ }
286
+
287
+ if (!canvaskitUsable) {
288
+ logger.warn(
289
+ "OG image generation requires canvaskit-wasm, which could not be loaded.\n" +
290
+ " pnpm users: run `pnpm add canvaskit-wasm`\n" +
291
+ " The build will continue without OG images.",
292
+ );
293
+ } else {
294
+ themeConfig.ogImage.enabled = true;
295
+ themeConfig.ogImage.logoPath = faviconPath;
296
+ const rawTitle = starlightConfig.title;
297
+ themeConfig.ogImage.siteTitle =
298
+ typeof rawTitle === "string"
299
+ ? rawTitle
300
+ : (Object.values(rawTitle as Record<string, string>)[0] ?? "");
301
+
302
+ addRouteMiddleware({
303
+ entrypoint: "@nu-appdev/northwestern-starlight-theme/src/og/route-middleware",
304
+ order: "post",
305
+ });
306
+
307
+ addIntegration({
308
+ name: "northwestern-theme-og-image",
309
+ hooks: {
310
+ "astro:config:setup": ({ injectRoute }) => {
311
+ injectRoute({
312
+ pattern: "/og/[...slug]",
313
+ entrypoint: "@nu-appdev/northwestern-starlight-theme/src/og/endpoint.ts",
314
+ });
315
+ },
316
+ },
317
+ });
318
+
319
+ logger.info("OG image generation enabled");
320
+ }
321
+ }
322
+
247
323
  updateConfig({
248
324
  ...(consumerSetFavicon ? {} : { favicon: "/favicon.png" }),
249
325
  components: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nu-appdev/northwestern-starlight-theme",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "A Northwestern-branded theme for Astro Starlight",
5
5
  "license": "MIT",
6
6
  "author": "Danny Foster <danny@northwestern.edu>",
@@ -44,6 +44,7 @@
44
44
  },
45
45
  "./src/styles/components/*": "./src/styles/components/*",
46
46
  "./src/components/*": "./src/components/*",
47
+ "./src/og/*": "./src/og/*",
47
48
  "./src/scripts/*": "./src/scripts/*",
48
49
  "./src/styles/*": "./src/styles/*"
49
50
  },
@@ -56,6 +57,7 @@
56
57
  ],
57
58
  "dependencies": {
58
59
  "@expressive-code/plugin-line-numbers": "^0.41.7",
60
+ "astro-og-canvas": "^0.10.1",
59
61
  "khroma": "^2.1.0"
60
62
  },
61
63
  "peerDependencies": {
@@ -130,6 +130,7 @@ const imageWidth = themeConfig.homepage.imageWidth;
130
130
  line-height: var(--sl-line-height-headings);
131
131
  font-weight: 600;
132
132
  color: var(--nu-purple-100);
133
+ box-shadow: none;
133
134
  }
134
135
 
135
136
  :root:not([data-theme="light"]) h1 {
@@ -0,0 +1,53 @@
1
+ import { getCollection } from "astro:content";
2
+ import config from "virtual:northwestern-theme/config";
3
+ import { OGImageRoute } from "astro-og-canvas";
4
+
5
+ const siteTitle = config.ogImage.siteTitle;
6
+ const logoPath = config.ogImage.logoPath;
7
+ const docs = await getCollection("docs");
8
+ let changelogs: typeof docs = [];
9
+ try {
10
+ changelogs = await getCollection("changelogs" as "docs");
11
+ } catch {}
12
+ const pages = Object.fromEntries([...docs, ...changelogs].map((entry) => [entry.id, entry]));
13
+
14
+ export const { getStaticPaths, GET } = await OGImageRoute({
15
+ param: "slug",
16
+ pages,
17
+ getImageOptions: (path, page) => {
18
+ const isIndex = path === "index";
19
+ const isChangelogVersion = path.startsWith("changelog/version/");
20
+ const title = isIndex ? siteTitle : isChangelogVersion ? `Changelog\n${page.data.title}` : page.data.title;
21
+ return {
22
+ title,
23
+ description: page.data.description,
24
+ logo: { path: logoPath, size: [60] },
25
+ bgGradient: [[64, 31, 104]],
26
+ padding: 120,
27
+ border: {
28
+ color: [164, 149, 195],
29
+ width: 12,
30
+ side: "inline-start",
31
+ },
32
+ font: {
33
+ title: {
34
+ families: ["Poppins"],
35
+ weight: "Bold",
36
+ size: isChangelogVersion ? 56 : 48,
37
+ lineHeight: 1.3,
38
+ color: [255, 255, 255],
39
+ },
40
+ description: {
41
+ families: ["Akkurat Pro"],
42
+ size: 28,
43
+ lineHeight: 1.5,
44
+ color: [182, 172, 209],
45
+ },
46
+ },
47
+ fonts: [
48
+ "https://common.northwestern.edu/v8/css/fonts/Poppins-Bold.woff",
49
+ "https://common.northwestern.edu/v8/css/fonts/AkkuratProRegular.woff",
50
+ ],
51
+ };
52
+ },
53
+ });
@@ -0,0 +1,125 @@
1
+ import { defineRouteMiddleware } from "@astrojs/starlight/route-data";
2
+ import type { HeadConfig } from "@astrojs/starlight/schemas/head";
3
+
4
+ type HeadEntry = HeadConfig[number];
5
+ type JsonLd = Record<string, unknown>;
6
+
7
+ function getStructuredData({
8
+ imageUrl,
9
+ isHomepage,
10
+ isChangelogVersion,
11
+ logoUrl,
12
+ pageUrl,
13
+ siteName,
14
+ title,
15
+ description,
16
+ }: {
17
+ imageUrl: string;
18
+ isHomepage: boolean;
19
+ isChangelogVersion: boolean;
20
+ logoUrl: string;
21
+ pageUrl: string;
22
+ siteName: string;
23
+ title: string;
24
+ description?: string;
25
+ }): JsonLd {
26
+ const origin = new URL(pageUrl).origin;
27
+ const organization = {
28
+ "@id": `${origin}/#organization`,
29
+ "@type": "Organization",
30
+ name: "Northwestern University",
31
+ url: "https://www.northwestern.edu",
32
+ logo: logoUrl,
33
+ };
34
+ const website = {
35
+ "@id": `${origin}/#website`,
36
+ "@type": "WebSite",
37
+ name: siteName,
38
+ url: `${origin}/`,
39
+ description,
40
+ image: imageUrl,
41
+ publisher: { "@id": organization["@id"] },
42
+ };
43
+
44
+ if (isHomepage) {
45
+ return {
46
+ "@context": "https://schema.org",
47
+ "@graph": [organization, website],
48
+ };
49
+ }
50
+
51
+ return {
52
+ "@context": "https://schema.org",
53
+ "@graph": [
54
+ organization,
55
+ website,
56
+ {
57
+ "@type": isChangelogVersion ? "Article" : "TechArticle",
58
+ headline: title,
59
+ name: title,
60
+ description,
61
+ image: imageUrl,
62
+ url: pageUrl,
63
+ mainEntityOfPage: pageUrl,
64
+ isPartOf: { "@id": website["@id"] },
65
+ publisher: { "@id": organization["@id"] },
66
+ },
67
+ ],
68
+ };
69
+ }
70
+
71
+ export const onRequest = defineRouteMiddleware((context) => {
72
+ const route = context.locals.starlightRoute;
73
+ const site = context.site;
74
+ if (!site) return;
75
+
76
+ const pageUrl = new URL(context.url.pathname, site).href;
77
+ const siteName = route.siteTitle ?? route.entry.data.title;
78
+ const isHomepage = route.id === "index" || context.url.pathname === "/";
79
+ const isChangelogVersion = route.id?.startsWith("changelog/version/") ?? false;
80
+ const ogPath = route.id ? `/og/${route.id}.png` : "/og/index.png";
81
+ const imageUrl = new URL(ogPath, site).href;
82
+
83
+ // Replace Starlight's twitter:card (defaults to "summary" on non-splash
84
+ // pages) with "summary_large_image" so the 1200x630 image isn't cropped.
85
+ const twitterCardIndex = route.head.findIndex(
86
+ (entry: HeadEntry) => entry.tag === "meta" && entry.attrs.name === "twitter:card",
87
+ );
88
+ if (twitterCardIndex !== -1) {
89
+ route.head[twitterCardIndex] = {
90
+ tag: "meta",
91
+ attrs: { name: "twitter:card", content: "summary_large_image" },
92
+ };
93
+ }
94
+
95
+ // Resolve the favicon path from Starlight's head tags for og:logo.
96
+ const faviconEntry = route.head.find(
97
+ (entry: HeadEntry) => entry.tag === "link" && entry.attrs.rel === "shortcut icon",
98
+ );
99
+ const faviconHref = faviconEntry?.attrs.href ?? "/favicon.png";
100
+ const logoUrl = new URL(faviconHref, site).href;
101
+ const structuredData = JSON.stringify(
102
+ getStructuredData({
103
+ imageUrl,
104
+ isHomepage,
105
+ isChangelogVersion,
106
+ logoUrl,
107
+ pageUrl,
108
+ siteName,
109
+ title: route.entry.data.title,
110
+ description: route.entry.data.description,
111
+ }),
112
+ );
113
+
114
+ route.head.push(
115
+ { tag: "meta", attrs: { property: "og:image", content: imageUrl } },
116
+ { tag: "meta", attrs: { property: "og:image:type", content: "image/png" } },
117
+ { tag: "meta", attrs: { property: "og:image:width", content: "1200" } },
118
+ { tag: "meta", attrs: { property: "og:image:height", content: "630" } },
119
+ { tag: "meta", attrs: { property: "og:image:alt", content: route.entry.data.title } },
120
+ { tag: "meta", attrs: { property: "og:logo", content: logoUrl } },
121
+ { tag: "meta", attrs: { name: "twitter:image", content: imageUrl } },
122
+ { tag: "meta", attrs: { name: "twitter:image:alt", content: route.entry.data.title } },
123
+ { tag: "script", attrs: { type: "application/ld+json" }, content: structuredData },
124
+ );
125
+ });
@@ -7,8 +7,9 @@
7
7
  padding: 1rem 1.5rem;
8
8
  margin: 2rem 0;
9
9
  color: var(--nu-color-text);
10
- background: rgb(228 224 238 / 42%);
10
+ background: #f9f6ff;
11
11
  border-inline-start: 10px solid var(--nu-purple-100);
12
+ border-radius: 0 0.75rem 0.75rem 0 !important;
12
13
  }
13
14
 
14
15
  .sl-markdown-content blockquote:not(:where(.not-content *)) p:last-child {
@@ -557,6 +557,7 @@
557
557
  .nu-property-table:not(.nu-property-table--table) .nu-property__head {
558
558
  flex-direction: column;
559
559
  gap: 0.25rem;
560
+ align-items: flex-start;
560
561
  }
561
562
 
562
563
  .nu-property-table:not(.nu-property-table--table) .nu-property__prop-col {
@@ -16,18 +16,20 @@
16
16
  }
17
17
 
18
18
  .sl-markdown-content table:not(:where(.not-content *)) > thead > tr > th {
19
- padding: 0.5rem 1rem;
20
- font-family: var(--nu-font-heading);
21
- font-weight: 600;
22
- color: var(--nu-black-80);
19
+ padding: 0.75rem 0.5rem;
20
+ font-family: var(--nu-font-body);
21
+ font-size: 1rem;
22
+ font-weight: 700;
23
+ color: #fff;
23
24
  text-align: left;
24
- background-color: var(--nu-purple-surface);
25
+ background-color: var(--nu-purple-100);
25
26
  border: 1px solid var(--nu-border-color);
27
+ border-right: 1px solid rgb(255 255 255 / 30%);
28
+ border-left: 1px solid rgb(255 255 255 / 30%);
26
29
  }
27
30
 
28
31
  :root:not([data-theme="light"]) .sl-markdown-content table:not(:where(.not-content *)) > thead > tr > th {
29
- color: var(--nu-color-text);
30
- background-color: rgb(78 42 132 / 14%);
32
+ background-color: rgb(78 42 132 / 40%);
31
33
  }
32
34
 
33
35
  .sl-markdown-content table:not(:where(.not-content *)) > tbody > tr > td {
@@ -46,15 +48,19 @@
46
48
  text-align: center;
47
49
  }
48
50
 
49
- .sl-markdown-content table:not(:where(.not-content *)) > tbody > tr:nth-child(even) {
50
- background-color: #f3f4f6;
51
+ .sl-markdown-content table:not(:where(.not-content *)) > tbody > tr:nth-child(odd) {
52
+ background-color: #f9f6ff;
53
+ }
54
+
55
+ .sl-markdown-content table:not(:where(.not-content *)) > tbody > tr:hover {
56
+ background-color: #f0f0f0;
57
+ }
58
+
59
+ :root:not([data-theme="light"]) .sl-markdown-content table:not(:where(.not-content *)) > tbody > tr:nth-child(odd) {
60
+ background-color: rgb(78 42 132 / 10%);
51
61
  }
52
62
 
53
- :root:not([data-theme="light"])
54
- .sl-markdown-content
55
- table:not(:where(.not-content *))
56
- > tbody
57
- > tr:nth-child(even) {
58
- background-color: #1e1e1e;
63
+ :root:not([data-theme="light"]) .sl-markdown-content table:not(:where(.not-content *)) > tbody > tr:hover {
64
+ background-color: rgb(255 255 255 / 5%);
59
65
  }
60
66
  }
@@ -39,7 +39,7 @@
39
39
  width: 6px;
40
40
  height: 6px;
41
41
  content: "";
42
- background: var(--nu-purple-40);
42
+ background: var(--nu-purple-100);
43
43
  }
44
44
 
45
45
  :root:not([data-theme="light"]) .sl-markdown-content ul:not(:where(.not-content *)) > li::before {
@@ -56,10 +56,16 @@
56
56
  border-color: var(--nu-purple-30);
57
57
  }
58
58
 
59
+ .sl-markdown-content mark:not(:where(.not-content *)),
60
+ .sl-markdown-content .highlight:not(:where(.not-content *)) {
61
+ padding: 3px;
62
+ background: rgb(228 224 238 / 50%);
63
+ }
64
+
59
65
  .sl-markdown-content hr:not(:where(.not-content *)) {
60
- height: 1px;
66
+ height: 3px;
61
67
  margin: 2.5rem 0;
62
- background: var(--nu-border-color);
68
+ background: var(--nu-purple-10);
63
69
  border: 0;
64
70
  }
65
71
 
@@ -78,7 +78,15 @@
78
78
  font-family: Poppins;
79
79
  font-style: normal;
80
80
  font-weight: 400;
81
- src: url("https://common.northwestern.edu/v8/css/fonts/Poppins-Light.woff") format("woff");
81
+ src: url("https://common.northwestern.edu/v8/css/fonts/Poppins-Regular.woff") format("woff");
82
+ font-display: swap;
83
+ }
84
+
85
+ @font-face {
86
+ font-family: Poppins;
87
+ font-style: normal;
88
+ font-weight: 500;
89
+ src: url("https://common.northwestern.edu/dept/4.0/css/fonts/poppins/poppins-v21-latin-500.woff2") format("woff2");
82
90
  font-display: swap;
83
91
  }
84
92
 
@@ -150,18 +158,81 @@
150
158
 
151
159
  h1,
152
160
  h2 {
153
- font-family: var(--nu-font-heading);
154
- }
155
-
156
- h1 {
157
- color: var(--sl-color-text-accent);
161
+ font-family: var(--nu-font-display);
158
162
  }
159
163
 
160
164
  h3,
161
165
  h4,
162
166
  h5,
163
167
  h6 {
164
- font-family: var(--nu-font-body);
168
+ font-family: var(--nu-font-heading);
169
+ }
170
+
171
+ .main-pane h1 {
172
+ display: inline;
173
+ font-size: clamp(2rem, 1rem + 3vw, 2.8125rem);
174
+ font-weight: 500;
175
+ line-height: 1.4;
176
+ color: #000;
177
+ box-shadow: inset 0 -0.55em 0 0 var(--nu-purple-10);
178
+ box-decoration-break: clone;
179
+ }
180
+
181
+ :root:not([data-theme="light"]) .main-pane h1 {
182
+ color: #fff;
183
+ box-shadow: inset 0 -0.55em 0 0 var(--nu-purple-140);
184
+ }
185
+
186
+ .sl-markdown-content h2 {
187
+ font-size: 1.875rem;
188
+ font-weight: 700;
189
+ line-height: 1.4;
190
+ color: var(--nu-purple-120);
191
+ }
192
+
193
+ .sl-markdown-content h3 {
194
+ font-size: 1.625rem;
195
+ font-weight: 500;
196
+ line-height: 1.4;
197
+ color: var(--nu-black-80);
198
+ }
199
+
200
+ .sl-markdown-content h4 {
201
+ font-size: 1.4375rem;
202
+ font-weight: 500;
203
+ line-height: 1.4;
204
+ color: var(--nu-purple-120);
205
+ }
206
+
207
+ .sl-markdown-content h5 {
208
+ font-size: 1.25rem;
209
+ font-weight: 500;
210
+ line-height: 1.4;
211
+ color: var(--nu-black-80);
212
+ }
213
+
214
+ .sl-markdown-content h6 {
215
+ font-size: 1.125rem;
216
+ font-weight: 500;
217
+ line-height: 1.4;
218
+ color: var(--nu-black-80);
219
+ }
220
+
221
+ :root:not([data-theme="light"]) .sl-markdown-content h2 {
222
+ color: var(--nu-purple-30);
223
+ }
224
+
225
+ :root:not([data-theme="light"]) .sl-markdown-content h3 {
226
+ color: var(--nu-color-text);
227
+ }
228
+
229
+ :root:not([data-theme="light"]) .sl-markdown-content h4 {
230
+ color: var(--nu-purple-30);
231
+ }
232
+
233
+ :root:not([data-theme="light"]) .sl-markdown-content h5,
234
+ :root:not([data-theme="light"]) .sl-markdown-content h6 {
235
+ color: var(--nu-color-text);
165
236
  }
166
237
 
167
238
  .site-title {
@@ -55,12 +55,12 @@
55
55
  --nu-transition-default: 200ms ease;
56
56
  --nu-transition-slow: 300ms ease-in-out;
57
57
 
58
- --nu-purple-surface: #f3f0f7;
59
- --nu-purple-surface-subtle: #f7f5f9;
58
+ --nu-purple-surface: #f9f6ff;
59
+ --nu-purple-surface-subtle: #f3f0f7;
60
60
  --nu-purple-tint: rgb(78 42 132 / 15%);
61
61
  --nu-purple-tint-strong: rgb(78 42 132 / 20%);
62
62
  --nu-surface-muted: #f9fafb;
63
- --nu-hover-bg: #f3f4f6;
63
+ --nu-hover-bg: #f0f0f0;
64
64
 
65
65
  --nu-focus-ring: var(--nu-purple-100);
66
66
  --nu-focus-border: #a795c2;
@@ -90,10 +90,10 @@
90
90
  --nu-focus-ring: var(--nu-purple-100);
91
91
  --nu-focus-shadow-color: rgb(78 42 132 / 25%);
92
92
 
93
- --nu-purple-surface: #f3f0f7;
94
- --nu-purple-surface-subtle: #f7f5f9;
93
+ --nu-purple-surface: #f9f6ff;
94
+ --nu-purple-surface-subtle: #f3f0f7;
95
95
  --nu-surface-muted: #f9fafb;
96
- --nu-hover-bg: #f3f4f6;
96
+ --nu-hover-bg: #f0f0f0;
97
97
  }
98
98
 
99
99
  :root:not([data-theme="light"]),
package/src/virtual.d.ts CHANGED
@@ -14,6 +14,11 @@ declare module "virtual:northwestern-theme/config" {
14
14
  showTitle: boolean;
15
15
  imageWidth: string;
16
16
  };
17
+ ogImage: {
18
+ enabled: boolean;
19
+ siteTitle: string;
20
+ logoPath: string;
21
+ };
17
22
  };
18
23
  export default config;
19
24
  }