Restructure course landing page into data-driven sections

Splits the single hardcoded-feeling course template into hero,
credibility, curriculum, and repeat-CTA sections, each rendering only
when its front matter fields are set. The enroll form (and its
EEC-trigger JS) moves into a reusable partial so it can appear more
than once on a page without id collisions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 08:51:49 -04:00
parent 6afa0cdf0d
commit ca5c8862fd
3 changed files with 157 additions and 48 deletions
+70
View File
@@ -233,3 +233,73 @@ input:focus, textarea:focus {
height: 1px; height: 1px;
overflow: hidden; overflow: hidden;
} }
/* Course landing pages (layouts/courses/single.html) */
.course-hero {
text-align: center;
}
.course-subhead {
font-style: italic;
color: var(--fg-muted);
margin-top: 0;
}
.course-value-prop {
font-size: 1.05rem;
margin: 1.5rem 0;
}
.course-social-proof {
font-style: italic;
font-size: 0.9rem;
color: var(--fg-muted);
}
.enroll-form {
max-width: 24rem;
margin: 1.5rem auto;
text-align: left;
}
.enroll-status {
text-align: center;
}
.course-credibility,
.course-curriculum {
background: var(--bg-raised);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 2rem;
margin: 3rem 0;
}
.course-bullets {
list-style: none;
padding: 0;
margin: 1.5rem 0 0;
}
.course-bullets li {
position: relative;
padding-left: 1.75rem;
margin-bottom: 0.75rem;
}
.course-bullets li::before {
content: "✓";
position: absolute;
left: 0;
color: var(--cta);
font-weight: 700;
}
.course-curriculum-list {
list-style: none;
padding: 0;
margin: 1.5rem 0 0;
}
.course-curriculum-list li {
margin-bottom: 1rem;
}
.course-day-label {
font-family: var(--font-heading);
color: var(--cta);
font-weight: 700;
}
.course-repeat-cta {
text-align: center;
margin-top: 3rem;
}
+39 -48
View File
@@ -1,52 +1,43 @@
{{ define "main" }} {{ define "main" }}
<h1 style="font-size:2rem; margin-bottom:0.5rem;">{{ .Title }}</h1> <section class="course-hero">
{{ with .Params.pitch }}<div style="font-size:1.05rem; opacity:0.9;">{{ . | markdownify }}</div>{{ end }} <h1>{{ .Title }}</h1>
{{ with .Params.hero_subhead }}<p class="course-subhead">{{ . }}</p>{{ end }}
{{ with .Params.pitch }}<div class="course-value-prop">{{ . | markdownify }}</div>{{ end }}
{{ partial "course-enroll-form.html" (dict "id" "top" "page" .) }}
{{ with .Params.social_proof }}<p class="course-social-proof">{{ . }}</p>{{ end }}
</section>
{{ if or .Params.credibility_heading .Params.credibility_bullets }}
<section class="course-credibility">
{{ with .Params.credibility_heading }}<h2>{{ . }}</h2>{{ end }}
{{ with .Params.credibility_bullets }}
<ul class="course-bullets">
{{ range . }}<li>{{ . | markdownify }}</li>{{ end }}
</ul>
{{ end }}
</section>
{{ end }}
{{ if or .Params.curriculum_heading .Params.curriculum_items }}
<section class="course-curriculum">
{{ with .Params.curriculum_heading }}<h2>{{ . }}</h2>{{ end }}
{{ with .Params.curriculum_intro }}<p>{{ . }}</p>{{ end }}
{{ with .Params.curriculum_items }}
<ol class="course-curriculum-list">
{{ range . }}
<li>{{ with .label }}<span class="course-day-label">{{ . }}</span> {{ end }}{{ .text | markdownify }}</li>
{{ end }}
</ol>
{{ end }}
</section>
{{ end }}
{{ .Content }} {{ .Content }}
<form id="enroll-form" style="margin-top:2rem; max-width:24rem;"> <section class="course-repeat-cta">
<label for="email">Email</label> {{ with .Params.repeat_cta_heading }}<h2>{{ . }}</h2>{{ end }}
<input type="email" id="email" name="email" required autocomplete="email" placeholder="you@example.com"> {{ partial "course-enroll-form.html" (dict "id" "bottom" "page" .) }}
</section>
<div class="field-honeypot" aria-hidden="true">
<label for="website">Leave this field empty</label>
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off">
</div>
<button type="submit" class="btn">Send me day one</button>
</form>
<p id="enroll-status" role="status" style="margin-top:1rem;"></p>
<script>
(function () {
var form = document.getElementById("enroll-form");
var status = document.getElementById("enroll-status");
var slug = {{ .Params.eec_slug }};
form.addEventListener("submit", function (e) {
e.preventDefault();
if (form.website.value) return; // honeypot
var submitBtn = form.querySelector("button[type=submit]");
submitBtn.disabled = true;
status.textContent = "Signing you up…";
fetch("{{ .Site.Params.eec_base_url }}/courses/" + slug + "/enroll", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: form.email.value })
}).then(function (res) {
if (res.ok) {
form.reset();
form.hidden = true;
status.textContent = "Check your inbox — let us know you're ready and we'll get started.";
return;
}
return res.text().then(function (msg) {
submitBtn.disabled = false;
status.textContent = msg || "Something went wrong. Try again in a moment.";
});
}).catch(function () {
submitBtn.disabled = false;
status.textContent = "Something went wrong. Try again in a moment.";
});
});
})();
</script>
{{ end }} {{ end }}
+48
View File
@@ -0,0 +1,48 @@
{{ $id := .id }}
{{ $page := .page }}
<form id="enroll-form-{{ $id }}" class="enroll-form">
<label for="email-{{ $id }}">Email</label>
<input type="email" id="email-{{ $id }}" name="email" required autocomplete="email" placeholder="you@example.com">
<div class="field-honeypot" aria-hidden="true">
<label for="website-{{ $id }}">Leave this field empty</label>
<input type="text" id="website-{{ $id }}" name="website" tabindex="-1" autocomplete="off">
</div>
<button type="submit" class="btn">{{ $page.Params.cta_text | default "Send me day one" }}</button>
</form>
<p id="enroll-status-{{ $id }}" role="status" class="enroll-status"></p>
<script>
(function () {
var form = document.getElementById("enroll-form-{{ $id }}");
var status = document.getElementById("enroll-status-{{ $id }}");
var slug = {{ $page.Params.eec_slug }};
form.addEventListener("submit", function (e) {
e.preventDefault();
if (form.website.value) return; // honeypot
var submitBtn = form.querySelector("button[type=submit]");
submitBtn.disabled = true;
status.textContent = "Signing you up…";
fetch("{{ $page.Site.Params.eec_base_url }}/courses/" + slug + "/enroll", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: form.email.value })
}).then(function (res) {
if (res.ok) {
form.reset();
form.hidden = true;
status.textContent = "Check your inbox — let us know you're ready and we'll get started.";
return;
}
return res.text().then(function (msg) {
submitBtn.disabled = false;
status.textContent = msg || "Something went wrong. Try again in a moment.";
});
}).catch(function () {
submitBtn.disabled = false;
status.textContent = "Something went wrong. Try again in a moment.";
});
});
})();
</script>