Skip to content
Merged
34 changes: 34 additions & 0 deletions .okf/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -2174,3 +2174,37 @@ cross-batch sweep to `content/voice-rules.md`. Also: critic-tech caught that
ruby_llm 1.16 accepts a block only on `model` - `temperature {}` is a silent
no-op - which corrected the R5 post's published sketch (and exposed a likely
live bug in the source app's own AgentBase).
## 2026-08-20 - Phase 0 swarm: instrumentation + record-baselines (PR #489, draft)

Two worktree-isolated agents built 2608 Phase 0 in parallel: GA4 conversion
instrumentation (generate_lead one-shot on data-lead-form submits, cta_click
with location=hero/section/blog-index/tag-index/article-end, scroll_depth
25/50/75/90 on blog posts - named to dodge enhanced measurement's built-in
90% scroll) and bin/record-baselines (keep-globs, restore-the-rest, --dry-run,
--linux prints the CI dispatch per the ARM-drift rule). Review chain earned
its keep: coordinator 4-eyes caught untracked-created baselines crashing the
restore under set -e; codex added two majors on top - a red test run skipped
the restore pass entirely, and non-z porcelain parsing broke on space paths
and whole-new directories. A wrapper whose one job is "leave the tree
reconciled" failed exactly that job in three ways before review.

Standing coordination note: master frozen for this session (Paul: another
agent owns it) - PR #489 is DRAFT until cleared. Known red handed to the
master owner: macos/desktop/homepage/_clients.png is stale on master itself
(Jul 21 record, card order changed since; fails on clean master).
Detail: PR #489, docs/projects/2608-site-design-system/20-29-strategy/20.01-rollout-plan.md

## 2026-08-20 - Blog engagement baseline: 3-day Clarity windows swing 25-75%

Phase 0.4 hardened before the read could be corrupted by it: blog scroll
depth measured 75.1% / 50.9% / 25.2% across three consecutive 3-day Clarity
windows (Aug 12-20, 70-220 sessions each). At this traffic the metric is
dominated by WHICH posts got traffic, not by design - a single-window
pre/post read is noise dressed as a result. Protocol locked in 2608 40.01:
28-day windows, session-weighted (before = ~46.0% scroll / 35.3s over
Aug 12-20), segmented by top-trafficked posts, read due 2026-09-17. Clarity's
API accepts explicit historical date ranges (verified) - not just
"last 3 days". Tag pages also got their missing screenshot coverage
(phase 0.3 gap: primary navigation with no baseline), recorded via
bin/record-baselines' first real outing - kept 1, restored 0, both legs.
Detail: docs/projects/2608-site-design-system/40-49-measurement/40.01-blog-engagement-baseline.md
105 changes: 105 additions & 0 deletions bin/record-baselines
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
# bin/record-baselines — accept ONLY the screenshot baselines you asked for
# from a re-record run, restoring everything else FORCE_SCREENSHOT_UPDATE
# rewrote. Automates the manual dance done by hand 3x on 2026-08-14 (copy
# out changed files, git checkout -- the rest, restore the copies).
#
# Usage:
# bin/record-baselines <path-or-glob>... # record on macOS host
# TEST=test/system/foo_test.rb bin/record-baselines macos/desktop/blog/index.png
# bin/record-baselines --dry-run <path-or-glob>... # show keep/restore, no test run
# bin/record-baselines --linux <path-or-glob>... # print the CI dispatch command instead
#
# Paths/globs are relative to test/fixtures/screenshots/, e.g.
# macos/desktop/blog/index.png
# macos/*/blog/*
#
# TEST= / TESTOPTS= pass through to `bin/rake test:system` unchanged (export
# them before calling this script) so you can record from one test file.

set -euo pipefail
cd "$(dirname "$0")/.."

SCREENSHOTS=test/fixtures/screenshots

if [ "${1:-}" = "--linux" ]; then
shift
branch=$(git rev-parse --abbrev-ref HEAD)
echo "record-baselines: local ARM Docker records plant false drift on Linux"
echo "baselines (documented 2026-08-20) — dispatching CI instead runs on the"
echo "same architecture the baselines were recorded for. Run:"
echo ""
echo " gh workflow run test.yml --ref $branch -f update-baselines=true"
exit 0
fi

