diff --git a/build/Site.hs b/build/Site.hs index b5e5447..de34a97 100644 --- a/build/Site.hs +++ b/build/Site.hs @@ -4,11 +4,13 @@ module Site (rules) where import Control.Monad (filterM, when) import Data.List (isPrefixOf) -import Data.Maybe (fromMaybe) +import Data.Maybe (catMaybes, fromMaybe) import System.Environment (lookupEnv) import System.FilePath (takeDirectory, takeFileName, replaceExtension) +import Text.Read (readMaybe) import qualified Data.Aeson as Aeson import qualified Data.ByteString.Lazy.Char8 as LBS +import qualified Data.Map.Strict as Map import Hakyll import Authors (buildAllAuthors, applyAuthorRules) import Backlinks (backlinkRules) @@ -445,6 +447,24 @@ rules = do let urls = [ "/" ++ r | Just r <- routes ] makeItem $ LBS.unpack (Aeson.encode urls) + -- --------------------------------------------------------------------------- + -- Epistemic metadata manifest — maps page URLs to epistemic fields + -- (status, confidence, importance, evidence, scope, novelty, practicality, + -- stability, score) for client-side search filtering. + -- --------------------------------------------------------------------------- + create ["data/epistemic-meta.json"] $ do + route idRoute + compile $ do + essays <- loadAll (allEssays .&&. hasNoVersion) :: Compiler [Item String] + posts <- loadAll ("content/blog/*.md" .&&. hasNoVersion) :: Compiler [Item String] + fiction <- loadAll ("content/fiction/*.md" .&&. hasNoVersion) :: Compiler [Item String] + poetry <- loadAll (allPoetry .&&. hasNoVersion) :: Compiler [Item String] + music <- loadAll ("content/music/*/index.md" .&&. hasNoVersion) :: Compiler [Item String] + let items = essays ++ posts ++ fiction ++ poetry ++ music + pairs <- mapM epistemicEntry items + let metaMap = Map.fromList (catMaybes pairs) + makeItem $ LBS.unpack (Aeson.encode metaMap) + -- --------------------------------------------------------------------------- -- Atom feed — all content sorted by date -- --------------------------------------------------------------------------- @@ -485,3 +505,48 @@ rules = do <> bodyField "description" <> defaultContext renderAtom musicFeedConfig feedCtx compositions + +-- --------------------------------------------------------------------------- +-- Epistemic metadata extraction +-- --------------------------------------------------------------------------- + +-- | Extract epistemic metadata from a content item's frontmatter. +-- Returns Nothing if the item has no route or no epistemic fields. +epistemicEntry :: Item String -> Compiler (Maybe (String, Map.Map String String)) +epistemicEntry item = do + let ident = itemIdentifier item + mRoute <- getRoute ident + case mRoute of + Nothing -> return Nothing + Just r -> do + meta <- getMetadata ident + let url = "/" ++ r + fields = catMaybes + [ grab "status" meta + , grab "confidence" meta + , grab "importance" meta + , grab "evidence" meta + , grab "scope" meta + , grab "novelty" meta + , grab "practicality" meta + , grab "stability" meta + ] + obj = Map.fromList fields + -- Compute overall-score the same way Contexts.overallScoreField does. + obj' = case ( readMaybe =<< lookupString "confidence" meta :: Maybe Int + , readMaybe =<< lookupString "evidence" meta :: Maybe Int + ) of + (Just conf, Just ev) -> + let raw :: Double + raw = fromIntegral conf / 100.0 * 0.6 + + fromIntegral (ev - 1) / 4.0 * 0.4 + score = max 0 (min 100 (round (raw * 100.0) :: Int)) + in Map.insert "score" (show score) obj + _ -> obj + if Map.null obj' + then return Nothing + else return (Just (url, obj')) + where + grab name meta = case lookupString name meta of + Just v -> Just (name, v) + Nothing -> Nothing diff --git a/content/search.md b/content/search.md index 5a45417..b07f7f1 100644 --- a/content/search.md +++ b/content/search.md @@ -8,6 +8,105 @@ search: true +
+ +
+ +

