Files Origin · The Magnus Institute ● Link Secure Clearance 05 SYS 0451
THE BUREAU obsidian declassified
File OCV-02122004 Clearance 05 · SEALED Vol. I · Jun 14, 2026
NIGHT FEED

Podcasts

Statement Begins

Network, hosts, how many episodes deep I am, the one line I wanted to keep. A podcast note is a show page waiting for someone to lay it out, and the card obliges, doors and all.

It reads the note once for the body, takes the rest from the fields, and keeps the exits lit: the feed, the Apple link, the way back to the audio.

A card that knows the show lives somewhere else, and points you at the door.

Custom Views Code

<div class="pod" data-name="{{file.name}}">
  <section class="pod-hero">
    <div class="pod-hero-bg"></div>
    <div class="pod-hero-art-wrap"><img class="pod-hero-art" src="" alt="" /></div>
    <div class="pod-hero-scrim"></div>
    <div class="pod-ghost"></div>
    <div class="pod-logo"><span class="pod-logo-dot"></span><span class="pod-logo-txt">PODCAST</span></div>
    <button class="pod-more" type="button">SHOW MORE</button>
    <div class="pod-hero-content">
      <p class="pod-eyebrow"></p>
      <span class="pod-accent"></span>
      <h1 class="pod-title" data-raw="{{file.basename}}"></h1>
      <p class="pod-epline"></p>
      <p class="pod-desc"></p>
      <div class="pod-scorechip">
        <span class="pod-score"></span>
        <span class="pod-verdict"></span>
      </div>
    </div>
  </section>
  <div class="pod-detail">
    <div class="pod-dims"></div>
    <div class="pod-picks"></div>
    <blockquote class="pod-quote" hidden></blockquote>
    <p class="pod-meta"></p>
  </div>
  <div class="pod-notes"></div>
</div>
.obsidian-custom-view-editable .obsidian-custom-view-render,
.obsidian-custom-view-render { padding: 0 !important; --line-width: 100%; --max-width: none; }

.pod {
  --pod-tint: #0c0c11;
  --pod-accent: #d8893f;
  --pod-ink: #f4f1ea;
  --pod-ink-dim: #b9b4ab;
  container-type: inline-size;
  container-name: pod;
  width: 100%;
  box-sizing: border-box;
  color: var(--pod-ink);
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro', sans-serif;
  background: var(--pod-tint);
  overflow: hidden;
}

/* ── HERO ─────────────────────────────────────────────── */
.pod-hero {
  position: relative;
  display: flex;
  isolation: isolate;
  min-height: clamp(360px, 60vh, 640px);
  overflow: hidden;
}
.pod-hero-bg {
  position: absolute; inset: -10%;
  background-size: cover; background-position: center;
  filter: blur(46px) saturate(1.35) brightness(.5);
  transform: scale(1.18);
  z-index: 0;
}
.pod-hero-art-wrap {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: 56%;
  display: flex; align-items: center; justify-content: flex-end;
  padding-right: clamp(20px, 4vw, 64px);
  z-index: 1;
}
.pod-hero-art {
  height: 80%; max-height: 540px;
  aspect-ratio: 1 / 1; object-fit: cover;
  border-radius: 10px;
  box-shadow: 0 40px 90px rgba(0,0,0,.7), 0 6px 20px rgba(0,0,0,.5);
  background: #0c0c0f;
}
.pod-hero-scrim {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background:
    linear-gradient(90deg,
      var(--pod-tint) 0%,
      color-mix(in srgb, var(--pod-tint) 88%, transparent) 30%,
      color-mix(in srgb, var(--pod-tint) 32%, transparent) 60%,
      transparent 82%),
    linear-gradient(0deg, var(--pod-tint) 1%, transparent 52%),
    linear-gradient(180deg, color-mix(in srgb, var(--pod-tint) 50%, transparent) 0%, transparent 22%);
}
.pod-ghost {
  position: absolute; left: clamp(16px, 3vw, 48px); bottom: clamp(-8px, -0.4vw, 0px);
  z-index: 1; pointer-events: none;
  font-weight: 800; letter-spacing: -.02em; line-height: .9;
  font-size: clamp(34px, 7vw, 92px);
  color: rgba(255,255,255,.055);
  white-space: nowrap; max-width: 92%; overflow: hidden; text-overflow: clip;
  text-transform: capitalize;
}
.pod-logo {
  position: absolute; right: clamp(16px, 3vw, 44px); bottom: clamp(14px, 2vw, 26px);
  z-index: 4; display: flex; align-items: center; gap: 9px;
  font: 700 11px/1 -apple-system, sans-serif; letter-spacing: .2em;
  color: rgba(255,255,255,.55);
}
.pod-logo-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--pod-accent);
  box-shadow: 0 0 12px color-mix(in srgb, var(--pod-accent) 70%, transparent); }
