Add timestamp anchors and search term highlighting
Pagefind sub-results link to anchored paragraphs. Episode
pages highlight query terms passed via ?q= parameter.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/lib/pages/episode.html.erb b/lib/pages/episode.html.erb
index 6eb5710..1635fb1 100644
--- a/lib/pages/episode.html.erb
+++ b/lib/pages/episode.html.erb
@@ -12,6 +12,8 @@
.back:hover { text-decoration: underline; }
p { margin-bottom: 1em; }
.timestamp { color: #888; font-size: 0.85rem; font-family: monospace; }
+ :target { background: #fff3a8; }
+ mark { background: #fff3a8; padding: 0 2px; border-radius: 2px; }
</style>
</head>
<body>
@@ -19,13 +21,44 @@
<a class="back" href="index.html">← All episodes</a>
<h1><%= CGI.escapeHTML("#{ep[:number]}. #{ep[:title]}") %></h1>
-<% ep[:text].split("\n\n").each do |para| %>
+<% ep[:text].split("\n\n").each_with_index do |para, i| %>
<% if para =~ /\A\[([^\]]+)\]\s*(.*)/ %>
- <p><span class="timestamp">[<%= CGI.escapeHTML($1) %>]</span> <%= CGI.escapeHTML($2) %></p>
+ <% anchor = $1.gsub(/[^0-9:]/, "").gsub(":", "-") %>
+ <p id="t-<%= anchor %>"><span class="timestamp">[<%= CGI.escapeHTML($1) %>]</span> <%= CGI.escapeHTML($2) %></p>
<% else %>
<p><%= CGI.escapeHTML(para) %></p>
<% end %>
<% end %>
+<script>
+// Highlight search terms from the pagefind query parameter.
+const params = new URLSearchParams(window.location.search);
+const query = params.get("q");
+if (query) {
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
+ const nodes = [];
+ while (walker.nextNode()) nodes.push(walker.currentNode);
+
+ nodes.forEach(node => {
+ if (node.parentElement.closest("h1, .back, script, style")) return;
+ let html = node.textContent;
+ let replaced = false;
+ terms.forEach(term => {
+ const regex = new RegExp(`(${term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi");
+ if (regex.test(html)) {
+ html = html.replace(regex, "<mark>$1</mark>");
+ replaced = true;
+ }
+ });
+ if (replaced) {
+ const span = document.createElement("span");
+ span.innerHTML = html;
+ node.parentNode.replaceChild(span, node);
+ }
+ });
+}
+</script>
+
</body>
</html>