Learning jam scaffolding

This commit is contained in:
Asaf Gartner 2024-02-08 22:21:01 +02:00
parent d896298117
commit b5d4fe9ba2
14 changed files with 572 additions and 6 deletions

View File

@ -62,6 +62,17 @@ var WRJ2023 = Jam{
UrlSlug: "2023",
}
var LJ2024 = Jam{
Event: Event{
StartTime: time.Date(2024, 3, 15, 17, 0, 0, 0, time.UTC),
EndTime: time.Date(2024, 3, 24, 20, 0, 0, 0, time.UTC),
},
Name: "Learning Jam 2024",
Slug: "LJ2024",
UrlSlug: "learning-2024",
}
// Conferences
var HMS2022 = Event{
StartTime: time.Date(2022, 11, 16, 0, 0, 0, 0, utils.Must1(time.LoadLocation("America/Los_Angeles"))),
EndTime: time.Date(2022, 11, 18, 0, 0, 0, 0, utils.Must1(time.LoadLocation("America/Los_Angeles"))),
@ -77,7 +88,7 @@ var HMBoston2023 = Event{
EndTime: time.Date(2023, 8, 4, 0, 0, 0, 0, utils.Must1(time.LoadLocation("America/Los_Angeles"))),
}
var AllJams = []Jam{WRJ2021, WRJ2022, VJ2023, WRJ2023}
var AllJams = []Jam{WRJ2021, WRJ2022, VJ2023, WRJ2023, LJ2024}
func CurrentJam() *Jam {
now := time.Now()

View File

@ -143,7 +143,6 @@ func TestFeed(t *testing.T) {
func TestProjectIndex(t *testing.T) {
AssertRegexMatch(t, BuildProjectIndex(1, ""), RegexProjectIndex, nil)
AssertRegexMatch(t, BuildProjectIndex(2, ""), RegexProjectIndex, map[string]string{"page": "2"})
AssertRegexMatch(t, BuildProjectIndex(1, "test"), RegexProjectIndex, map[string]string{"category": "test"})
AssertRegexMatch(t, BuildProjectIndex(2, "test"), RegexProjectIndex, map[string]string{"page": "2", "category": "test"})
assert.Panics(t, func() { BuildProjectIndex(0, "") })
@ -427,6 +426,16 @@ func TestJamFeed2023(t *testing.T) {
AssertSubdomain(t, BuildJamFeed2023(), "")
}
func TestJamIndex2024_Learning(t *testing.T) {
AssertRegexMatch(t, BuildJamIndex2024_Learning(), RegexJamIndex2024_Learning, nil)
AssertSubdomain(t, BuildJamIndex2024_Learning(), "")
}
func TestJamFeed2024_Learning(t *testing.T) {
AssertRegexMatch(t, BuildJamFeed2024_Learning(), RegexJamFeed2024_Learning, nil)
AssertSubdomain(t, BuildJamFeed2024_Learning(), "")
}
func TestTimeMachine(t *testing.T) {
AssertRegexMatch(t, BuildTimeMachine(), RegexTimeMachine, nil)
AssertSubdomain(t, BuildTimeMachine(), "")

View File

@ -126,6 +126,20 @@ func BuildJamRecap2023_Visibility() string {
return Url("/jam/visibility-2023/recap", nil)
}
var RegexJamIndex2024_Learning = regexp.MustCompile("^/jam/learning-2024$")
func BuildJamIndex2024_Learning() string {
defer CatchPanic()
return Url("/jam/learning-2024", nil)
}
var RegexJamFeed2024_Learning = regexp.MustCompile("^/jam/learning-2024/feed$")
func BuildJamFeed2024_Learning() string {
defer CatchPanic()
return Url("/jam/learning-2024/feed", nil)
}
func BuildJamIndexAny(slug string) string {
defer CatchPanic()
return Url(fmt.Sprintf("/jam/%s", slug), nil)

View File

@ -91,7 +91,7 @@
<a href="{{ .Header.FishbowlUrl }}">Fishbowls</a>
<a href="{{ .Header.PodcastUrl }}">Podcast</a>
<a href="https://guide.handmade-seattle.com/s" target="_blank">Handmade Dev Show</a>
<a href="{{ .Header.CalendarUrl }}">Calendar</a>
{{/*<a href="{{ .Header.CalendarUrl }}">Calendar</a>*/}}
</div>
</div>
<div class="root-item">

View File

@ -0,0 +1,74 @@
{{ template "jam_2024_learning_base.html" . }}
{{ define "content-top" }}
<div class="flex flex-column items-center pa3 g3">
<img class="mw5" src="{{ static "visjam2023/logo.svg" }}">
<div class="f1 fw7">The Learning Jam</div>
<div class="flex flex-row g5 lh-solid">
<div>
<div class="fw7 f3">Learn</div>
<div class="fw3 f4">March 15-17, 2024</div>
</div>
<div>
<div class="fw7 f3">Teach</div>
<div class="fw3 f4">March 22-24, 2024</div>
</div>
</div>
<div class="mt3">
<a href="{{ .DiscordInviteUrl }}" class="db ph2 pv1 button-simple">Join the Discord</a>
</div>
</div>
{{ end }}
{{ define "content" }}
<div class="flex flex-column g3 items-center pa3">
<div class="w6">
<h2 class="c--theme-gradient-light">Recent activity</h2>
<div class="flex flex-column g3">
{{ if .TimelineItems }}
{{ range .TimelineItems }}
<div class="flex flex-column g2 bg--rich-gray pa2">
<div class="flex flex-row g2">
{{ if .OwnerAvatarUrl }}
<a class="flex flex-shrink-0 br-100 square items-center justify-center overflow-hidden bg--gray" href="{{ .OwnerUrl }}">
<img class="w2" src="{{ .OwnerAvatarUrl }}" />
</a>
{{ end }}
<div class="flex flex-column">
<div class="fw6">{{ .OwnerName }}</div>
<div class="f7">{{ timehtml (relativedate .Date) .Date }}</div>
</div>
</div>
{{ if .Description }}
<div>{{ trim .Description }}</div>
{{ end }}
{{ range .EmbedMedia }}
<div class="flex flex-column {{ if eq .Type mediaembed }}wide-screen{{ end }} justify-stretch iframe-fill">
{{ if eq .Type mediaimage }}
<img src="{{ .AssetUrl }}">
{{ else if eq .Type mediavideo }}
{{ if .ThumbnailUrl }}
<video src="{{ .AssetUrl }}" poster="{{ .ThumbnailUrl }}" preload="none" controls>
{{ else }}
<video src="{{ .AssetUrl }}" preload="metadata" controls>
{{ end }}
{{ else if eq .Type mediaaudio }}
<audio src="{{ .AssetUrl }}" controls>
{{ else if eq .Type mediaembed }}
{{ .EmbedHTML }}
{{ else }}
<div class="">
<a href="{{ .AssetUrl }}" target="_blank">{{ .Filename }} ({{ filesize .FileSize }})</a>
</div>
{{ end }}
</div>
{{ end }}
</div>
{{ end }}
{{ else }}
Be the first!
{{ end }}
</div>
</div>
</div>
{{ end }}

View File

@ -0,0 +1,93 @@
{{ template "jam_2024_learning_base.html" . }}
{{ define "content-top" }}
<div class="flex flex-column items-center pa3 g3">
<img class="mw5" src="{{ static "visjam2023/logo.svg" }}">
<div class="f1 fw7">The Learning Jam</div>
<div class="flex flex-row g5 lh-solid">
<div>
<div class="fw7 f3">Learn</div>
<div class="fw3 f4">March 15-17, 2024</div>
</div>
<div>
<div class="fw7 f3">Teach</div>
<div class="fw3 f4">March 22-24, 2024</div>
</div>
</div>
{{ if lt .DaysUntilStart 0 }}
<div class="flex flex-row g3 mt2">
{{ if .SubmittedProjectUrl }}
<a class="db ph3 pv2 f4 button-simple" href="{{ .SubmittedProjectUrl }}">Share your progress</a>
{{ else }}
<a class="db ph3 pv2 f4 button-simple" href="{{ .ProjectSubmissionUrl }}">Create your project</a>
{{ end }}
<a class="db ph3 pv2 f4 button-simple" href="{{ .JamFeedUrl }}">Recent activity</a>
</div>
{{ end }}
<div class="mt2">
<a href="{{ .DiscordInviteUrl }}" class="db ph2 pv1 button-simple">Join the Discord</a>
</div>
</div>
{{ end }}
{{ define "content" }}
<style>
.participate-icon {
--mask-url: url("{{ static "visjam2023/logo.svg" }}");
}
</style>
<div class="flex flex-column g3 items-center pa3">
<div class="w6">
<h2 class="c--theme-gradient-light">How to participate</h2>
<div class="flex flex-column g2 pa3">
<div class="flex flex-row g3 items-center bg--rich-gray pa2">
<div class="svg-mask participate-icon bg--theme-gradient-light">
<img class="w4 invisible" src="{{ static "visjam2023/logo.svg" }}" />
</div>
<div class="flex flex-column g2">
<div class="f4 fw7">Pick a project and form a team</div>
<div>Find a project idea that excites you etc...</div>
</div>
</div>
<div class="flex flex-row g3 items-center bg--rich-gray pa2">
<div class="svg-mask participate-icon bg--theme-gradient-light">
<img class="w4 invisible" src="{{ static "visjam2023/logo.svg" }}" />
</div>
<div class="flex flex-column g2">
<div class="f4 fw7">Pick a project and form a team</div>
<div>Find a project idea that excites you etc...</div>
</div>
</div>
<div class="flex flex-row g3 items-center bg--rich-gray pa2">
<div class="svg-mask participate-icon bg--theme-gradient-light">
<img class="w4 invisible" src="{{ static "visjam2023/logo.svg" }}" />
</div>
<div class="flex flex-column g2">
<div class="f4 fw7">Pick a project and form a team</div>
<div>Find a project idea that excites you etc...</div>
</div>
</div>
</div>
</div>
<div class="w7 center bb b--rich-gray"></div>
<div class="w6">
<h2 class="c--theme-gradient-light">March 15 - 17, 2024</h2>
<div>
Do things
</div>
</div>
<div class="w7 center bb b--rich-gray"></div>
<div class="w6">
<h2 class="c--theme-gradient-light">March 22 - 24, 2024</h2>
<div>
Do more things
</div>
</div>
<div class="w7 center bb b--rich-gray"></div>
<div class="w6">
<h2 class="c--theme-gradient-light">Rules</h2>
<div>
Do more things
</div>
</div>
</div>
{{ end }}

View File

@ -16,6 +16,11 @@
<p>Since 2020, we have been running programming jams to encourage community members to explore new ideas and start projects. You can view all the past submissions and results here.</p>
<div class="jam-grid g3">
<a href="{{ .LJ2024Url }}">
<div class="br2 overflow-hidden flex">
<img src="{{ static "learningjam2024/TwitterCard.png" }}">
</div>
</a>
<a href="{{ .WRJ2023Url }}">
<div class="br2 overflow-hidden flex">
<img src="{{ static "wheeljam2023/TwitterCard.png" }}">

View File

@ -111,6 +111,74 @@
</div>
*/}}
<div class="mb3 ph3 ph0-ns">
<style>
#jam-banner {
background: linear-gradient(to bottom right, #003c83, #019AD2);
color: white !important;
text-align: center;
}
#jam-banner h3 {
font-family: 'MohaveHMN', sans-serif;
font-weight: normal;
font-size: 2.2rem;
line-height: 0.8;
margin: 0;
text-transform: uppercase;
}
#jam-details {
font-family: 'MohaveHMN', sans-serif;
font-variant: small-caps;
font-size: 1.2rem;
line-height: 0.8;
margin-top: 0.6rem;
letter-spacing: 0.02rem;
}
#jam-learn-more {
font-size: 1rem;
}
@media screen and (min-width: 30rem) {
#jam-banner {
text-align: left;
}
#jam-title-container {
padding-top: 0.2rem;
}
}
</style>
<a id="jam-banner" class="pv3 ph3 ph4-l br3 flex flex-column flex-row-ns items-center" href="{{ .JamUrl }}">
<img class="h3" src="{{ static "wheeljam2023/logo.svg" }}">
<div id="jam-title-container" class="flex flex-column pl3-m pl4-l pv3 pv0-ns">
<h3 id="jam-title">The Learning Jam</h3>
<div id="jam-details">
March 15 - March 24.
{{ if gt .JamDaysUntilEnd 0 }}
{{ if eq .JamDaysUntilStart 0 }}
<b>Happening now.</b>
{{ else if eq .JamDaysUntilStart 1 }}
<b>Starting tomorrow.</b>
{{ else }}
<b>In {{ .JamDaysUntilStart }} days.</b>
{{ end }}
{{ else }}
<b>See the results.</b>
{{ end }}
</div>
</div>
<div class="flex-grow-1"></div>
<div id="jam-learn-more">
Learn more
<div class="dib svgicon">{{ svg "chevron-right" }}</div>
</div>
</a>
</div>
{{/*
<div class="mb3 ph3 ph0-ns">
<style>
#jam-banner {
@ -177,6 +245,7 @@
</div>
</a>
</div>
*/}}
{{/*
<div class="mb3 ph3 ph0-ns">

View File

@ -0,0 +1,152 @@
{{/*
This is a copy-paste from base.html because we want to preserve the unique
style of this page no matter what future changes we make to the base.
*/}}
<!DOCTYPE html{{ if .OpenGraphItems }} prefix="og: http://ogp.me/ns#"{{ end }}>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" sizes="16x16" href="{{ static "learningjam2024/favicon-16x16.png" }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ static "learningjam2024/favicon-32x32.png" }}">
{{ if .CanonicalLink }}<link rel="canonical" href="{{ .CanonicalLink }}">{{ end }}
{{ range .OpenGraphItems }}
{{ if .Property }}
<meta property="{{ .Property }}" content="{{ .Value }}" />
{{ else }}
<meta name="{{ .Name }}" content="{{ .Value }}" />
{{ end }}
{{ end }}
{{ if .Title }}
<title>{{ .Title }} | Handmade Network</title>
{{ else }}
<title>Handmade Network</title>
{{ end }}
<meta name="theme-color" content="#003C83">
<script src="{{ static "js/templates.js" }}"></script>
<link rel="stylesheet" href="{{ static "fonts/mohave/stylesheet.css" }}">
<link href='https://fonts.googleapis.com/css?family=Fira+Sans:300,400,500,600' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Fira+Mono:300,400,500,700' rel='stylesheet' type='text/css'>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="{{ static "style.css" }}">
<style>
:root {
--theme-gradient-dark: linear-gradient(to bottom right, #003c83, #019AD2);
--theme-gradient-light: linear-gradient(to bottom right, #8BD5FF, #2F69FF);
--white: #fff;
--bg-button: #082F5766;
--bg-button-hover: #082F5733;
--charcoal: #2F2F2F;
--gray: #CBCBCB;
--rich-gray: #494949;
}
body {
font-family: "Inter", "Fira Sans", sans-serif;
}
.bg--theme-gradient-dark {
background: var(--theme-gradient-dark);
}
.bg--theme-gradient-light {
background: var(--theme-gradient-light);
}
.bg--charcoal {
background: var(--charcoal);
}
.bg--rich-gray {
background: var(--rich-gray);
}
.bg--gray {
background: var(--gray);
}
.b--rich-gray {
border-color: var(--rich-gray);
}
.c--theme-gradient-dark {
background: var(--theme-gradient-dark);
background-clip: text;
color: transparent;
}
.c--theme-gradient-light {
background: var(--theme-gradient-light);
background-clip: text;
color: transparent;
}
.button-simple {
background: var(--bg-button);
border: 1px solid var(--white);
}
.button-simple:hover {
background: var(--bg-button-hover);
}
.c-white {
color: var(--white);
}
.invisible {
visibility: hidden;
}
.svg-mask {
mask: var(--mask-url);
}
.square {
aspect-ratio: 1 / 1;
}
.wide-screen {
aspect-ratio: 16 / 9;
}
.iframe-fill iframe {
flex-grow: 1;
}
</style>
</head>
<body class="flex flex-column">
<div class="c-white bg--theme-gradient-dark">
<div class="bb b--white pa1 mb3 flex flex-row g2 items-center">
<a href="{{ .Header.HMNHomepageUrl }}" class="f3">HANDMADE</a>
<div class="flex-grow-1 flex-shrink-1"></div>
</div>
{{ block "content-top" . }}{{ end }}
</div>
<div class="bg--charcoal c-white flex-grow-1">
{{ block "content" . }}{{ end }}
</div>
<script>
const dtf = new Intl.DateTimeFormat([], {
dateStyle: "full",
timeStyle: "short",
});
for (const time of document.querySelectorAll('time')) {
const d = new Date(Date.parse(time.dateTime));
if (time.getAttribute("data-type") == "content") {
time.textContent = dtf.format(d);
} else {
time.title = dtf.format(d);
}
}
</script>
</body>
</html>

View File

@ -20,6 +20,7 @@ type BaseData struct {
CurrentProjectUrl string
LoginPageUrl string
ProjectCSSUrl string
DiscordInviteUrl string
Project Project
User *User

View File

@ -51,6 +51,7 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc
CurrentProjectUrl: c.UrlContext.BuildHomepage(),
LoginPageUrl: hmnurl.BuildLoginPage(c.FullUrl()),
ProjectCSSUrl: hmnurl.BuildProjectCSS(project.Color1),
DiscordInviteUrl: "https://discord.gg/hmn",
Project: templates.ProjectToTemplate(&project, c.UrlContext.BuildHomepage()),
User: templateUser,

View File

@ -27,6 +27,7 @@ func JamsIndex(c *RequestContext) ResponseData {
WRJ2022Url string
VJ2023Url string
WRJ2023Url string
LJ2024Url string
}
res.MustWriteTemplate("jams_index.html", TemplateData{
@ -37,6 +38,140 @@ func JamsIndex(c *RequestContext) ResponseData {
WRJ2022Url: hmnurl.BuildJamIndex2022(),
VJ2023Url: hmnurl.BuildJamIndex2023_Visibility(),
WRJ2023Url: hmnurl.BuildJamIndex2023(),
LJ2024Url: hmnurl.BuildJamIndex2024_Learning(),
}, c.Perf)
return res
}
func JamIndex2024_Learning(c *RequestContext) ResponseData {
var res ResponseData
daysUntilStart := daysUntil(hmndata.LJ2024.StartTime)
daysUntilEnd := daysUntil(hmndata.LJ2024.EndTime)
baseData := getBaseDataAutocrumb(c, hmndata.LJ2024.Name)
baseData.OpenGraphItems = []templates.OpenGraphItem{
{Property: "og:site_name", Value: "Handmade Network"},
{Property: "og:type", Value: "website"},
{Property: "og:image", Value: hmnurl.BuildPublic("learningjam2024/opengraph.png", true)},
{Property: "og:description", Value: "Need desc"},
{Property: "og:url", Value: hmnurl.BuildJamIndex2024_Learning()},
}
type JamPageData struct {
templates.BaseData
DaysUntilStart, DaysUntilEnd int
TwitchEmbedUrl string
ProjectSubmissionUrl string
SubmittedProjectUrl string
JamFeedUrl string
}
twitchEmbedUrl := ""
twitchStatus, err := db.QueryOne[models.TwitchLatestStatus](c, c.Conn,
`
SELECT $columns
FROM twitch_latest_status
WHERE twitch_login = $1
`,
"handmadenetwork",
)
if err == nil {
if twitchStatus.Live {
hmnUrl, err := url.Parse(config.Config.BaseUrl)
if err == nil {
twitchEmbedUrl = fmt.Sprintf("https://player.twitch.tv/?channel=%s&parent=%s", twitchStatus.TwitchLogin, hmnUrl.Hostname())
}
}
}
submittedProjectUrl := ""
if c.CurrentUser != nil {
projects, err := hmndata.FetchProjects(c, c.Conn, c.CurrentUser, hmndata.ProjectsQuery{
OwnerIDs: []int{c.CurrentUser.ID},
JamSlugs: []string{hmndata.WRJ2023.Slug},
Limit: 1,
})
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch jam projects for current user"))
}
if len(projects) > 0 {
urlContext := hmndata.UrlContextForProject(&projects[0].Project)
submittedProjectUrl = urlContext.BuildHomepage()
}
}
res.MustWriteTemplate("jam_2024_lj_index.html", JamPageData{
BaseData: baseData,
DaysUntilStart: daysUntilStart,
DaysUntilEnd: daysUntilEnd,
ProjectSubmissionUrl: hmnurl.BuildProjectNewJam(),
SubmittedProjectUrl: submittedProjectUrl,
JamFeedUrl: hmnurl.BuildJamFeed2024_Learning(),
TwitchEmbedUrl: twitchEmbedUrl,
}, c.Perf)
return res
}
func JamFeed2024_Learning(c *RequestContext) ResponseData {
jamProjects, err := hmndata.FetchProjects(c, c.Conn, c.CurrentUser, hmndata.ProjectsQuery{
JamSlugs: []string{hmndata.LJ2024.Slug},
})
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch jam projects for current user"))
}
projectIds := make([]int, 0, len(jamProjects))
for _, jp := range jamProjects {
projectIds = append(projectIds, jp.Project.ID)
}
var timelineItems []templates.TimelineItem
if len(projectIds) > 0 {
snippets, err := hmndata.FetchSnippets(c, c.Conn, c.CurrentUser, hmndata.SnippetQuery{
ProjectIDs: projectIds,
})
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch snippets for jam showcase"))
}
timelineItems = make([]templates.TimelineItem, 0, len(snippets))
for _, s := range snippets {
timelineItem := SnippetToTimelineItem(&s.Snippet, s.Asset, s.DiscordMessage, s.Projects, s.Owner, c.Theme, false)
timelineItem.SmallInfo = true
timelineItems = append(timelineItems, timelineItem)
}
}
type JamFeedData struct {
templates.BaseData
DaysUntilStart, DaysUntilEnd int
TimelineItems []templates.TimelineItem
}
daysUntilStart := daysUntil(hmndata.LJ2024.StartTime)
daysUntilEnd := daysUntil(hmndata.LJ2024.EndTime)
baseData := getBaseDataAutocrumb(c, hmndata.LJ2024.Name)
baseData.OpenGraphItems = []templates.OpenGraphItem{
{Property: "og:site_name", Value: "Handmade Network"},
{Property: "og:type", Value: "website"},
{Property: "og:image", Value: hmnurl.BuildPublic("learningjam2024/opengraph.png", true)},
{Property: "og:description", Value: "Need desc"},
{Property: "og:url", Value: hmnurl.BuildJamFeed2024_Learning()},
}
var res ResponseData
res.MustWriteTemplate("jam_2024_lj_feed.html", JamFeedData{
BaseData: baseData,
DaysUntilStart: daysUntilStart,
DaysUntilEnd: daysUntilEnd,
TimelineItems: timelineItems,
// ProjectSubmissionUrl: hmnurl.BuildProjectNewJam(),
// SubmittedProjectUrl: submittedProjectUrl,
// JamProjects: pageProjects,
}, c.Perf)
return res
}

