@devdogsuga/newsletter 0.1.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/README.md +193 -0
- package/dist/assets.d.ts +54 -0
- package/dist/assets.js +42 -0
- package/dist/components.d.ts +21 -0
- package/dist/components.js +269 -0
- package/dist/darkmode.d.ts +45 -0
- package/dist/darkmode.js +138 -0
- package/dist/export/eml.d.ts +39 -0
- package/dist/export/eml.js +52 -0
- package/dist/export/index.d.ts +29 -0
- package/dist/export/index.js +82 -0
- package/dist/generated/lockup.d.ts +6 -0
- package/dist/generated/lockup.js +9 -0
- package/dist/icons.d.ts +16 -0
- package/dist/icons.js +29 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +24 -0
- package/dist/issues.d.ts +39 -0
- package/dist/issues.js +146 -0
- package/dist/theme.d.ts +123 -0
- package/dist/theme.js +155 -0
- package/package.json +51 -0
package/dist/icons.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The footer's social icons: Phosphor regular-weight path data on the 256-unit
|
|
3
|
+
* grid, kept here rather than taken from `@devdogsuga/og`'s generated set for
|
|
4
|
+
* two reasons — og carries no Discord mark, and its icons are the bold cut
|
|
5
|
+
* while a 17px footer glyph wants the regular one.
|
|
6
|
+
*
|
|
7
|
+
* They ship as paths so the fill stays the caller's to choose; `iconSvg`
|
|
8
|
+
* wraps one in a standalone `<svg>` for rasterising or for a data URI.
|
|
9
|
+
*/
|
|
10
|
+
import { PALETTE } from "./theme.js";
|
|
11
|
+
export const SOCIAL_ICON_PATHS = {
|
|
12
|
+
/** Phosphor `DiscordLogo`, regular. */
|
|
13
|
+
discord: "M104,140a12,12,0,1,1-12-12A12,12,0,0,1,104,140Zm60-12a12,12,0,1,0,12,12A12,12,0,0,0,164,128Zm74.45,64.9-67,29.71a16.17,16.17,0,0,1-21.71-9.1l-8.11-22q-6.72.45-13.63.46t-13.63-.46l-8.11,22a16.18,16.18,0,0,1-21.71,9.1l-67-29.71a15.93,15.93,0,0,1-9.06-18.51L38,58A16.07,16.07,0,0,1,51,46.14l36.06-5.93a16.22,16.22,0,0,1,18.26,11.88l3.26,12.84Q118.11,64,128,64t19.4.93l3.26-12.84a16.21,16.21,0,0,1,18.26-11.88L205,46.14A16.07,16.07,0,0,1,218,58l29.53,116.38A15.93,15.93,0,0,1,238.45,192.9ZM232,178.28,202.47,62s0,0-.08,0L166.33,56a.17.17,0,0,0-.17,0l-2.83,11.14c5,.94,10,2.06,14.83,3.42A8,8,0,0,1,176,86.31a8.09,8.09,0,0,1-2.16-.3A172.25,172.25,0,0,0,128,80a172.25,172.25,0,0,0-45.84,6,8,8,0,1,1-4.32-15.4c4.82-1.36,9.78-2.48,14.82-3.42L89.83,56s0,0-.12,0h0L53.61,61.93a.17.17,0,0,0-.09,0L24,178.33,91,208a.23.23,0,0,0,.22,0L98,189.72a173.2,173.2,0,0,1-20.14-4.32A8,8,0,0,1,82.16,170,171.85,171.85,0,0,0,128,176a171.85,171.85,0,0,0,45.84-6,8,8,0,0,1,4.32,15.41A173.2,173.2,0,0,1,158,189.72L164.75,208a.22.22,0,0,0,.21,0Z",
|
|
14
|
+
/** Phosphor `InstagramLogo`, regular. */
|
|
15
|
+
instagram: "M128,80a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160ZM176,24H80A56.06,56.06,0,0,0,24,80v96a56.06,56.06,0,0,0,56,56h96a56.06,56.06,0,0,0,56-56V80A56.06,56.06,0,0,0,176,24Zm40,152a40,40,0,0,1-40,40H80a40,40,0,0,1-40-40V80A40,40,0,0,1,80,40h96a40,40,0,0,1,40,40ZM192,76a12,12,0,1,1-12-12A12,12,0,0,1,192,76Z",
|
|
16
|
+
/** Phosphor `GithubLogo`, regular. */
|
|
17
|
+
github: "M208.31,75.68A59.78,59.78,0,0,0,202.93,28,8,8,0,0,0,196,24a59.75,59.75,0,0,0-48,24H124A59.75,59.75,0,0,0,76,24a8,8,0,0,0-6.93,4,59.78,59.78,0,0,0-5.38,47.68A58.14,58.14,0,0,0,56,104v8a56.06,56.06,0,0,0,48.44,55.47A39.8,39.8,0,0,0,96,192v8H72a24,24,0,0,1-24-24A40,40,0,0,0,8,136a8,8,0,0,0,0,16,24,24,0,0,1,24,24,40,40,0,0,0,40,40H96v16a8,8,0,0,0,16,0V192a24,24,0,0,1,48,0v40a8,8,0,0,0,16,0V192a39.8,39.8,0,0,0-8.44-24.53A56.06,56.06,0,0,0,216,112v-8A58.14,58.14,0,0,0,208.31,75.68ZM200,112a40,40,0,0,1-40,40H112a40,40,0,0,1-40-40v-8a41.74,41.74,0,0,1,6.9-22.48A8,8,0,0,0,80,73.83a43.81,43.81,0,0,1,.79-33.58,43.88,43.88,0,0,1,32.32,20.06A8,8,0,0,0,119.82,64h32.35a8,8,0,0,0,6.74-3.69,43.87,43.87,0,0,1,32.32-20.06A43.81,43.81,0,0,1,192,73.83a8.09,8.09,0,0,0,1,7.65A41.72,41.72,0,0,1,200,104Z",
|
|
18
|
+
/** Phosphor `Globe`, regular. */
|
|
19
|
+
globe: "M128,24h0A104,104,0,1,0,232,128,104.12,104.12,0,0,0,128,24Zm88,104a87.61,87.61,0,0,1-3.33,24H174.16a157.44,157.44,0,0,0,0-48h38.51A87.61,87.61,0,0,1,216,128ZM102,168H154a115.11,115.11,0,0,1-26,45A115.27,115.27,0,0,1,102,168Zm-3.9-16a140.84,140.84,0,0,1,0-48h59.88a140.84,140.84,0,0,1,0,48ZM40,128a87.61,87.61,0,0,1,3.33-24H81.84a157.44,157.44,0,0,0,0,48H43.33A87.61,87.61,0,0,1,40,128ZM154,88H102a115.11,115.11,0,0,1,26-45A115.27,115.27,0,0,1,154,88Zm52.33,0H170.71a135.28,135.28,0,0,0-22.3-45.6A88.29,88.29,0,0,1,206.37,88ZM107.59,42.4A135.28,135.28,0,0,0,85.29,88H49.63A88.29,88.29,0,0,1,107.59,42.4ZM49.63,168H85.29a135.28,135.28,0,0,0,22.3,45.6A88.29,88.29,0,0,1,49.63,168Zm98.78,45.6a135.28,135.28,0,0,0,22.3-45.6h35.66A88.29,88.29,0,0,1,148.41,213.6Z",
|
|
20
|
+
};
|
|
21
|
+
export const SOCIAL_ICON_NAMES = Object.keys(SOCIAL_ICON_PATHS);
|
|
22
|
+
/** A standalone `<svg>` around one icon path, ready to rasterise or inline. */
|
|
23
|
+
export function socialIconSvg(name, fill = PALETTE.ink) {
|
|
24
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="${fill}"><path d="${SOCIAL_ICON_PATHS[name]}"/></svg>`;
|
|
25
|
+
}
|
|
26
|
+
/** The same icon as an `<img>` source for the page render, where SVG is safe. */
|
|
27
|
+
export function socialIconDataUri(name) {
|
|
28
|
+
return `data:image/svg+xml;utf8,${encodeURIComponent(socialIconSvg(name))}`;
|
|
29
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser-safe surface: components, content and tokens, with nothing that
|
|
3
|
+
* assumes Node. The platform's /changelog pages import from here.
|
|
4
|
+
*
|
|
5
|
+
* The export-side surface (react-dom/server, MIME assembly, the cid: image
|
|
6
|
+
* manifest) lives behind `@devdogsuga/newsletter/export` so bundling a page
|
|
7
|
+
* never drags a server renderer along.
|
|
8
|
+
*/
|
|
9
|
+
export { ChangelogDocument, ChangelogEmail } from "./components.js";
|
|
10
|
+
export { paintCss } from "./darkmode.js";
|
|
11
|
+
export { DATA_URI_ASSETS, DEVDOGS_LOCKUP, GDGC_UGA, MARK_SIZES, type NewsletterAssets, type RenderContext, } from "./assets.js";
|
|
12
|
+
export { type ChangelogEvent, type ChangelogIssue, ISSUES, issueByVersion, } from "./issues.js";
|
|
13
|
+
export { SOCIAL_ICON_NAMES, socialIconDataUri, socialIconSvg, type SocialIconName, } from "./icons.js";
|
|
14
|
+
export { CSS_VARIABLE_FONTS, EMAIL_FONTS, FONTS_HREF, type FontStacks, KIND, PALETTE, SITE, SOCIAL_LINKS, UGA, } from "./theme.js";
|
|
15
|
+
import { type RenderContext } from "./assets.js";
|
|
16
|
+
/**
|
|
17
|
+
* The context a platform page renders with: the `next/font` CSS variables the
|
|
18
|
+
* root layout sets (next/font renames every family it self-hosts, so literal
|
|
19
|
+
* names would never match) and SVG data-URI image sources.
|
|
20
|
+
*/
|
|
21
|
+
export declare function webRenderContext(): RenderContext;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser-safe surface: components, content and tokens, with nothing that
|
|
3
|
+
* assumes Node. The platform's /changelog pages import from here.
|
|
4
|
+
*
|
|
5
|
+
* The export-side surface (react-dom/server, MIME assembly, the cid: image
|
|
6
|
+
* manifest) lives behind `@devdogsuga/newsletter/export` so bundling a page
|
|
7
|
+
* never drags a server renderer along.
|
|
8
|
+
*/
|
|
9
|
+
export { ChangelogDocument, ChangelogEmail } from "./components.js";
|
|
10
|
+
export { paintCss } from "./darkmode.js";
|
|
11
|
+
export { DATA_URI_ASSETS, DEVDOGS_LOCKUP, GDGC_UGA, MARK_SIZES, } from "./assets.js";
|
|
12
|
+
export { ISSUES, issueByVersion, } from "./issues.js";
|
|
13
|
+
export { SOCIAL_ICON_NAMES, socialIconDataUri, socialIconSvg, } from "./icons.js";
|
|
14
|
+
export { CSS_VARIABLE_FONTS, EMAIL_FONTS, FONTS_HREF, KIND, PALETTE, SITE, SOCIAL_LINKS, UGA, } from "./theme.js";
|
|
15
|
+
import { DATA_URI_ASSETS } from "./assets.js";
|
|
16
|
+
import { CSS_VARIABLE_FONTS } from "./theme.js";
|
|
17
|
+
/**
|
|
18
|
+
* The context a platform page renders with: the `next/font` CSS variables the
|
|
19
|
+
* root layout sets (next/font renames every family it self-hosts, so literal
|
|
20
|
+
* names would never match) and SVG data-URI image sources.
|
|
21
|
+
*/
|
|
22
|
+
export function webRenderContext() {
|
|
23
|
+
return { fonts: CSS_VARIABLE_FONTS, assets: DATA_URI_ASSETS };
|
|
24
|
+
}
|
package/dist/issues.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ChangelogEvent {
|
|
2
|
+
/** The chip label — the calendar's event-type vocabulary. */
|
|
3
|
+
chip: string;
|
|
4
|
+
/** The event-type accent from `KIND`. */
|
|
5
|
+
color: string;
|
|
6
|
+
title: string;
|
|
7
|
+
/** Three-letter weekday, uppercase. */
|
|
8
|
+
dow: string;
|
|
9
|
+
/** `"Sep 9"` — month then day, split for the stacked date column. */
|
|
10
|
+
date: string;
|
|
11
|
+
time: string;
|
|
12
|
+
loc: string;
|
|
13
|
+
rsvp: string | null;
|
|
14
|
+
blurb: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ChangelogIssue {
|
|
17
|
+
/** Semver, doubling as the URL segment and the export filename. */
|
|
18
|
+
version: string;
|
|
19
|
+
term: string;
|
|
20
|
+
/** The title-bar date, `"Wed · Sep 9"`. */
|
|
21
|
+
sendLabel: string;
|
|
22
|
+
/** The prompt line, `"changelog --date 2026-09-09"`. */
|
|
23
|
+
command: string;
|
|
24
|
+
/** The email subject line. */
|
|
25
|
+
title: string;
|
|
26
|
+
/** Inbox preview text, hidden in the body. */
|
|
27
|
+
preview: string;
|
|
28
|
+
tagline: string;
|
|
29
|
+
intro: string;
|
|
30
|
+
/** The `##` heading over the hero card, e.g. `"happening_today"`. */
|
|
31
|
+
featuredLabel: string;
|
|
32
|
+
featured: ChangelogEvent;
|
|
33
|
+
/** The hero card's button label. */
|
|
34
|
+
cta: string;
|
|
35
|
+
upcoming: ChangelogEvent[];
|
|
36
|
+
signoff: string;
|
|
37
|
+
}
|
|
38
|
+
export declare const ISSUES: ChangelogIssue[];
|
|
39
|
+
export declare function issueByVersion(version: string): ChangelogIssue | undefined;
|
package/dist/issues.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Changelog's content: event data authored in `@devdogsuga/events`'s
|
|
3
|
+
* meetings (times converted to ET) and the dated sends themselves.
|
|
4
|
+
*
|
|
5
|
+
* Issues are versioned with semver — a send is a release. Event copy lives
|
|
6
|
+
* here rather than in the components so a copy pass before a send touches one
|
|
7
|
+
* file, and so the platform's archive pages and the exported emails can never
|
|
8
|
+
* disagree about what an issue said.
|
|
9
|
+
*/
|
|
10
|
+
import { KIND } from "./theme.js";
|
|
11
|
+
const DEV_SESSION_BLURB = "Catch up on workshop materials, meet your teammates for hackathons, get unblocked by focus leads and officers, or just come hang out and get work done.";
|
|
12
|
+
const EVENTS = {
|
|
13
|
+
interest: {
|
|
14
|
+
chip: "Interest Meeting",
|
|
15
|
+
color: KIND.interest,
|
|
16
|
+
title: "Interest Meeting",
|
|
17
|
+
dow: "WED",
|
|
18
|
+
date: "Sep 9",
|
|
19
|
+
time: "6:00 – 7:00 PM",
|
|
20
|
+
loc: "DLW 110",
|
|
21
|
+
rsvp: "https://uga.campuslabs.com/engage/event/12664203",
|
|
22
|
+
blurb: "Come meet the new leadership team and get the details on all things DevDogs. Returning? Learn what's changing, what's staying the same, and how to get involved this year. Free food, too.",
|
|
23
|
+
},
|
|
24
|
+
coldstart: {
|
|
25
|
+
chip: "Kickoff",
|
|
26
|
+
color: KIND.workshop,
|
|
27
|
+
title: "Cold Start",
|
|
28
|
+
dow: "MON",
|
|
29
|
+
date: "Sep 14",
|
|
30
|
+
time: "6:00 – 7:30 PM",
|
|
31
|
+
loc: "DLW 124",
|
|
32
|
+
rsvp: "https://uga.campuslabs.com/engage/event/12664196",
|
|
33
|
+
blurb: "The inaugural meeting for the 2026–2027 year. Get set up to contribute to this year's projects. Plus, a collaborative coding workshop: an introduction to Git, GitHub, and how to contribute to a team project.",
|
|
34
|
+
},
|
|
35
|
+
dev1: {
|
|
36
|
+
chip: "Dev Session",
|
|
37
|
+
color: KIND.build,
|
|
38
|
+
title: "Dev Session #1",
|
|
39
|
+
dow: "WED",
|
|
40
|
+
date: "Sep 16",
|
|
41
|
+
time: "6:00 – 7:00 PM",
|
|
42
|
+
loc: "DLW 124",
|
|
43
|
+
rsvp: "https://uga.campuslabs.com/engage/event/12664183",
|
|
44
|
+
blurb: DEV_SESSION_BLURB,
|
|
45
|
+
},
|
|
46
|
+
// The three workshop nights have no summary in events yet — these
|
|
47
|
+
// blurbs are authored from their linked workshop topics; swap in the real
|
|
48
|
+
// copy once it lands in the meetings config.
|
|
49
|
+
nextflutter: {
|
|
50
|
+
chip: "Workshop",
|
|
51
|
+
color: KIND.workshop,
|
|
52
|
+
title: "Workshops: Next.js & Flutter",
|
|
53
|
+
dow: "MON",
|
|
54
|
+
date: "Sep 21",
|
|
55
|
+
time: "6:00 – 7:30 PM",
|
|
56
|
+
loc: "DLW 124",
|
|
57
|
+
rsvp: null,
|
|
58
|
+
blurb: "A framework double-header: build for the web with Next.js and go cross-platform with Flutter.",
|
|
59
|
+
},
|
|
60
|
+
dev2: {
|
|
61
|
+
chip: "Dev Session",
|
|
62
|
+
color: KIND.build,
|
|
63
|
+
title: "Dev Session #2",
|
|
64
|
+
dow: "WED",
|
|
65
|
+
date: "Sep 23",
|
|
66
|
+
time: "6:00 – 7:00 PM",
|
|
67
|
+
loc: "DLW 124",
|
|
68
|
+
rsvp: "https://uga.campuslabs.com/engage/event/12664184",
|
|
69
|
+
blurb: DEV_SESSION_BLURB,
|
|
70
|
+
},
|
|
71
|
+
supabase: {
|
|
72
|
+
chip: "Workshop",
|
|
73
|
+
color: KIND.workshop,
|
|
74
|
+
title: "Workshop: Supabase",
|
|
75
|
+
dow: "MON",
|
|
76
|
+
date: "Sep 28",
|
|
77
|
+
time: "6:00 – 7:30 PM",
|
|
78
|
+
loc: "DLW 124",
|
|
79
|
+
rsvp: null,
|
|
80
|
+
blurb: "Get hands-on with Supabase: Postgres, auth, and realtime data for this year's projects.",
|
|
81
|
+
},
|
|
82
|
+
dev3: {
|
|
83
|
+
chip: "Dev Session",
|
|
84
|
+
color: KIND.build,
|
|
85
|
+
title: "Dev Session #3",
|
|
86
|
+
dow: "WED",
|
|
87
|
+
date: "Sep 30",
|
|
88
|
+
time: "6:00 – 7:00 PM",
|
|
89
|
+
loc: "DLW 124",
|
|
90
|
+
rsvp: "https://uga.campuslabs.com/engage/event/12664184",
|
|
91
|
+
blurb: DEV_SESSION_BLURB,
|
|
92
|
+
},
|
|
93
|
+
career: {
|
|
94
|
+
chip: "Workshop",
|
|
95
|
+
color: KIND.workshop,
|
|
96
|
+
title: "Workshop: Career Fair Readiness",
|
|
97
|
+
dow: "MON",
|
|
98
|
+
date: "Oct 5",
|
|
99
|
+
time: "6:00 – 8:00 PM",
|
|
100
|
+
loc: "DLW 110",
|
|
101
|
+
rsvp: null,
|
|
102
|
+
blurb: "Get ready for the fall career fair: resume polish, portfolio pointers, and how to talk about what you've built.",
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
export const ISSUES = [
|
|
106
|
+
{
|
|
107
|
+
version: "3.0.0",
|
|
108
|
+
term: "Fall 2026",
|
|
109
|
+
sendLabel: "Mon · Sep 14",
|
|
110
|
+
command: "changelog --date 2026-09-14",
|
|
111
|
+
title: "DevDogs Changelog v3.0.0: Get Started Tonight!",
|
|
112
|
+
preview: "Tonight at 6: Cold Start. Get set up to build with us this semester.",
|
|
113
|
+
tagline: "Let's boot up!",
|
|
114
|
+
intro: "Missed the interest meeting? No problem. Cold Start is the inaugural meeting of the year. Get set up to contribute to this semester's projects, with onboarding we've streamlined so you leave ready to build.",
|
|
115
|
+
featuredLabel: "happening_tonight",
|
|
116
|
+
featured: EVENTS.coldstart,
|
|
117
|
+
cta: "RSVP for Cold Start",
|
|
118
|
+
upcoming: [
|
|
119
|
+
EVENTS.dev1,
|
|
120
|
+
EVENTS.nextflutter,
|
|
121
|
+
EVENTS.dev2,
|
|
122
|
+
EVENTS.supabase,
|
|
123
|
+
EVENTS.dev3,
|
|
124
|
+
EVENTS.career,
|
|
125
|
+
],
|
|
126
|
+
signoff: "Doors tonight at 6 in DLW 124. Bring a laptop if you have one, and we'll get you set up to ship either way.",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
version: "3.0.1",
|
|
130
|
+
term: "Fall 2026",
|
|
131
|
+
sendLabel: "Mon · Sep 14",
|
|
132
|
+
command: "changelog --date 2026-09-21",
|
|
133
|
+
title: "Workshops You Won't Want to Miss (DevDogs Changelog v3.0.1)",
|
|
134
|
+
preview: "Tonight at 6: Next.js and Flutter Workshops. Get started on your web or mobile application development journey.",
|
|
135
|
+
tagline: "Feature competition #1 kicks off tonight!",
|
|
136
|
+
intro: "But first, they're saying it's the most important workshops of the semester: Flutter and Next.js, the frameworks underpinning this year's projects. Then, we'll be kicking off our first feature competition!",
|
|
137
|
+
featuredLabel: "happening_tonight",
|
|
138
|
+
featured: EVENTS.nextflutter,
|
|
139
|
+
cta: "RSVP for Cold Start",
|
|
140
|
+
upcoming: [EVENTS.dev2, EVENTS.supabase, EVENTS.dev3, EVENTS.career],
|
|
141
|
+
signoff: "Doors tonight at 6 in DLW 124. Bring a laptop if you have one, but we'll get you set up to ship either way.",
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
export function issueByVersion(version) {
|
|
145
|
+
return ISSUES.find((issue) => issue.version === version);
|
|
146
|
+
}
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Changelog's design tokens, transcribed to email-safe literals.
|
|
3
|
+
*
|
|
4
|
+
* Everything here must survive the worst renderer the newsletter will meet —
|
|
5
|
+
* classic Outlook's Word engine — so colors are flat hex (no alpha, no
|
|
6
|
+
* oklch), fonts are longhand stacks, and the site's translucent chip classes
|
|
7
|
+
* (`border-{c}-400/30 bg-{c}-500/10 text-{c}-300`) are precomputed with
|
|
8
|
+
* `mix()` instead of asking a mail client to composite them.
|
|
9
|
+
*/
|
|
10
|
+
import type { CSSProperties } from "react";
|
|
11
|
+
/** The club accent — UGA arch black on red. */
|
|
12
|
+
export declare const UGA = "#ba0c2f";
|
|
13
|
+
/**
|
|
14
|
+
* Event-type colors, taken from the calendar's own vocabulary in
|
|
15
|
+
* `@devdogsuga/brand/event` rather than restated: the chips here and the
|
|
16
|
+
* chips on the site's schedule are the same promise about the same event
|
|
17
|
+
* types.
|
|
18
|
+
*/
|
|
19
|
+
export declare const KIND: {
|
|
20
|
+
readonly interest: "#a684ff";
|
|
21
|
+
readonly build: "#00a6f4";
|
|
22
|
+
readonly study: "#ffb900";
|
|
23
|
+
readonly social: "#ff637e";
|
|
24
|
+
readonly workshop: "#00d492";
|
|
25
|
+
};
|
|
26
|
+
/** The mauve surfaces, matching the platform (mauve-950 canvas → -300 text). */
|
|
27
|
+
export declare const PALETTE: {
|
|
28
|
+
readonly bg: "#13121b";
|
|
29
|
+
readonly bar: "#0e0d15";
|
|
30
|
+
readonly card: "#201e2b";
|
|
31
|
+
readonly card2: "#26232f";
|
|
32
|
+
readonly border: "#332f40";
|
|
33
|
+
readonly ink: "#f3f1f6";
|
|
34
|
+
readonly mute: "#a49eb1";
|
|
35
|
+
readonly dim: "#6f6a80";
|
|
36
|
+
};
|
|
37
|
+
export declare const SITE = "https://devdogsuga.org";
|
|
38
|
+
export declare const SOCIAL_LINKS: readonly [{
|
|
39
|
+
readonly label: "discord";
|
|
40
|
+
readonly icon: "discord";
|
|
41
|
+
readonly url: "https://discord.gg/devdogs";
|
|
42
|
+
}, {
|
|
43
|
+
readonly label: "instagram";
|
|
44
|
+
readonly icon: "instagram";
|
|
45
|
+
readonly url: "https://instagram.com/devdogsuga";
|
|
46
|
+
}, {
|
|
47
|
+
readonly label: "github";
|
|
48
|
+
readonly icon: "github";
|
|
49
|
+
readonly url: "https://github.com/devdogsuga";
|
|
50
|
+
}, {
|
|
51
|
+
readonly label: "web";
|
|
52
|
+
readonly icon: "globe";
|
|
53
|
+
readonly url: "https://devdogsuga.org/?utm_content=linkinbio";
|
|
54
|
+
}];
|
|
55
|
+
/**
|
|
56
|
+
* The site's three typefaces on Google Fonts — the same trio `layout.tsx`
|
|
57
|
+
* loads through next/font. Apple Mail, iOS Mail and most webview clients honor
|
|
58
|
+
* the `<link>`/`@import`; Gmail and Outlook ignore them and fall down the
|
|
59
|
+
* stacks in `EMAIL_FONTS`.
|
|
60
|
+
*/
|
|
61
|
+
export declare const FONTS_HREF = "https://fonts.googleapis.com/css2?family=Alan+Sans:wght@400..900&family=Cascadia+Code:wght@400;600;700&family=Hanken+Grotesk:wght@400;600;700;800&display=swap";
|
|
62
|
+
export interface FontStacks {
|
|
63
|
+
display: string;
|
|
64
|
+
sans: string;
|
|
65
|
+
mono: string;
|
|
66
|
+
}
|
|
67
|
+
/** Literal family stacks for the email body, web font first. */
|
|
68
|
+
export declare const EMAIL_FONTS: FontStacks;
|
|
69
|
+
/**
|
|
70
|
+
* The page-side stacks: next/font renames every family it self-hosts, so a
|
|
71
|
+
* literal 'Alan Sans' would never match on the site. The CSS variables the
|
|
72
|
+
* root layout sets are the only spelling both dev and prod agree on.
|
|
73
|
+
*/
|
|
74
|
+
export declare const CSS_VARIABLE_FONTS: FontStacks;
|
|
75
|
+
/**
|
|
76
|
+
* A flat blend of `fg` into `bg` at opacity `t` — what the site's translucent
|
|
77
|
+
* chip classes compute at composite time, precomputed here because alpha
|
|
78
|
+
* channels do not survive every mail client.
|
|
79
|
+
*/
|
|
80
|
+
export declare function mix(fg: string, bg: string, t: number): string;
|
|
81
|
+
/**
|
|
82
|
+
* The three flattened colors of one event chip, shared between the `Chip`
|
|
83
|
+
* component and the dark-mode pinning stylesheet so the pinned values can
|
|
84
|
+
* never drift from the rendered ones.
|
|
85
|
+
*/
|
|
86
|
+
export declare function chipColors(color: string, ground: string): {
|
|
87
|
+
border: string;
|
|
88
|
+
fill: string;
|
|
89
|
+
text: string;
|
|
90
|
+
};
|
|
91
|
+
/** The two lightened accent tints the quiet bottom cards title with. */
|
|
92
|
+
export declare const HEADING_TINTS: {
|
|
93
|
+
readonly newHere: string;
|
|
94
|
+
readonly signoff: string;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Longhand font styling. The shorthand `font: 700 13px/1 …` reads better in a
|
|
98
|
+
* template string but classic Outlook applies it unreliably, so every text
|
|
99
|
+
* style is spelled out property by property.
|
|
100
|
+
*/
|
|
101
|
+
export declare function font(weight: number, size: number, lineHeight: number | string, family: string): CSSProperties;
|
|
102
|
+
/**
|
|
103
|
+
* A solid background painted twice: as `background-color` for the clients
|
|
104
|
+
* that only read that, and again as a same-color `linear-gradient` image
|
|
105
|
+
* layer, as Gmail dark-mode armor (Gmail rewrites background colors but
|
|
106
|
+
* leaves background images alone). Classic Outlook ignores background images
|
|
107
|
+
* and falls back to the flat color.
|
|
108
|
+
*
|
|
109
|
+
* These background styles ride in `paintCss()` rules, NEVER inline: the web
|
|
110
|
+
* Outlooks' dark mode rewrites every inline background in place with inline
|
|
111
|
+
* `!important` (which no stylesheet rule can outrank) and strips inline
|
|
112
|
+
* background images, but leaves the embedded stylesheet's colors untouched —
|
|
113
|
+
* proven on a received copy, 2026-09-11. The one deliberate exception is
|
|
114
|
+
* `<body>` in `ChangelogDocument`, whose inline background exists to be
|
|
115
|
+
* repainted (see the comment there).
|
|
116
|
+
*/
|
|
117
|
+
export declare function solidBg(color: string): CSSProperties;
|
|
118
|
+
/** The site's `.slants` stripe: white 45° slants over a color. The solid `backgroundColor` comes first so clients that drop gradients degrade flat; the closing `linear-gradient` layer is the same Gmail armor as `solidBg` — and like `solidBg`, this ships only through `paintCss()`. */
|
|
119
|
+
export declare function slants(color: string): CSSProperties;
|
|
120
|
+
/** `bg-dot-grid` — a radial dot texture degrading to the flat color underneath, with the same-color gradient underlay as Gmail armor (see `solidBg`; stylesheet-only, like every background). */
|
|
121
|
+
export declare function dotGrid(bg: string, dot: string): CSSProperties;
|
|
122
|
+
/** A colored block shadow edged in black — the site's `shadow-block-outlined-*`. */
|
|
123
|
+
export declare function blockShadow(color: string, size?: number): CSSProperties;
|
package/dist/theme.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { EVENT_KIND_VISUALS, EVENT_SEGMENT_VISUALS, } from "@devdogsuga/brand/event";
|
|
2
|
+
/** The club accent — UGA arch black on red. */
|
|
3
|
+
export const UGA = "#ba0c2f";
|
|
4
|
+
/**
|
|
5
|
+
* Event-type colors, taken from the calendar's own vocabulary in
|
|
6
|
+
* `@devdogsuga/brand/event` rather than restated: the chips here and the
|
|
7
|
+
* chips on the site's schedule are the same promise about the same event
|
|
8
|
+
* types.
|
|
9
|
+
*/
|
|
10
|
+
export const KIND = {
|
|
11
|
+
interest: EVENT_KIND_VISUALS["Interest Meeting"].accent,
|
|
12
|
+
build: EVENT_KIND_VISUALS["Dev Session"].accent,
|
|
13
|
+
study: EVENT_KIND_VISUALS["Study Session"].accent,
|
|
14
|
+
social: EVENT_KIND_VISUALS.Social.accent,
|
|
15
|
+
workshop: EVENT_SEGMENT_VISUALS.workshop.accent,
|
|
16
|
+
};
|
|
17
|
+
/** The mauve surfaces, matching the platform (mauve-950 canvas → -300 text). */
|
|
18
|
+
export const PALETTE = {
|
|
19
|
+
bg: "#13121b",
|
|
20
|
+
bar: "#0e0d15",
|
|
21
|
+
card: "#201e2b",
|
|
22
|
+
card2: "#26232f",
|
|
23
|
+
border: "#332f40",
|
|
24
|
+
ink: "#f3f1f6",
|
|
25
|
+
mute: "#a49eb1",
|
|
26
|
+
dim: "#6f6a80",
|
|
27
|
+
};
|
|
28
|
+
export const SITE = "https://devdogsuga.org";
|
|
29
|
+
export const SOCIAL_LINKS = [
|
|
30
|
+
{ label: "discord", icon: "discord", url: "https://discord.gg/devdogs" },
|
|
31
|
+
{
|
|
32
|
+
label: "instagram",
|
|
33
|
+
icon: "instagram",
|
|
34
|
+
url: "https://instagram.com/devdogsuga",
|
|
35
|
+
},
|
|
36
|
+
{ label: "github", icon: "github", url: "https://github.com/devdogsuga" },
|
|
37
|
+
{
|
|
38
|
+
label: "web",
|
|
39
|
+
icon: "globe",
|
|
40
|
+
// The link-in-bio entry point: ?utm_content=linkinbio opens the site's
|
|
41
|
+
// app switcher (see the platform's AppSwitcher/AutoOpen), landing the
|
|
42
|
+
// reader on the same linktree experience as QR codes and social bios.
|
|
43
|
+
url: `${SITE}/?utm_content=linkinbio`,
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
/**
|
|
47
|
+
* The site's three typefaces on Google Fonts — the same trio `layout.tsx`
|
|
48
|
+
* loads through next/font. Apple Mail, iOS Mail and most webview clients honor
|
|
49
|
+
* the `<link>`/`@import`; Gmail and Outlook ignore them and fall down the
|
|
50
|
+
* stacks in `EMAIL_FONTS`.
|
|
51
|
+
*/
|
|
52
|
+
export const FONTS_HREF = "https://fonts.googleapis.com/css2?family=Alan+Sans:wght@400..900&family=Cascadia+Code:wght@400;600;700&family=Hanken+Grotesk:wght@400;600;700;800&display=swap";
|
|
53
|
+
/** Literal family stacks for the email body, web font first. */
|
|
54
|
+
export const EMAIL_FONTS = {
|
|
55
|
+
display: "'Alan Sans', 'Hanken Grotesk', -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
|
56
|
+
sans: "'Hanken Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
|
57
|
+
mono: "'Cascadia Code', 'SFMono-Regular', ui-monospace, Menlo, Consolas, monospace",
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* The page-side stacks: next/font renames every family it self-hosts, so a
|
|
61
|
+
* literal 'Alan Sans' would never match on the site. The CSS variables the
|
|
62
|
+
* root layout sets are the only spelling both dev and prod agree on.
|
|
63
|
+
*/
|
|
64
|
+
export const CSS_VARIABLE_FONTS = {
|
|
65
|
+
display: "var(--font-display, sans-serif)",
|
|
66
|
+
sans: "var(--font-sans, sans-serif)",
|
|
67
|
+
mono: "var(--font-mono, monospace)",
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* A flat blend of `fg` into `bg` at opacity `t` — what the site's translucent
|
|
71
|
+
* chip classes compute at composite time, precomputed here because alpha
|
|
72
|
+
* channels do not survive every mail client.
|
|
73
|
+
*/
|
|
74
|
+
export function mix(fg, bg, t) {
|
|
75
|
+
const channels = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
|
76
|
+
const [a, b] = [channels(fg), channels(bg)];
|
|
77
|
+
return `#${a
|
|
78
|
+
.map((v, i) => Math.round(v * t + (b[i] ?? 0) * (1 - t))
|
|
79
|
+
.toString(16)
|
|
80
|
+
.padStart(2, "0"))
|
|
81
|
+
.join("")}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The three flattened colors of one event chip, shared between the `Chip`
|
|
85
|
+
* component and the dark-mode pinning stylesheet so the pinned values can
|
|
86
|
+
* never drift from the rendered ones.
|
|
87
|
+
*/
|
|
88
|
+
export function chipColors(color, ground) {
|
|
89
|
+
return {
|
|
90
|
+
border: mix(color, ground, 0.35),
|
|
91
|
+
fill: mix(color, ground, 0.12),
|
|
92
|
+
text: mix(color, "#ffffff", 0.62),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** The two lightened accent tints the quiet bottom cards title with. */
|
|
96
|
+
export const HEADING_TINTS = {
|
|
97
|
+
newHere: mix(KIND.study, "#ffffff", 0.62),
|
|
98
|
+
signoff: mix(UGA, "#ffffff", 0.45),
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Longhand font styling. The shorthand `font: 700 13px/1 …` reads better in a
|
|
102
|
+
* template string but classic Outlook applies it unreliably, so every text
|
|
103
|
+
* style is spelled out property by property.
|
|
104
|
+
*/
|
|
105
|
+
export function font(weight, size, lineHeight, family) {
|
|
106
|
+
return {
|
|
107
|
+
fontWeight: weight,
|
|
108
|
+
fontSize: `${size}px`,
|
|
109
|
+
lineHeight,
|
|
110
|
+
fontFamily: family,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* A solid background painted twice: as `background-color` for the clients
|
|
115
|
+
* that only read that, and again as a same-color `linear-gradient` image
|
|
116
|
+
* layer, as Gmail dark-mode armor (Gmail rewrites background colors but
|
|
117
|
+
* leaves background images alone). Classic Outlook ignores background images
|
|
118
|
+
* and falls back to the flat color.
|
|
119
|
+
*
|
|
120
|
+
* These background styles ride in `paintCss()` rules, NEVER inline: the web
|
|
121
|
+
* Outlooks' dark mode rewrites every inline background in place with inline
|
|
122
|
+
* `!important` (which no stylesheet rule can outrank) and strips inline
|
|
123
|
+
* background images, but leaves the embedded stylesheet's colors untouched —
|
|
124
|
+
* proven on a received copy, 2026-09-11. The one deliberate exception is
|
|
125
|
+
* `<body>` in `ChangelogDocument`, whose inline background exists to be
|
|
126
|
+
* repainted (see the comment there).
|
|
127
|
+
*/
|
|
128
|
+
export function solidBg(color) {
|
|
129
|
+
return {
|
|
130
|
+
backgroundColor: color,
|
|
131
|
+
backgroundImage: `linear-gradient(${color},${color})`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** The site's `.slants` stripe: white 45° slants over a color. The solid `backgroundColor` comes first so clients that drop gradients degrade flat; the closing `linear-gradient` layer is the same Gmail armor as `solidBg` — and like `solidBg`, this ships only through `paintCss()`. */
|
|
135
|
+
export function slants(color) {
|
|
136
|
+
return {
|
|
137
|
+
backgroundColor: color,
|
|
138
|
+
backgroundImage: `repeating-linear-gradient(45deg,rgba(255,255,255,.24) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.24) 50%,rgba(255,255,255,.24) 75%,transparent 75%),linear-gradient(${color},${color})`,
|
|
139
|
+
backgroundSize: "16px 16px,auto",
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** `bg-dot-grid` — a radial dot texture degrading to the flat color underneath, with the same-color gradient underlay as Gmail armor (see `solidBg`; stylesheet-only, like every background). */
|
|
143
|
+
export function dotGrid(bg, dot) {
|
|
144
|
+
return {
|
|
145
|
+
backgroundColor: bg,
|
|
146
|
+
backgroundImage: `radial-gradient(${dot} 1px,transparent 1px),linear-gradient(${bg},${bg})`,
|
|
147
|
+
backgroundSize: "15px 15px,auto",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** A colored block shadow edged in black — the site's `shadow-block-outlined-*`. */
|
|
151
|
+
export function blockShadow(color, size = 6) {
|
|
152
|
+
return {
|
|
153
|
+
boxShadow: `${size + 1}px ${size + 1}px 0 0 #000, ${size}px ${size}px 0 0 ${color}`,
|
|
154
|
+
};
|
|
155
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devdogsuga/newsletter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "The DevDogs Changelog: email-safe React components rendered as platform pages and exported as Outlook-importable .eml files",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/DevDogsUGA/Backstage",
|
|
11
|
+
"directory": "packages/newsletter"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public",
|
|
15
|
+
"provenance": true
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./export": {
|
|
23
|
+
"types": "./dist/export/index.d.ts",
|
|
24
|
+
"default": "./dist/export/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"react": "^19.2.8",
|
|
32
|
+
"react-dom": "^19.2.8",
|
|
33
|
+
"@devdogsuga/brand": "0.1.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^26.2.0",
|
|
37
|
+
"@types/react": "^19.2.18",
|
|
38
|
+
"@types/react-dom": "^19.2.4",
|
|
39
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
40
|
+
"typescript": "^6.0.3",
|
|
41
|
+
"vitest": "^4.1.11",
|
|
42
|
+
"@devdogsuga/config": "0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc",
|
|
46
|
+
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"test:coverage": "vitest run --coverage"
|
|
50
|
+
}
|
|
51
|
+
}
|