dry_run=false
if [ "${1:-}" = "--dry-run" ]; then
dry_run=true
shift
fi

if [ "$#" -eq 0 ]; then
echo "usage: bin/record-baselines [--linux|--dry-run] <path-or-glob>..." >&2
exit 1
fi

keep_globs=("$@")

# Same dirty-fixture guard as bin/qtest / test/application_system_test_case.rb.
# --dry-run is exempt: it inspects whatever is already dirty as its demo set.
dirty=$(git status --porcelain "$SCREENSHOTS")
if [ -n "$dirty" ] && [ "$dry_run" = false ]; then
echo "record-baselines: screenshot fixtures are dirty — commit or reset first:" >&2
echo "$dirty" | head -10 >&2
echo " bin/rake test:screenshots:reset" >&2
exit 1
fi

rake_status=0
if [ "$dry_run" = true ]; then
echo "record-baselines: --dry-run — using the currently dirty files as the simulated record set"
else
echo "record-baselines: FORCE_SCREENSHOT_UPDATE=true bin/rake test:system"
# Don't let a red test skip the restore pass: record mode rewrites files
# as it runs, and an unrelated failure must still leave a reconciled tree.
FORCE_SCREENSHOT_UPDATE=true bin/rake test:system || rake_status=$?
fi

# -z: NUL-delimited, immune to git's C-quoting of paths with spaces.
# --untracked-files=all: a record run that creates a whole new directory of
# baselines must be enumerated file-by-file, not as one '?? dir/' entry.
# NOTE: keep-globs use bash `case` matching, where `*` crosses `/` —
# `macos/*` keeps EVERYTHING under macos/. Be as specific as you mean.
kept=() restored=()
while IFS= read -r -d '' line; do
[ -z "$line" ] && continue
status="${line:0:2}"
f="${line:3}"
rel="${f#"$SCREENSHOTS"/}"
match=false
for g in "${keep_globs[@]}"; do
case "$rel" in
$g) match=true; break ;;
esac
done
if [ "$match" = true ]; then
kept+=("$f")
else
restored+=("$f")
# Created baselines can't be `git checkout --`ed (untracked).
if [ "$status" = "??" ]; then
rm -f "$f"
else
git checkout -- "$f"
fi
fi
done < <(git status --porcelain -z --untracked-files=all "$SCREENSHOTS")

echo "record-baselines: kept ${#kept[@]}, restored ${#restored[@]}"
git status --porcelain "$SCREENSHOTS"

if [ "$rake_status" -ne 0 ]; then
echo "record-baselines: test run exited $rake_status (tree reconciled anyway)" >&2
fi
exit "$rake_status"
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,15 @@ before, which is a coherent state to sit in indefinitely.

**Gates per bundle — A, B, C**, as 1b.

### The blog engagement read (gates 2.4+)
### Measurement pivot (Paul, 2026-08-20 evening)

> **"Avoid measures, rebuild the whole blog, and we will use measure based on
> the whole blog."** The per-phase read choreography below is RETIRED as a
> gate: the blog is rebuilt in full now, and ONE measure of the whole rebuilt
> blog runs afterwards (protocol still 40.01 - session-weighted 28d windows,
> per-post segmentation). Nothing blog-scoped waits on a reading anymore.

### The blog engagement read (informational, no longer gating)

The whole point of blog-first. Baseline recorded 2026-08-20 (Clarity,
bot-filtered): blog pages **25.2% avg scroll depth, 26.3s avg engagement,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 40.01 — Blog engagement baseline (the "before" for phases 2.1+2.2)

**Recorded:** 2026-08-20, Clarity project `xum05dgnec` (bot-filtered), pages
under `/blog/`.
**Ship marker:** phases 2.1+2.2 merged to master `f80de8088` at 14:35 UTC,
2026-08-20. **The "after" clock starts at the first successful production
deploy of that SHA or later** — deploys were being managed by another session
at merge time; confirm the live-date before reading.