.pod-more {
  position: absolute; top: clamp(14px, 2vw, 22px); right: clamp(16px, 3vw, 44px);
  z-index: 5;
  font: 600 11px/1 -apple-system, sans-serif; letter-spacing: .12em;
  color: var(--pod-ink);
  background: rgba(0,0,0,.42); border: 1px solid rgba(255,255,255,.2);
  backdrop-filter: blur(6px);
  padding: 8px 15px; border-radius: 999px; cursor: pointer; transition: background .15s ease;
}
.pod-more:hover { background: rgba(0,0,0,.66); }

.pod-hero-content {
  position: relative; z-index: 3; align-self: flex-end;
  max-width: min(620px, 70%);
  padding: clamp(20px, 4vw, 56px);
  padding-top: clamp(48px, 9vw, 96px);
  padding-bottom: clamp(46px, 6vw, 78px);
}
.pod-eyebrow {
  margin: 0 0 14px; display: flex; align-items: center; gap: 9px;
  font: 700 11px/1 -apple-system, sans-serif; letter-spacing: .2em; text-transform: uppercase;
  color: var(--pod-accent);
  text-shadow: 0 1px 12px rgba(0,0,0,.4);
}
.pod-eyebrow::before {
  content: ""; width: 0; height: 0;
  border-left: 8px solid currentColor; border-top: 5px solid transparent; border-bottom: 5px solid transparent;
}
.pod-accent {
  display: block; width: 54px; height: 3px; border-radius: 2px;
  background: var(--pod-accent); margin-bottom: 18px;
  box-shadow: 0 0 18px color-mix(in srgb, var(--pod-accent) 55%, transparent);
}
.pod-title {
  margin: 0; font-weight: 800; letter-spacing: -.02em; line-height: 1.02;
  font-size: clamp(30px, 5.2vw, 62px);
  text-shadow: 0 2px 34px rgba(0,0,0,.55);
}
.pod-epline {
  margin: 14px 0 0; font: 600 clamp(11px, 1.1vw, 14px)/1.35 -apple-system, sans-serif;
  letter-spacing: .05em; text-transform: uppercase; color: var(--pod-ink);
}
.pod-epline:empty { display: none; }
.pod-desc {
  margin: 13px 0 0; font-size: clamp(13px, 1.1vw, 16px); line-height: 1.55;
  color: var(--pod-ink-dim); max-width: 54ch;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.pod-scorechip {
  margin-top: 22px; display: inline-flex; align-items: center; gap: 14px;
  padding: 10px 18px 10px 15px; border-radius: 14px;
  background: rgba(0,0,0,.34); backdrop-filter: blur(8px);
  border: 1px solid rgba(255,255,255,.13);
}
.pod-score { font-size: clamp(26px, 3vw, 40px); font-weight: 800; line-height: 1; color: #fff; }
.pod-score[data-empty="1"] { font-size: clamp(14px, 1.4vw, 17px); font-weight: 600; color: var(--pod-ink-dim); }
.pod-verdict { font-size: clamp(12px, 1.05vw, 14px); line-height: 1.35; color: var(--pod-ink-dim);
  max-width: 320px; font-style: italic; }

/* ── DETAIL STRIP ─────────────────────────────────────── */
.pod-detail {
  position: relative; z-index: 1;
  background: var(--pod-tint);
  padding: clamp(26px, 4vw, 52px) clamp(20px, 5vw, 80px);
  display: flex; flex-direction: column; gap: clamp(22px, 3vw, 32px);
}
.pod-dims { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px 40px; }
.pod-dims:empty { display: none; }
.pod-dim { display: grid; grid-template-columns: 100px 1fr 30px; align-items: center; gap: 12px; }
.pod-dim-k { font-size: 11px; letter-spacing: .07em; text-transform: uppercase; color: #97938b; white-space: nowrap; }
.pod-dim-bar { height: 6px; border-radius: 3px; background: rgba(255,255,255,.1); overflow: hidden; }
.pod-dim-bar > i { display: block; height: 100%; background: var(--pod-accent); border-radius: 3px; }
.pod-dim-v { font-size: 13px; font-weight: 600; color: var(--pod-ink); text-align: right; }

.pod-sec-title { font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: #8d8980; margin: 0 0 12px; display: flex; align-items: center; gap: 14px; }
.pod-sec-title::after { content: ""; flex: 1; height: 1px; background: rgba(255,255,255,.1); }
.pod-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.pod-chip { font-size: 13px; color: #cfcbc3; border: 1px solid rgba(255,255,255,.14); border-radius: 999px; padding: 5px 12px; }

.pod-quote { margin: 0; border-inline-start: 3px solid var(--pod-accent); padding: 4px 0 4px 18px; color: #d8d4cc; font-style: italic; font-size: clamp(14px,1.3vw,17px); line-height: 1.55; }
.pod-quote cite { display: block; margin-top: 8px; font-style: normal; font-size: 12px; letter-spacing: .04em; color: #8d8980; }

.pod-meta { margin: 0; font-size: 12.5px; color: #8d8980; letter-spacing: .02em; }
.pod-meta b { color: #b7b3ab; font-weight: 600; }
.pod-meta a { color: var(--pod-accent); text-decoration: none; }

/* ── NOTES (SHOW MORE) ────────────────────────────────── */
.pod-notes { display: none; background: var(--pod-tint); padding: 0 clamp(20px,5vw,80px) clamp(30px,5vw,60px); }
.pod.notes-open .pod-notes { display: block; }
.pod-notes-body { font-size: 15.5px; line-height: 1.7; color: #b7b3ab; }
.pod-notes-body > :first-child { margin-top: 0; }
.pod-notes-body h2 { font-size: 14px; letter-spacing: .06em; text-transform: uppercase; color: #cfcbc3; margin: 24px 0 8px; }
.pod-notes-body h3 { font-size: 14px; color: #cfcbc3; margin: 16px 0 6px; }
.pod-notes-body a { color: var(--pod-accent); text-decoration: none; }

/* ── RESPONSIVE ───────────────────────────────────────── */
@container pod (max-width: 680px) {
  .pod-hero { min-height: clamp(440px, 86vw, 600px); }
  .pod-hero-art-wrap { width: 100%; justify-content: center; align-items: flex-start; padding: 9% 0 0; opacity: .42; }
  .pod-hero-art { height: auto; width: 62%; }
  .pod-hero-content { max-width: 100%; }
  .pod-dims { grid-template-columns: 1fr; }
}
@media (max-width: 680px) {
  .pod-hero { min-height: clamp(440px, 86vw, 600px); }
  .pod-hero-art-wrap { width: 100%; justify-content: center; align-items: flex-start; padding: 9% 0 0; opacity: .42; }
  .pod-hero-art { height: auto; width: 62%; }
  .pod-hero-content { max-width: 100%; }
  .pod-dims { grid-template-columns: 1fr; }
}
const _c = this;
(async () => {
  try {
    const root = _c.querySelector(".pod");
    if (!root) return;

    const wantName = root.dataset.name || "";
    let file = app.workspace.getActiveFile();
    if ((!file && wantName) || (file && wantName && file.name !== wantName)) {
      const byName = app.vault.getFiles().find(f => f.name === wantName);
      if (byName) file = byName;
    }
    if (!file) return;
    const fm = (app.metadataCache.getFileCache(file) || {}).frontmatter || {};
    const clean = s => String(s == null ? "" : s).replace(/\[\[|\]\]/g, "").trim();
    const list = v => Array.isArray(v) ? v : (v == null || v === "" ? [] : [v]);
    const label = s => { let v = clean(s); v = v.includes("|") ? v.split("|").pop() : v.replace(/^[^|]+ - /, ""); return v.trim(); };

    /* ── cover: local [[png]] -> resource path, or remote URL ── */
    let coverVal = fm.cover; if (Array.isArray(coverVal)) coverVal = coverVal[0];
    let resolved = "", dest = null;
    if (coverVal) {
      const s = String(coverVal).trim();
      if (/^https?:\/\//.test(s)) resolved = s;
      else {
        const m = s.match(/\[\[([^\]|#]+)/);
        const linkpath = m ? m[1].trim() : s.replace(/^!?\[\[|\]\]$/g, "").trim();
        dest = app.metadataCache.getFirstLinkpathDest(linkpath, file.path);
        if (dest) resolved = app.vault.getResourcePath(dest);
      }
    }
    const artWrap = root.querySelector(".pod-hero-art-wrap");
    const bgEl = root.querySelector(".pod-hero-bg");
    if (resolved) {
      const art = root.querySelector(".pod-hero-art"); if (art) art.src = resolved;
      if (bgEl) bgEl.style.backgroundImage = "url('" + resolved.replace(/'/g, "%27") + "')";
    } else if (artWrap) { artWrap.style.display = "none"; }

    /* ── title ── */
    const titleEl = root.querySelector(".pod-title");
    if (titleEl) titleEl.textContent = (titleEl.getAttribute("data-raw") || "").replace(/^Podcasts?\s*-\s*/, "");

    /* ── eyebrow = network ── */
    const hosts = list(fm.host).map(label).filter(Boolean);
    const eyebrowEl = root.querySelector(".pod-eyebrow");
    if (eyebrowEl) eyebrowEl.textContent = fm.network ? label(fm.network) : "Podcast";

    /* ── episode line ── */
    const genres = list(fm.genre).map(clean).filter(Boolean);
    const ep = [];
    if (hosts.length) ep.push("With " + hosts.join(", "));
    if (genres.length) ep.push(genres.join(" / "));
    if (fm.episode_count) ep.push(fm.episode_count + " episodes");
    if (fm.latest_episode) ep.push("Latest " + String(fm.latest_episode).slice(0, 10));
    else if (fm.year) ep.push("Since " + String(fm.year));
    const epEl = root.querySelector(".pod-epline"); if (epEl) epEl.textContent = ep.join("  ·  ");

    /* ── ghost word = primary genre ── */
    const ghostEl = root.querySelector(".pod-ghost");
    if (ghostEl) { if (genres.length) ghostEl.textContent = genres[0]; else ghostEl.remove(); }

    /* ── read body once: description + notes ── */
    let body = "";
    try {
      body = await app.vault.read(file);
      body = body.replace(/^---[\s\S]*?\n---\n?/, "");
      body = body.replace(/^\s*((?:>.*\n?)+)\n?/, m => /dataviewjs|\[!abstract\]/.test(m) ? "" : m);
      body = body.trim();
    } catch (e) {}

    function firstPara(md) {
      const out = [];
      for (const raw of md.split(/\n/)) {
        const t = raw.trim();
        const isBlock = !t || /^#{1,6}\s/.test(t) || /^>/.test(t) || /^[-*+]\s/.test(t) || /^\d+\.\s/.test(t) || /^(```|---|\|)/.test(t);
        if (isBlock) { if (out.length) break; else continue; }
        out.push(t);
      }
      return out.join(" ");
    }
    let desc = firstPara(body)
      .replace(/\[\[([^\]|#]+)(?:\|([^\]]+))?\]\]/g, (m, a, b) => b || a)
      .replace(/[*_`]/g, "").trim();
    if (desc.length > 280) desc = desc.slice(0, 277).replace(/\s+\S*$/, "") + "…";
    const descEl = root.querySelector(".pod-desc");
    if (descEl) { if (desc) descEl.textContent = desc; else descEl.remove(); }

    /* ── score (podcast rubric, mirrors Podcasts.base) ── */
    const num = v => (typeof v === "number" && !isNaN(v)) ? v : 0;
    const C = ["hosting","content","production_quality","guests","consistency","engagement","structure","artwork","personal_score"];
    const sum = C.reduce((a, k) => a + num(fm[k]), 0);
    const filled = C.filter(k => num(fm[k])).length;
    const bonus = (fm.favorite ? 3 : 0) + (fm.binged_14d ? 2 : 0) + (fm.subscribed ? 2 : 0);
    const total = filled >= 6 ? Math.min(100, Math.round(sum / filled * 10) + bonus) : null;
    const B = [[100,'A masterpiece of the form — flawless in every respect.'],[95,'An all-time great of the medium, with only unoverlookable minor flaws.'],[90,'Format-defining, held back only slightly. A permanent subscription.'],[85,'A standout — the kind of show you press on people. Very few flaws.'],[80,'Excellent and distinctive. Well worth your ears.'],[75,'Above average, with several elevating elements.'],[70,'Solid, lifted by some standout element — a host, a beat, a guest.'],[65,'Above average, but with noticeable flaws.'],[60,'Decent — some genuinely good episodes.'],[55,'Passable, but disappoints on most fronts.'],[50,'The midpoint — average, forgettable.'],[45,'Subpar; falls short on most fronts.'],[40,'Poor — significant flaws, little to enjoy.'],[35,'Below average; flat and unoriginal.'],[30,'Flat and typical; background noise.'],[25,'Disappointing; problems outweigh the positives.'],[20,'Largely unlistenable; few redeeming qualities.'],[15,'Severely flawed; not worth the feed slot.'],[10,'Among the worst of its kind.'],[5,'Abysmal — a complete failure.'],[0,'Not worth mentioning.']];
    const scoreEl = root.querySelector(".pod-score"), verdictEl = root.querySelector(".pod-verdict");
    if (total !== null) { scoreEl.textContent = String(total); verdictEl.textContent = (B.find(b => total >= b[0]) || B[B.length - 1])[1]; }
    else { scoreEl.textContent = "Not yet scored"; scoreEl.setAttribute("data-empty", "1"); verdictEl.textContent = filled + "/6 core fields"; }

    /* ── dimension bars ── */
    const DIMS = [["hosting","Hosting"],["content","Content"],["production_quality","Production"],["guests","Guests"],["consistency","Consistency"],["engagement","Engagement"],["structure","Structure"],["artwork","Artwork"],["personal_score","Personal"]];
    const dimsEl = root.querySelector(".pod-dims");
    if (dimsEl) DIMS.forEach(([k, lab]) => {
      const val = num(fm[k]); if (!val) return;
      const row = document.createElement("div"); row.className = "pod-dim";
      row.innerHTML = `<span class="pod-dim-k">${lab}</span><span class="pod-dim-bar"><i style="width:${Math.min(100, val * 10)}%"></i></span><span class="pod-dim-v">${val}</span>`;
      dimsEl.appendChild(row);
    });

    /* ── standout episodes ── */
    const eps = list(fm.standout_episodes).map(clean).filter(Boolean);
    const picksEl = root.querySelector(".pod-picks");
    if (picksEl && eps.length) picksEl.innerHTML = `<p class="pod-sec-title">Standout episodes</p><div class="pod-chips">${eps.map(t => `<span class="pod-chip">${t}</span>`).join("")}</div>`;
    else if (picksEl) picksEl.remove();

    /* ── favorite quote ── */
    const quote = clean(fm.favorite_quote), quoteEl = root.querySelector(".pod-quote");
    if (quoteEl && quote) {
      const src = clean(fm.favorite_quote_ep);
      quoteEl.innerHTML = `“${quote}”` + (src ? `<cite>— ${src}</cite>` : "");
      quoteEl.removeAttribute("hidden");
    } else if (quoteEl) quoteEl.remove();

    /* ── metadata ── */
    const meta = [];
    if (fm.network) meta.push(`<b>Network</b> ${label(fm.network)}`);
    if (fm.latest_episode) meta.push(`<b>Latest</b> ${String(fm.latest_episode).slice(0,10)}`);
    if (fm.last) meta.push(`<b>Last heard</b> ${String(fm.last).slice(0,10)}`);
    if (fm.date_reviewed) meta.push(`<b>Reviewed</b> ${String(fm.date_reviewed).slice(0,10)}`);
    if (fm.feed) meta.push(`<a href="${String(fm.feed)}">RSS feed</a>`);
    if (fm.apple_url) meta.push(`<a href="${String(fm.apple_url)}">Apple Podcasts</a>`);
    const metaEl = root.querySelector(".pod-meta");
    if (metaEl) { if (meta.length) metaEl.innerHTML = meta.join("  ·  "); else metaEl.remove(); }

    /* ── drop the detail strip entirely if it has nothing ── */
    const detail = root.querySelector(".pod-detail");
    if (detail && !detail.querySelector(".pod-dim, .pod-chip, .pod-quote, .pod-meta")) detail.remove();

    /* ── notes (SHOW MORE) ── */
    const more = root.querySelector(".pod-more");
    const notesEl = root.querySelector(".pod-notes");
    let hasNotes = false;
    if (notesEl) {
      try {
        const meaningful = body.replace(/^#{1,6}\s.*$/gm, "").replace(/\s+/g, "");
        if (meaningful) {
          notesEl.innerHTML = '<p class="pod-sec-title">Notes</p><div class="pod-notes-body markdown-rendered"></div>';
          const target = notesEl.querySelector(".pod-notes-body");
          const obs = require("obsidian");
          const comp = new obs.Component();
          if (obs.MarkdownRenderer && obs.MarkdownRenderer.render) await obs.MarkdownRenderer.render(app, body, target, file.path, comp);
          else if (obs.MarkdownRenderer) await obs.MarkdownRenderer.renderMarkdown(body, target, file.path, comp);
          hasNotes = true;
        } else notesEl.remove();
      } catch (e) { console.error("[Podcasts notes]", e); if (notesEl) notesEl.remove(); }
    }
    if (more) {
      if (!hasNotes) more.remove();
      else more.addEventListener("click", () => {
        const open = root.classList.toggle("notes-open");
        more.textContent = open ? "SHOW LESS" : "SHOW MORE";
      });
    }

    /* ── accent + tint sampled from the cover ── */
    if (resolved) {
      const toHsl = (r, g, b) => {
        r/=255; g/=255; b/=255; const mx=Math.max(r,g,b), mn=Math.min(r,g,b); let h, s, l=(mx+mn)/2;
        if (mx===mn){h=s=0;} else { const d=mx-mn; s=l>0.5?d/(2-mx-mn):d/(mx+mn);
          switch(mx){case r:h=(g-b)/d+(g<b?6:0);break;case g:h=(b-r)/d+2;break;default:h=(r-g)/d+4;} h/=6; }
        return [h*360, s, l];
      };
      const hslToRgb = (h, s, l) => { h/=360; let r,g,b;
        if (s===0){r=g=b=l;} else { const hue=(p,q,t)=>{if(t<0)t+=1;if(t>1)t-=1;if(t<1/6)return p+(q-p)*6*t;if(t<1/2)return q;if(t<2/3)return p+(q-p)*(2/3-t)*6;return p;};
          const q=l<0.5?l*(1+s):l+s-l*s, p=2*l-q; r=hue(p,q,h+1/3); g=hue(p,q,h); b=hue(p,q,h-1/3); }
        return [Math.round(r*255),Math.round(g*255),Math.round(b*255)];
      };
      const hex = (r,g,b) => "#"+[r,g,b].map(v=>Math.max(0,Math.min(255,v)).toString(16).padStart(2,"0")).join("");
      const sample = img => {
        try {
          const W=44,H=44, cv=document.createElement("canvas"); cv.width=W; cv.height=H;
          const ctx=cv.getContext("2d"); ctx.drawImage(img,0,0,W,H);
          const dt=ctx.getImageData(0,0,W,H).data; let wR=0,wG=0,wB=0,ws=0;
          for (let i=0;i<dt.length;i+=4){ if(dt[i+3]<128)continue; const r=dt[i],g=dt[i+1],b=dt[i+2];
            const mx=Math.max(r,g,b),mn=Math.min(r,g,b); const sc=mx===0?0:(mx-mn)/mx; const w=0.1+sc*sc;
            wR+=r*w; wG+=g*w; wB+=b*w; ws+=w; }
          if(!ws) return;
          let [h,s,l]=toHsl(wR/ws,wG/ws,wB/ws);
          // deep tinted scrim base — keeps a hint of the artwork's hue
          { const ts=Math.min(s,0.55), tl=Math.min(Math.max(l*0.14,0.05),0.11);
            const [tr,tg,tb]=hslToRgb(h,ts,tl); root.style.setProperty("--pod-tint", hex(tr,tg,tb)); }
          // vibrant accent (only when the artwork has real colour)
          if (s >= 0.12) {
            const as=Math.min(Math.max(s,0.55),0.95), al=Math.min(Math.max(l,0.52),0.64);
            const [r,g,b]=hslToRgb(h,as,al); root.style.setProperty("--pod-accent", hex(r,g,b));
          }
        } catch (e) {}
      };
      let src = resolved, isBlob = false, cors = false;
      if (dest) { try { const buf = await app.vault.readBinary(dest); src = URL.createObjectURL(new Blob([buf])); isBlob = true; } catch (e) {} }
      else cors = true;
      const probe = new Image(); if (cors) probe.crossOrigin = "anonymous";
      probe.onload = () => { sample(probe); if (isBlob) URL.revokeObjectURL(src); };
      probe.onerror = () => { if (isBlob) URL.revokeObjectURL(src); };
      probe.src = src;
    }
  } catch (e) { console.error("[Custom Views: Podcasts]", e); }
})();

Templater Template

<%*
/**
 * iTunes Search → Podcast Frontmatter (Obsidian Templater)
 * No API key required. Searches Apple's podcast directory, pulls the show
 * artwork, network, genre, feed URL and episode count, writes full
 * frontmatter, renames the note. Rendered as a card by the Custom Views
 * "Podcasts" view. Scoring fields are written blank for you to fill by hand;
 * the score is derived live (Podcasts.base + the in-note dataviewjs callout).
 */
const ITUNES = "https://itunes.apple.com/search";
const today = tp.date.now("YYYY-MM-DD");
const fileTitle = tp.file.title;

// 1) Ask for podcast name -----------------------------------------------
let query = await tp.system.prompt("Enter podcast name (leave empty for file name):");
if (!query || !query.trim()) query = fileTitle;
query = query.trim();

// 2) Search the Apple podcast directory ---------------------------------
let searchResponse;
try {
  searchResponse = await tp.web.request(
    `${ITUNES}?media=podcast&entity=podcast&limit=15&term=${encodeURIComponent(query)}`
  );
} catch (e) { tR += `iTunes search error: ${e}`; return; }
// tp.web.request may return a parsed object or a JSON string depending on version
let payload = searchResponse;
if (typeof payload === "string") { try { payload = JSON.parse(payload); } catch (e) { payload = {}; } }
const results = (payload && payload.results) || [];
if (!results.length) { tR += `No results for "${query}".`; return; }

const display = results.map(r => {
  const t = r.collectionName || r.trackName || "Unknown";
  const a = r.artistName || "?";
  const g = r.primaryGenreName || "";
  const n = r.trackCount ? `${r.trackCount} eps` : "";
  return [t, a && `— ${a}`, g && `[${g}]`, n].filter(Boolean).join(" ");
});

// 3) Pick ----------------------------------------------------------------
const chosen = await tp.system.suggester(display, results, true, "Pick a podcast");
if (!chosen) { tR += "Selection cancelled."; return; }

// 4) Extract -------------------------------------------------------------
const showTitle = (chosen.collectionName || chosen.trackName || fileTitle).trim();
const network = (chosen.artistName || "").trim();
const feedUrl = chosen.feedUrl || "";
const collectionUrl = chosen.collectionViewUrl || "";
const itunesId = chosen.collectionId || "";
const episodeCount = chosen.trackCount || "";
const latestEpisode = (chosen.releaseDate || "").slice(0, 10);
const country = chosen.country || "";
// highest-res square artwork iTunes will serve
let cover = chosen.artworkUrl600 || chosen.artworkUrl100 || chosen.artworkUrl60 || "";
if (cover) cover = cover.replace(/\/\d+x\d+bb\.(jpg|png)$/i, "/1000x1000bb.$1");
// genres: drop the umbrella "Podcasts" entry
const genres = (chosen.genres || []).filter(g => g && g.toLowerCase() !== "podcasts");

// Helper: link an existing note if present (host → Person note) ----------
async function noteExists(name) {
  for (const folder of ["References", ""]) {
    const path = folder ? `${folder}/${name}.md` : `${name}.md`;
    try { if (await tp.file.exists(path)) return true; } catch (e) {}
  }
  return false;
}
async function linkOrName(names) {
  const out = [];
  for (const raw of names) {
    const name = String(raw).trim();
    if (!name) continue;
    out.push((await noteExists(name)) ? `[[${name}]]` : name);
  }
  return out;
}
const genreLinks = await linkOrName(genres);

// 5) Last heard date -----------------------------------------------------
let lastDate = await tp.system.prompt("Last heard date (YYYY-MM-DD)", today);
if (!lastDate || !/^\d{4}-\d{2}-\d{2}$/.test(lastDate.trim())) lastDate = today;
lastDate = lastDate.trim();

// 6) Rename to "Podcasts - Title" ---------------------------------------
const colonFixed = showTitle.replace(/:/g, " - ");
let sanitized = colonFixed.replace(/[\/#%&{}<>*?$!'"@+`|=]/g, "").trim();
if (!sanitized) sanitized = fileTitle;
const newName = sanitized;
if (newName !== fileTitle) await tp.file.rename(newName);

// 7) Build YAML ----------------------------------------------------------
const L = [];
L.push("---");
L.push("connections:");
L.push("categories:");
L.push('  - "[[Podcasts]]"');
L.push("host: []");
L.push(network ? `network: "${network.replace(/"/g, '\\"')}"` : "network: []");
if (genreLinks.length) { L.push("genre:"); genreLinks.forEach(g => L.push(`  - "${g}"`)); } else L.push("genre: []");
L.push(cover ? `cover: "${cover}"` : "cover: []");
L.push(`year: `);
L.push(`episode_count: ${episodeCount}`);
L.push(`latest_episode: ${latestEpisode}`);
L.push(feedUrl ? `feed: "${feedUrl}"` : "feed: []");
L.push(collectionUrl ? `apple_url: "${collectionUrl}"` : "apple_url: []");
L.push(itunesId ? `itunes_id: ${itunesId}` : "itunes_id: []");
L.push(country ? `country: ${country}` : "country: []");
L.push(`created: ${today}`);
L.push(`last: ${lastDate}`);

// Review meta -----------------------------------------------------------
L.push("status: Active");
L.push(`date_reviewed: ${today}`);

// Scores — each 0–10. Defaulted to 0 (a number, not blank text) so Obsidian
// registers these as Number properties; the formulas treat 0 as "unfilled".
L.push("hosting: 0");
L.push("content: 0");
L.push("production_quality: 0");
L.push("guests: 0");
L.push("consistency: 0");
L.push("engagement: 0");
L.push("structure: 0");
L.push("artwork: 0");
L.push("personal_score: 0");

// Flags ------------------------------------------------------------------
L.push("favorite: false");
L.push("binged_14d: false");
L.push("subscribed: false");

// Standouts --------------------------------------------------------------
L.push("standout_episodes: []");
L.push("favorite_quote: ");
L.push("favorite_quote_ep: ");
L.push("---");

// Body scaffold ----------------------------------------------------------
// The dataviewjs block recomputes the score live from this note's own fields —
// the same formula as Podcasts.base, shown on the page itself.
const body = [
  "",
  "> [!abstract] Score",
  "> ```dataviewjs",
  "> const p = dv.current();",
  '> const n = v => (typeof v === "number" && !isNaN(v)) ? v : 0;',
  '> const C = ["hosting","content","production_quality","guests","consistency","engagement","structure","artwork","personal_score"];',
  "> const sum = C.reduce((a,k)=>a+n(p[k]),0);",
  "> const count = C.filter(k=>n(p[k])).length;",
  "> const bonus = (p.favorite?3:0) + (p.binged_14d?2:0) + (p.subscribed?2:0);",
  "> const total = count >= 6 ? Math.min(100, Math.round(sum/count*9 + bonus/7*10)) : null;",
  "> const B = [[100,'A masterpiece of the form — flawless in every respect.'],[95,'An all-time great of the medium, with only unoverlookable minor flaws.'],[90,'Format-defining, held back only slightly. A permanent subscription.'],[85,'A standout — the kind of show you press on people. Very few flaws.'],[80,'Excellent and distinctive. Well worth your ears.'],[75,'Above average, with several elevating elements.'],[70,'Solid, lifted by some standout element — a host, a beat, a guest.'],[65,'Above average, but with noticeable flaws.'],[60,'Decent — some genuinely good episodes.'],[55,'Passable, but disappoints on most fronts.'],[50,'The midpoint — average, forgettable.'],[45,'Subpar; falls short on most fronts.'],[40,'Poor — significant flaws, little to enjoy.'],[35,'Below average; flat and unoriginal.'],[30,'Flat and typical; background noise.'],[25,'Disappointing; problems outweigh the positives.'],[20,'Largely unlistenable; few redeeming qualities.'],[15,'Severely flawed; not worth the feed slot.'],[10,'Among the worst of its kind.'],[5,'Abysmal — a complete failure.'],[0,'Not worth mentioning.']];",
  "> const verdict = total === null ? '' : (B.find(b => total >= b[0]) || B[B.length-1])[1];",
  "> dv.paragraph(total === null ? `_Not yet scored — ${count}/6 core fields._` : `**${total}** — ${verdict}`);",
  "> ```",
  "",
  "## The Pitch",
  "",
  "## Hosts & Dynamic",
  "",
  "## Format & Structure",
  "",
  "## Standout Episodes",
  "",
  "## What Works",
  "",
  "## What Drags",
  "",
  "## Verdict",
  ""
];

tR += L.join("\n") + "\n" + body.join("\n");
-%>
DECLASSIFIED

Field Assembly

Two plugins stand this show page up.

  • Custom Views: the renderer. Make a view, build its rule in the menu to match categories contains Podcasts (the screenshot above), then paste the template, styling, and JS blocks into their fields.
  • Templater: the note-maker. Paste the Templater block into a template file and bind it to a hotkey or a folder template.

The template fills the note from a public database when you create it, no key needed. After that the card runs on what is in the note.

Statement Ends

▌ Cross-Referenced Files