View File

@ -156,9 +156,9 @@ func Index(c *RequestContext) ResponseData {
AtomFeedUrl: hmnurl.BuildAtomFeed(),
MarkAllReadUrl: hmnurl.HMNProjectContext.BuildForumMarkRead(0),
JamUrl: hmnurl.BuildJamIndex2023(),
JamDaysUntilStart: daysUntil(hmndata.WRJ2023.StartTime),
JamDaysUntilEnd: daysUntil(hmndata.WRJ2023.EndTime),
JamUrl: hmnurl.BuildJamIndex2024_Learning(),
JamDaysUntilStart: daysUntil(hmndata.LJ2024.StartTime),
JamDaysUntilEnd: daysUntil(hmndata.LJ2024.EndTime),
HMSDaysUntilStart: daysUntil(hmndata.HMS2023.StartTime),
HMSDaysUntilEnd: daysUntil(hmndata.HMS2023.EndTime),

View File

@ -71,6 +71,8 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
hmnOnly.GET(hmnurl.RegexJamRecap2023_Visibility, JamRecap2023_Visibility)
hmnOnly.GET(hmnurl.RegexJamIndex2023, JamIndex2023)
hmnOnly.GET(hmnurl.RegexJamFeed2023, JamFeed2023)
hmnOnly.GET(hmnurl.RegexJamIndex2024_Learning, JamIndex2024_Learning)
hmnOnly.GET(hmnurl.RegexJamFeed2024_Learning, JamFeed2024_Learning)
hmnOnly.GET(hmnurl.RegexTimeMachine, TimeMachine)
hmnOnly.GET(hmnurl.RegexTimeMachineSubmissions, TimeMachineSubmissions)