## The baseline — and why it is three windows, not one

| Window (3d) | Avg scroll depth | Avg engagement | Sessions |
|---|---|---|---|
| Aug 12–14 | **75.1%** | 32.3s | 144 |
| Aug 15–17 | **50.9%** | 56.4s | 73 |
| Aug 18–20 | **25.2%** | 26.3s | 219 |

Site-wide same period for reference: mobile 40.3% / 33.5s, PC 32.9% / 28.3s.

**A 3-day blog window swings 25→75% scroll depth.** At ~70–220 Clarity
sessions per window, the metric is dominated by which posts happened to get
traffic (a short viral post vs. a 16-minute guide) — not by design. Any
pre/post read on a single short window would be noise dressed as a result.

## Read protocol (gates phases 1a/1b/2.4+, per 20.01)

1. **Window: full 28 days**, 2026-08-20 → 2026-09-17, pulled as rolling 3-day
Clarity queries (the API accepts explicit historical ranges — verified) and
**averaged weighted by sessions**, not by window.
2. Compare against the session-weighted before: the three windows above give
**(75.1×144 + 50.9×73 + 25.2×219) / 436 ≈ 46.0% scroll, 35.3s engagement**
over Aug 12–20. Extend backwards with more windows at read time if Clarity
retention allows — more before-mass dampens the mix effect.
3. **Segment by post where sessions allow**: the honest comparison is
same-post before/after, not the shifting all-blog mix. At minimum, split
the read by the top-5 trafficked posts of each period.
4. `scroll_depth` GA4 milestones (25/50/75/90, shipping in PR #489) give a
second, independent read from first production deploy onward — no
"before" exists for them, so their first 28 days are themselves a baseline.
5. Qualitative stays primary at this traffic: ≥20 watched Clarity recordings
of post sessions in week one, rage/dead-click reports read.
6. **Write the result into this file** — better, flat, or worse, with the
numbers. Per 20.01: an unwritten result blocks the homepage/chrome phases;
a flat result does not.

**Read due: 2026-09-17.**
5 changes: 3 additions & 2 deletions docs/projects/2608-site-design-system/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ engagement** vs site avg 33–40% / 28–34s.

| Phase | What | Gate | Status |
|---|---|---|---|
| 0 (slim) | record-baselines wrapper, blog scroll/CTA events, coverage check | events verified firing | **not started** |
| 0 (slim) | record-baselines wrapper, blog scroll/CTA events, coverage, baseline doc | events verified firing | **PR #489** (GA4 admin steps manual) |
| 2.1 | `blog-list` restyle + tag pages | A + B + C | **shipped 2026-08-20** (PR pending) |
| 2.2 | posts: article-end CTA, ink tags (measure/full-bleed/code-ink deferred to 1a) | A + B + C | **shipped 2026-08-20** (PR pending) |
| — | **Engagement read written up** (28d Clarity pre/post) | written, with numbers | gates everything below |
| 2.2b | **Whole-blog rebuild** (post template: full-bleed cover, ink code blocks, meta-above-title; mobile covers on lists) | A + C | **in progress** (Paul 2026-08-20: no measure gates) |
| — | Whole-blog measure (28d after rebuild deploys, [40.01](40-49-measurement/40.01-blog-engagement-baseline.md) protocol) | informational | after rebuild |
| 1a/1b | Site-wide chrome (recolour, then spatial) | A + B + C | after the read |
| 2.4/2.5 | homepage, single-service | A + B + C, GSC gate on homepage | after the read |
| 3 | Content: real numbers, sample report | A + cold-eyes review | parallel |
Expand Down
Binary file modified test/fixtures/screenshots/linux/desktop/404.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/about_page/_missions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/about_us.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/blog/index.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/blog/index/_pagination.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/careers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/clients.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/clients/single-full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/contact_us.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/course/chapter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/course/landing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/free_consultation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/homepage/_services.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/privacy-policy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/services/_overview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/screenshots/linux/desktop/use-cases/_overview.png
Binary file modified test/fixtures/screenshots/linux/mobile/about_page/values.png
Binary file modified test/fixtures/screenshots/linux/mobile/blog/post.png
Binary file modified test/fixtures/screenshots/linux/mobile/careers.png
Binary file modified test/fixtures/screenshots/linux/mobile/clients.png
Binary file modified test/fixtures/screenshots/linux/mobile/clients/single-full.png
Binary file modified test/fixtures/screenshots/linux/mobile/course/landing.png
Binary file modified test/fixtures/screenshots/linux/mobile/free_consultation.png
Binary file modified test/fixtures/screenshots/linux/mobile/homepage.png
Binary file modified test/fixtures/screenshots/linux/mobile/vibe_code_rescue.png
9 changes: 9 additions & 0 deletions test/system/desktop_site_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ def test_blog_index_pagination
assert_stable_screenshot "blog/index/_pagination", skip_area: [".blog-post", ".post-feature"], tolerance: 0.03
end

def test_blog_tag_page
visit "/blog/tags/rails/"

# Rows churn as posts get (re)tagged; the lead carries the live post
# count. Chrome under test: eyebrow, tag-name H1, filter pills with the
# active-pill state.
assert_stable_screenshot "blog/tag", skip_area: [".blog-post", ".blog-lead"]
end

def test_visit_blog_post
visit "/"
within_top_bar { click_on "Blog" }
Expand Down
8 changes: 8 additions & 0 deletions test/system/mobile_site_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ def test_blog_index_pagination
assert_stable_screenshot "blog/index/_pagination", skip_area: [".blog-post", ".post-feature"], tolerance: 0.03
end

def test_blog_tag_page
visit "/blog/tags/rails/"

# Same masks as desktop: rows churn with tagging, the lead carries the
# live post count.
assert_stable_screenshot "blog/tag", skip_area: [".blog-post", ".blog-lead"]
end

def test_visit_blog_post
visit "/blog/"

Expand Down
2 changes: 1 addition & 1 deletion themes/beaver/layouts/blog/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ <h2 class="post-title">{{ .Title }}</h2>
{{ partial "blog/post-row.html" . }}
{{ end }}

{{ partial "blog/cta-band.html" . }}
{{ partial "blog/cta-band.html" "blog-index" }}

{{ partial "pagination" . }}
</div>
Expand Down
2 changes: 2 additions & 0 deletions themes/beaver/layouts/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ <h1 class="fl-heading">
<div
class="fl-button-wrap fl-button-width-auto fl-button-left">
<a
data-cta-location="hero"
href="{{ relURL "free-consultation/" }}"
target="_self"
class="fl-button">
Expand Down Expand Up @@ -1064,6 +1065,7 @@ <h2 class="fl-heading">
<div
class="fl-button-wrap fl-button-width-auto fl-button-center">
<a
data-cta-location="section"
href="{{ relURL "free-consultation/" }}"
target="_self"
class="fl-button">
Expand Down
2 changes: 1 addition & 1 deletion themes/beaver/layouts/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
{{ partial "blog/post-row.html" . }}
{{ end }}

{{ partial "blog/cta-band.html" . }}
{{ partial "blog/cta-band.html" "tag-index" }}

{{ partial "pagination.html" . }}
{{ end }}
Expand Down
9 changes: 6 additions & 3 deletions themes/beaver/layouts/partials/blog/cta-band.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{{- /* Audit CTA band before pagination — shared by the blog index and tag
pages. Copy follows the trust-note rule: every primary CTA carries a
{{- /* Audit CTA band before pagination — shared by the blog index, tag
pages, and article-end on posts. Call with the CTA location string
(blog-index / tag-index / article-end) — feeds the cta_click GA4 event
(2608 phase 0.1) via the data-cta-location listener in page/analytics.html.
Copy follows the trust-note rule: every primary CTA carries a
proof line (claims-canon: 4.8/5 on Clutch, linked, no review count). */ -}}
<div class="blog-cta">
<h2>Reading this because something is going wrong?</h2>
<p>A free code audit gives you a written assessment of your codebase in plain English.</p>
<a class="blog-cta-btn" href="{{ relURL "free-consultation/" }}">Get a Free Code Audit</a>
<a class="blog-cta-btn" data-cta-location="{{ . }}" href="{{ relURL "free-consultation/" }}">Get a Free Code Audit</a>
<p class="blog-cta-note">Rated <a href="https://clutch.co/profile/jetthoughts" rel="noopener" target="_blank">4.8/5 on Clutch</a> · you keep the write-up either way</p>
</div>
48 changes: 48 additions & 0 deletions themes/beaver/layouts/partials/page/analytics.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,53 @@
transport_type: 'beacon'
});
}, { passive: true });
{{/* Conversion event (2608 phase 0.1): contact / free-consultation form
submit. The form posts to an external service (data-lead-form on
partials/page/contact-form.html), so hook the submit event. Mark
generate_lead as a key event in GA4 admin. */}}
document.addEventListener('submit', function (e) {
if (!(e.target && e.target.hasAttribute && e.target.hasAttribute('data-lead-form'))) return;
// One-shot per pageview: the form targets _blank, so it stays filled
// and a double-submit would double-count the lead.
if (e.target.hasAttribute('data-lead-sent')) return;
e.target.setAttribute('data-lead-sent', '1');
gtag('event', 'generate_lead', {
transport_type: 'beacon'
});
}, true);
{{/* CTA clicks (2608 phase 0.1): any element carrying data-cta-location
(hero / section / blog-index / tag-index / article-end) reports which
CTA earned the click. */}}
document.addEventListener('click', function (e) {
var el = e.target && e.target.closest ? e.target.closest('[data-cta-location]') : null;
if (!el) return;
gtag('event', 'cta_click', {
location: el.getAttribute('data-cta-location'),
link_url: el.getAttribute('href') || '',
transport_type: 'beacon'
});
}, { passive: true });
{{ if and .IsPage (eq .Section "blog") }}
{{/* Scroll-depth milestones on blog posts (2608 phase 0.1). GA4 enhanced
measurement only records 90%; this adds the 25/50/75 read-depth
curve. Each milestone fires once per page view (marks are consumed),
and the listener removes itself after the last one. */}}
(function () {
var marks = [25, 50, 75, 90];
function depth() {
var max = document.documentElement.scrollHeight - window.innerHeight;
if (max <= 0) return;
var pct = (window.scrollY / max) * 100;
while (marks.length && pct >= marks[0]) {
gtag('event', 'scroll_depth', {
percent_scrolled: marks.shift(),
transport_type: 'beacon'
});
}
if (!marks.length) window.removeEventListener('scroll', depth);
}
window.addEventListener('scroll', depth, { passive: true });
})();
{{ end }}
</script>
{{ end }}
2 changes: 1 addition & 1 deletion themes/beaver/layouts/partials/page/contact-form.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<form method='post' target="_blank" rel="noopener noreferrer" action='{{ .Site.Params.forms.contact.action }}'>
<form method='post' data-lead-form target="_blank" rel="noopener noreferrer" action='{{ .Site.Params.forms.contact.action }}'>
<div class='gform-body gform_body'>
<div id='gform_fields_5' class='gform_fields top_label form_sublabel_below description_below'>
<div id="field_5_1" class="gfield gfield--type-text gfield--input-type-text gfield--width-half gf_half gf_half_left gfield_contains_required field_sublabel_below gfield--no-description field_description_below gfield_visibility_visible" data-js-reload="field_5_1" >
Expand Down
2 changes: 1 addition & 1 deletion themes/beaver/layouts/single.html
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ <h1 class="post-title">{{ .Title }}</h1>
surface with no conversion path (2608 phase 2.2). Course
chapters keep their own arrival actions instead. */}}
{{ if ne .Params.course_chapter true }}
{{ partial "blog/cta-band.html" . }}
{{ partial "blog/cta-band.html" "article-end" }}
{{ end }}
</article>

Expand Down
Loading