Compare commits

...

3 Commits

Author SHA1 Message Date
will 3e258930a8 Populated eec landing page with actual content
deploy / deploy (push) Successful in 4s
2026-07-09 09:44:14 -04:00
will 0fded0b845 Removed pitch from eec layout 2026-07-09 09:43:36 -04:00
will ca5c8862fd 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>
2026-07-09 08:51:49 -04:00
4 changed files with 192 additions and 56 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;
}
@@ -1,11 +1,39 @@
--- ---
title: "Find Your Right Readers" title: "The Find Your Right Readers Roadmap"
description: "A free email course for fiction authors on finding the readers who were always meant to find their story." description: "How to find readers who actually need your stories? avoid these 5 mistakes that lead to 0 sales on launch day, writing for an audience of 1 (just your mom), and posting every day for a year with nothing to show for it (even if you have zero platform and hate marketing)"
eec_slug: "find-your-right-readers" eec_slug: "find-your-right-readers"
pitch: |
Most advice about "finding readers" is really just advice about marketing — # Optional italic line under the title, for handling an objection up front.
ads, algorithms, funnels. This is different. Over a few short emails, at hero_subhead: "How to find readers who actually need your stories? avoid these 5 mistakes that lead to 0 sales on launch day, writing for an audience of 1 (just your mom), and posting every day for a year with nothing to show for it (even if you have zero platform and hate marketing)"
whatever pace you want to take it, I'll walk through how to think about who
your right readers actually are, and how to build a real path to them # Optional line under the signup button.
instead of shouting into the void. # social_proof: "Join a few hundred authors who've already started."
# Optional label on every "Send me day one" button across the page.
cta_text: "Get me started!"
# Optional "who's teaching this and why" section. Both fields are independently
# optional — set either or both.
credibility_heading: "Written by Will Estes, who's..."
credibility_bullets:
- "Noticed that most advice on selling your book is marketing advice about hustling."
- "Believes that most writers want to think about stories, not their next ad campaign."
# Optional day-by-day curriculum preview. `label` is optional per item.
curriculum_heading: "Here's what's inside"
curriculum_intro: "Five short emails, sent at whatever pace you want."
curriculum_items:
- label: "Day 1."
text: "Mistake #1: Writing for everyone who reads books. (Why a smaller target means more hits.)"
- label: "Day 2."
text: "Mistake #2: Perfecting your book before you start building your audience"
- label: "Day 3."
text: "Mistake #3: Selling the plot instead of the feeling."
- label: "Day 4."
text: "Mistake #4: Looking for readers in the wrong places. (Finding the people who need your stories means not talking to the ones who don't.)"
- label: "Day 5."
text: "Mistake #5: Quitting before the compounding kicks in."
# Optional heading above the repeated signup form at the bottom of the page.
repeat_cta_heading: "Ready to find your right readers?"
--- ---
+38 -48
View File
@@ -1,52 +1,42 @@
{{ 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 }}
{{ 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>