diff --git a/static/css/library.css b/static/css/library.css index 7bd8471..3fe9e1b 100644 --- a/static/css/library.css +++ b/static/css/library.css @@ -9,14 +9,17 @@ } /* ============================================================ - SORT CONTROLS + CONTROLS (sort + filter) ============================================================ */ .library-controls { + margin-bottom: 2.5rem; +} + +.library-controls-row { display: flex; align-items: center; gap: 0.6rem; - margin-bottom: 2.5rem; } .library-controls-label { @@ -53,6 +56,160 @@ font-weight: 600; } +/* Filter toggle */ + +.library-filter-toggle { + font-family: var(--font-sans); + font-size: 0.75rem; + color: var(--text-muted); + background: none; + border: 1px solid var(--border); + border-radius: 2px; + padding: 0.15em 0.55em; + cursor: pointer; + margin-left: auto; + transition: border-color 0.1s, color 0.1s; +} + +.library-filter-toggle:hover, +.library-filter-toggle[aria-expanded="true"] { + border-color: var(--text-muted); + color: var(--text); +} + +.filter-toggle-badge { + font-weight: 600; +} + +/* ============================================================ + FILTER PANEL + ============================================================ */ + +.library-filters { + border-top: 1px solid var(--border); + padding-top: 0.75rem; + margin-top: 0.75rem; +} + +.library-filters[hidden] { + display: none; +} + +.filter-row { + display: flex; + align-items: center; + gap: 0.45rem; + margin-bottom: 0.45rem; +} + +.filter-label { + font-family: var(--font-sans); + font-size: 0.72rem; + color: var(--text-faint); + width: 5.5rem; + flex-shrink: 0; +} + +.filter-options { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.25rem; +} + +.filter-prefix { + font-family: var(--font-sans); + font-size: 0.72rem; + color: var(--text-faint); +} + +.filter-btn { + font-family: var(--font-sans); + font-size: 0.72rem; + color: var(--text-muted); + background: none; + border: 1px solid var(--border); + border-radius: 2px; + padding: 0.1em 0.45em; + cursor: pointer; + transition: border-color 0.1s, color 0.1s; +} + +.filter-btn:hover { + border-color: var(--border-muted); + color: var(--text); +} + +.filter-btn.is-active { + border-color: var(--text-muted); + color: var(--text); + font-weight: 600; +} + +.filter-number { + font-family: var(--font-sans); + font-size: 0.72rem; + width: 3rem; + padding: 0.1em 0.3em; + border: 1px solid var(--border); + border-radius: 2px; + background: transparent; + color: var(--text); +} + +.filter-number:focus { + outline: none; + border-color: var(--text-muted); +} + +.filter-row-actions { + justify-content: flex-end; + margin-top: 0.25rem; +} + +.filter-clear-btn { + font-family: var(--font-sans); + font-size: 0.72rem; + color: var(--text-faint); + background: none; + border: none; + cursor: pointer; + padding: 0; +} + +.filter-clear-btn:hover { + color: var(--text); +} + +/* Filtered state */ + +.is-filtered { + display: none !important; +} + +/* Search-page result filtering (applied via search-filters.js) */ + +.search-filtered { + display: none !important; +} + +/* Search page filter controls — just the toggle, no sort buttons */ + +.search-filter-controls { + display: flex; + justify-content: flex-end; + margin-bottom: 0.5rem; +} + +/* Empty state message */ + +.library-empty { + font-family: var(--font-sans); + font-size: var(--text-size-small); + color: var(--text-faint); + font-style: italic; +} + /* ============================================================ PORTAL SECTIONS ============================================================ */ diff --git a/static/css/popups.css b/static/css/popups.css index d66400d..723b6b9 100644 --- a/static/css/popups.css +++ b/static/css/popups.css @@ -188,6 +188,24 @@ min-width: 230px; } +/* Epistemic term definition popup — shown on hover for filter labels, + metadata strip items, and footer DT elements. */ +[data-ep-term] { + cursor: help; +} + +.popup-ep-term { + max-width: 320px; +} + +.popup-ep-term .popup-source a { + color: inherit; + text-decoration: none; +} +.popup-ep-term .popup-source a:hover { + text-decoration: underline; +} + /* PDF thumbnail popup — first-page image generated by pdftoppm at build time */ .link-popup:has(.popup-pdf) { padding: 0; diff --git a/static/css/typography.css b/static/css/typography.css index 014cccb..b39d8dd 100644 --- a/static/css/typography.css +++ b/static/css/typography.css @@ -530,6 +530,14 @@ pre code { margin-top: 0.825rem; /* Half line-height */ } +/* Paragraphs inside annotation bodies: no first-line indent. + Annotations are short callouts, not body prose; the indent reads as a + typographic glitch when stacked inside the bordered box. */ +#markdownBody .annotation-body p + p { + text-indent: 0; + margin-top: 0.825rem; +} + /* ============================================================ MATHEMATICS (KaTeX) diff --git a/static/js/popups.js b/static/js/popups.js index 86f3ea5..bdb3df1 100644 --- a/static/js/popups.js +++ b/static/js/popups.js @@ -56,7 +56,26 @@ }); } + /* Epistemic term definitions — concise summaries from the colophon, + shown on hover for filter labels, metadata strip items, etc. */ + var EP_DEFS = { + status: 'A controlled vocabulary describing where the work stands: Draft, Working model, Durable, Refined, Superseded, or Deprecated.', + confidence: 'An integer from 0\u2013100, representing the author\u2019s credence in the central thesis.', + importance: 'How much the author thinks this matters, on a 1\u20135 dot scale. Useful for orienting a reader who has limited time.', + evidence: 'How well-evidenced the claims are, on a 1\u20135 scale. High importance and low evidence indicates a speculative position.', + trust: 'A 0\u2013100 score derived automatically from confidence (60%) and evidence quality (40%). Answers \u201chow much should you trust the central claim?\u201d and nothing else.', + scope: 'An orientation from personal to civilizational. Not a rating\u2009\u2014\u2009deliberately not folded into the trust score.', + novelty: 'An orientation from conventional to innovative. Not a rating\u2009\u2014\u2009deliberately not folded into the trust score.', + practicality: 'An orientation from abstract to exceptional. Not a rating\u2009\u2014\u2009deliberately not folded into the trust score.', + stability: 'Auto-computed from git history. Very new or barely-touched documents are volatile; actively-revised are revising; older settled documents are fairly stable, stable, or established.' + }; + function bindTargets(root) { + /* Epistemic term definitions — filter labels, metadata strip, footer */ + root.querySelectorAll('[data-ep-term]').forEach(function (el) { + bind(el, epistemicTermContent); + }); + /* Citation markers */ root.querySelectorAll('a.cite-link[href^="#ref-"]').forEach(function (el) { bind(el, citationContent); @@ -672,6 +691,24 @@ return Promise.resolve(wrap); } + /* Epistemic term definition — shows a concise description from the + colophon for any element tagged with data-ep-term="". */ + function epistemicTermContent(target) { + var term = target.dataset.epTerm; + var def = term && EP_DEFS[term]; + if (!def) return Promise.resolve(null); + var label = term.charAt(0).toUpperCase() + term.slice(1); + return Promise.resolve( + '' + ); + } + /* Local PDF — shows the build-time first-page thumbnail (.thumb.png). Returns null (no popup) if the thumbnail file does not exist. */ function pdfContent(target) { diff --git a/static/js/search-filters.js b/static/js/search-filters.js new file mode 100644 index 0000000..7d56d94 --- /dev/null +++ b/static/js/search-filters.js @@ -0,0 +1,314 @@ +/* search-filters.js — Epistemic effort filters for the search page. + * + * Loads /data/epistemic-meta.json (a map of URL → epistemic fields) + * and hides search results whose source page doesn't match the active + * filters. Works for both Pagefind keyword results and semantic results. + * + * Reuses the same CSS classes and filter-panel markup as library.html + * so the two pages look and behave identically. + */ +(function () { + 'use strict'; + + var KEY = 'search-filter-state'; + + var SCALES = { + scope: ['personal', 'average', 'broad', 'civilizational'], + novelty: ['conventional', 'moderate', 'idiosyncratic', 'innovative'], + practicality: ['abstract', 'moderate', 'high', 'exceptional'], + stability: ['volatile', 'revising', 'fairly stable', 'stable', 'established'] + }; + + var state = { + status: [], + confidence: null, + importance: null, + evidence: null, + score: null, + scope: null, + novelty: null, + practicality: null, + stability: null + }; + + var epistemicMeta = null; /* URL → {status, confidence, …} loaded lazily */ + + /* ---- Persistence ---- */ + + function load() { + try { + var raw = localStorage.getItem(KEY); + if (raw) { + var obj = JSON.parse(raw); + for (var k in state) { + if (obj.hasOwnProperty(k)) state[k] = obj[k]; + } + } + } catch (e) {} + } + + function save() { + try { localStorage.setItem(KEY, JSON.stringify(state)); } catch (e) {} + } + + /* ---- Metadata loading ---- */ + + var metaPromise = null; + + function loadMeta() { + if (epistemicMeta) return Promise.resolve(epistemicMeta); + if (metaPromise) return metaPromise; + metaPromise = fetch('/data/epistemic-meta.json') + .then(function (r) { return r.ok ? r.json() : {}; }) + .catch(function () { return {}; }) + .then(function (data) { epistemicMeta = data; return data; }); + return metaPromise; + } + + /* ---- Filtering logic ---- */ + + function passes(meta) { + if (!meta) return true; /* no metadata = don't filter out */ + + if (state.status.length) { + var s = (meta.status || '').toLowerCase(); + if (!s || state.status.indexOf(s) === -1) return false; + } + if (state.confidence !== null) { + if (!meta.confidence || +meta.confidence < state.confidence) return false; + } + if (state.importance !== null) { + if (!meta.importance || +meta.importance < state.importance) return false; + } + if (state.evidence !== null) { + if (!meta.evidence || +meta.evidence < state.evidence) return false; + } + if (state.score !== null) { + if (!meta.score || +meta.score < state.score) return false; + } + + var ords = ['scope', 'novelty', 'practicality', 'stability']; + for (var i = 0; i < ords.length; i++) { + var k = ords[i]; + if (state[k] !== null) { + var v = (meta[k] || '').toLowerCase(); + var idx = SCALES[k].indexOf(v); + if (idx === -1 || idx < state[k]) return false; + } + } + + return true; + } + + function hasActiveFilters() { + if (state.status.length) return true; + var fields = ['confidence', 'importance', 'evidence', 'score', + 'scope', 'novelty', 'practicality', 'stability']; + for (var i = 0; i < fields.length; i++) { + if (state[fields[i]] !== null) return true; + } + return false; + } + + /* ---- URL extraction ---- */ + + /* Normalise a URL to a pathname for lookup in epistemicMeta. + Pagefind results use full URLs; semantic results use relative paths. */ + function normUrl(href) { + if (!href) return null; + try { + var u = new URL(href, window.location.origin); + return u.pathname; + } catch (e) { + return href; + } + } + + /* ---- Apply filters to rendered results ---- */ + + function applyToPagefind() { + if (!epistemicMeta || !hasActiveFilters()) { + /* Remove any previous filtering */ + document.querySelectorAll('.pagefind-ui__result.search-filtered').forEach(function (el) { + el.classList.remove('search-filtered'); + }); + return; + } + document.querySelectorAll('.pagefind-ui__result').forEach(function (el) { + var link = el.querySelector('.pagefind-ui__result-link'); + if (!link) return; + var url = normUrl(link.getAttribute('href')); + var meta = url ? epistemicMeta[url] : null; + el.classList.toggle('search-filtered', !passes(meta)); + }); + } + + function applyToSemantic() { + if (!epistemicMeta || !hasActiveFilters()) { + document.querySelectorAll('.semantic-result.search-filtered').forEach(function (el) { + el.classList.remove('search-filtered'); + }); + return; + } + document.querySelectorAll('.semantic-result').forEach(function (el) { + var link = el.querySelector('.semantic-result-title'); + if (!link) return; + var url = normUrl(link.getAttribute('href')); + var meta = url ? epistemicMeta[url] : null; + el.classList.toggle('search-filtered', !passes(meta)); + }); + } + + function applyFilters() { + applyToPagefind(); + applyToSemantic(); + syncUI(); + save(); + } + + /* ---- UI sync ---- */ + + function activeCount() { + var n = 0; + if (state.status.length) n++; + var fields = ['confidence', 'importance', 'evidence', 'score', + 'scope', 'novelty', 'practicality', 'stability']; + for (var i = 0; i < fields.length; i++) { + if (state[fields[i]] !== null) n++; + } + return n; + } + + function syncUI() { + var badge = document.querySelector('.filter-toggle-badge'); + var n = activeCount(); + if (badge) badge.textContent = n ? ' (' + n + ')' : ''; + + document.querySelectorAll('.filter-status-btn').forEach(function (btn) { + btn.classList.toggle('is-active', state.status.indexOf(btn.dataset.value) !== -1); + }); + + var ci = document.getElementById('filter-confidence'); + if (ci) ci.value = state.confidence !== null ? state.confidence : ''; + var si = document.getElementById('filter-score'); + if (si) si.value = state.score !== null ? state.score : ''; + + document.querySelectorAll('.filter-threshold-btn').forEach(function (btn) { + btn.classList.toggle('is-active', state[btn.dataset.field] === +btn.dataset.value); + }); + + document.querySelectorAll('.filter-ordinal-btn').forEach(function (btn) { + btn.classList.toggle('is-active', state[btn.dataset.field] === +btn.dataset.index); + }); + } + + /* ---- Init ---- */ + + document.addEventListener('DOMContentLoaded', function () { + load(); + + var panel = document.getElementById('search-filters'); + var toggle = document.querySelector('.library-filter-toggle'); + + if (activeCount() > 0 && panel && toggle) { + panel.hidden = false; + toggle.setAttribute('aria-expanded', 'true'); + } + + syncUI(); + + /* Load metadata eagerly if filters are active */ + if (hasActiveFilters()) { + loadMeta().then(applyFilters); + } + + /* Toggle panel */ + if (toggle && panel) { + toggle.addEventListener('click', function () { + var opening = panel.hidden; + panel.hidden = !opening; + toggle.setAttribute('aria-expanded', opening ? 'true' : 'false'); + }); + } + + /* Status buttons */ + document.querySelectorAll('.filter-status-btn').forEach(function (btn) { + btn.addEventListener('click', function () { + var v = btn.dataset.value; + var i = state.status.indexOf(v); + if (i === -1) state.status.push(v); + else state.status.splice(i, 1); + loadMeta().then(applyFilters); + }); + }); + + /* Threshold buttons (importance, evidence) */ + document.querySelectorAll('.filter-threshold-btn').forEach(function (btn) { + btn.addEventListener('click', function () { + var f = btn.dataset.field; + var v = +btn.dataset.value; + state[f] = (state[f] === v) ? null : v; + loadMeta().then(applyFilters); + }); + }); + + /* Ordinal buttons (scope, novelty, practicality, stability) */ + document.querySelectorAll('.filter-ordinal-btn').forEach(function (btn) { + btn.addEventListener('click', function () { + var f = btn.dataset.field; + var idx = +btn.dataset.index; + state[f] = (state[f] === idx) ? null : idx; + loadMeta().then(applyFilters); + }); + }); + + /* Number inputs (confidence, trust/score) */ + ['confidence', 'score'].forEach(function (field) { + var el = document.getElementById('filter-' + (field === 'score' ? 'score' : field)); + if (!el) return; + el.addEventListener('input', function () { + var v = el.value.trim(); + state[field] = v !== '' ? Math.max(0, Math.min(100, parseInt(v, 10) || 0)) : null; + loadMeta().then(applyFilters); + }); + }); + + /* Clear all */ + var clearBtn = document.querySelector('.filter-clear-btn'); + if (clearBtn) { + clearBtn.addEventListener('click', function () { + state.status = []; + state.confidence = null; + state.importance = null; + state.evidence = null; + state.score = null; + state.scope = null; + state.novelty = null; + state.practicality = null; + state.stability = null; + applyFilters(); + }); + } + + /* Observe Pagefind result changes to re-apply filters. + Pagefind dynamically rebuilds the results container. */ + var searchEl = document.getElementById('search'); + if (searchEl) { + new MutationObserver(function () { + if (hasActiveFilters() && epistemicMeta) { + applyToPagefind(); + } + }).observe(searchEl, { childList: true, subtree: true }); + } + + /* Observe semantic results container */ + var semanticEl = document.getElementById('semantic-results'); + if (semanticEl) { + new MutationObserver(function () { + if (hasActiveFilters() && epistemicMeta) { + applyToSemantic(); + } + }).observe(semanticEl, { childList: true, subtree: true }); + } + }); +}()); diff --git a/templates/default.html b/templates/default.html index 28e4068..17bc05f 100644 --- a/templates/default.html +++ b/templates/default.html @@ -10,6 +10,7 @@ $if(search)$ + $endif$ $body$ $partial("templates/partials/footer.html")$ diff --git a/templates/library.html b/templates/library.html index afe30f1..d5a32d1 100644 --- a/templates/library.html +++ b/templates/library.html @@ -3,19 +3,119 @@

Everything on this site, organized by portal.

- Sort by -
- - - +
+ Sort by +
+ + + +
+ +
+
+

No entries match the current filters.

+ $if(research-entries)$

Research

    $for(research-entries)$ -
  • +
  • $title$ @@ -29,7 +129,7 @@ $if(nonfiction-entries)$

    Nonfiction

      $for(nonfiction-entries)$ -
    • +
    • $title$ @@ -43,7 +143,7 @@ $if(fiction-entries)$

      Fiction

        $for(fiction-entries)$ -
      • +
      • $title$ @@ -57,7 +157,7 @@ $if(poetry-entries)$

        Poetry

          $for(poetry-entries)$ -
        • +
        • $title$ @@ -71,7 +171,7 @@ $if(music-entries)$

          Music

            $for(music-entries)$ -
          • +
          • $title$ @@ -85,7 +185,7 @@ $if(ai-entries)$

            AI

              $for(ai-entries)$ -
            • +
            • $title$ @@ -99,7 +199,7 @@ $if(tech-entries)$

              Tech

                $for(tech-entries)$ -
              • +
              • $title$ @@ -113,7 +213,7 @@ $if(miscellany-entries)$

                Miscellany

                  $for(miscellany-entries)$ -
                • +
                • $title$ @@ -126,60 +226,264 @@ $endif$
                  diff --git a/templates/partials/head.html b/templates/partials/head.html index 0e7e7a3..9e0cc32 100644 --- a/templates/partials/head.html +++ b/templates/partials/head.html @@ -14,6 +14,7 @@ $if(home)$Levi Neuwirth$else$$title$ — Levi Neuwirth</ti <link rel="stylesheet" href="/css/images.css"> $if(home)$<link rel="stylesheet" href="/css/home.css">$endif$ $if(library)$<link rel="stylesheet" href="/css/library.css">$endif$ +$if(search)$<link rel="stylesheet" href="/css/library.css">$endif$ $if(new-page)$<link rel="stylesheet" href="/css/new.css">$endif$ $if(memento-mori)$<link rel="stylesheet" href="/css/memento-mori.css">$endif$ $if(catalog)$<link rel="stylesheet" href="/css/catalog.css">$endif$ diff --git a/templates/partials/metadata.html b/templates/partials/metadata.html index 2211728..9000d25 100644 --- a/templates/partials/metadata.html +++ b/templates/partials/metadata.html @@ -19,14 +19,14 @@ $endif$ $if(status)$ <div class="meta-row meta-epistemic-strip" data-pagefind-ignore="all"> - $if(overall-score)$<span class="ep-trust" title="Trust score: $overall-score$/100 (confidence × 0.6 + evidence × 0.4)"><span class="ep-score">$overall-score$%</span> trust</span>$endif$ - <span class="ep-status">$status$</span> - $if(confidence)$<span class="ep-row" title="Confidence">$confidence$% confidence</span>$endif$ - $if(importance-dots)$<span class="ep-row" title="Importance: $importance$/5"><span class="ep-dots">$importance-dots$</span> importance</span>$endif$ - $if(evidence-dots)$<span class="ep-row" title="Evidence quality: $evidence$/5"><span class="ep-dots">$evidence-dots$</span> evidence quality</span>$endif$ - $if(scope)$<span class="ep-row" title="Scope">$scope$ scope</span>$endif$ - $if(novelty)$<span class="ep-row" title="Novelty">$novelty$ novelty</span>$endif$ - $if(practicality)$<span class="ep-row" title="Practicality">$practicality$ practicality</span>$endif$ + $if(overall-score)$<span class="ep-trust" data-ep-term="trust"><span class="ep-score">$overall-score$%</span> trust</span>$endif$ + <span class="ep-status" data-ep-term="status">$status$</span> + $if(confidence)$<span class="ep-row" data-ep-term="confidence">$confidence$% confidence</span>$endif$ + $if(importance-dots)$<span class="ep-row" data-ep-term="importance"><span class="ep-dots">$importance-dots$</span> importance</span>$endif$ + $if(evidence-dots)$<span class="ep-row" data-ep-term="evidence"><span class="ep-dots">$evidence-dots$</span> evidence quality</span>$endif$ + $if(scope)$<span class="ep-row" data-ep-term="scope">$scope$ scope</span>$endif$ + $if(novelty)$<span class="ep-row" data-ep-term="novelty">$novelty$ novelty</span>$endif$ + $if(practicality)$<span class="ep-row" data-ep-term="practicality">$practicality$ practicality</span>$endif$ </div> $endif$ <nav class="meta-row meta-pagelinks" aria-label="Page sections"> diff --git a/templates/partials/page-footer.html b/templates/partials/page-footer.html index 8842814..61ce1ed 100644 --- a/templates/partials/page-footer.html +++ b/templates/partials/page-footer.html @@ -36,9 +36,9 @@ <div class="meta-footer-section meta-footer-epistemic" id="epistemic"> <h3><a href="/colophon.html#living-documents">Epistemic</a></h3> <dl class="ep-expanded"> - <dt>Stability</dt><dd>$stability$</dd> + <dt data-ep-term="stability">Stability</dt><dd>$stability$</dd> $if(last-reviewed)$<dt>Last reviewed</dt><dd>$last-reviewed$</dd>$endif$ - $if(confidence-trend)$<dt>Confidence trend</dt><dd>$confidence-trend$</dd>$endif$ + $if(confidence-trend)$<dt data-ep-term="confidence">Confidence trend</dt><dd>$confidence-trend$</dd>$endif$ </dl> </div> $endif$