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
change ypkulvlnuyommpwzznwlpwuqxvnpwozu
commit 1d23979ab3d0217f9ca3b5169c31e7557181a3cb
author Alpha Chen <alpha@kejadlen.dev>
date
parent suorouvr
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>