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

Musicians

Statement Begins

Give it a name, a country, the year they formed, and it will make you a card. Then it does the part I like: it goes through the rest of the vault for every album I ever filed under that artist and builds the discography out of my own notes.

So the card is not really about the musician. It is about everything I have already said about them, gathered into one file without my asking twice.

Nothing leaves the vault. The whole picture is assembled out of notes I had forgotten were connected.

Custom Views Code

<div class="mus-card" data-name="{{file.name}}">
  <div class="mus-stage">
    <img class="mus-side mus-side-l" alt="" />
    <div class="mus-portrait-wrap">
      <img class="mus-portrait" src="" alt="" />
      <button class="mus-more" type="button">SHOW MORE</button>
    </div>
    <img class="mus-side mus-side-r" alt="" />
  </div>
  <div class="mus-head">
    <div class="mus-accent"></div>
    <h1 class="mus-title" data-raw="{{file.basename}}"></h1>
    <p class="mus-sub"></p>
  </div>
  <blockquote class="mus-bio" hidden></blockquote>
  <div class="mus-notes"></div>
</div>
.obsidian-custom-view-editable .obsidian-custom-view-render,
.obsidian-custom-view-render { padding: 0 !important; --line-width: 100%; --max-width: none; }

.mus-card {
  --mus-accent: var(--color-accent, #d8893f);
  --mus-ink: #ece9e3;
  container-type: inline-size;
  container-name: mus;
  position: relative;
  width: 100%;
  box-sizing: border-box;
  padding: clamp(24px, 4vw, 56px) clamp(20px, 5vw, 80px) clamp(40px, 6vw, 80px);
  color: var(--mus-ink);
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro', sans-serif;
  background: radial-gradient(135% 95% at 50% 14%, #1b1b20 0%, #111114 55%, #07070a 100%);
  overflow: hidden;
}

/* ── coverflow stage ── */
.mus-stage { position: relative; display: flex; align-items: center; justify-content: center; perspective: 1700px; min-height: clamp(260px, 44vh, 500px); margin-bottom: clamp(18px, 3vw, 34px); }
.mus-portrait-wrap { position: relative; z-index: 3; flex: 0 0 auto; }
.mus-portrait {
  display: block;
  width: clamp(190px, 28vw, 330px);
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 8px;
  box-shadow: 0 34px 80px rgba(0,0,0,.72), 0 4px 14px rgba(0,0,0,.5);
  background: #0c0c0f;
}
.mus-side {
  flex: 0 0 auto;
  width: clamp(150px, 20vw, 250px);
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: 8px;
  opacity: .92;
  filter: brightness(.82) saturate(1);
  box-shadow: 0 24px 56px rgba(0,0,0,.62);
  z-index: 1;
}
.mus-side-l { transform: rotateY(30deg) scale(.9); margin-right: clamp(16px, 5vw, 80px); transform-origin: right center; }
.mus-side-r { transform: rotateY(-30deg) scale(.9); margin-left: clamp(16px, 5vw, 80px); transform-origin: left center; }

.mus-more {
  position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
  font: 600 11px/1 -apple-system, sans-serif; letter-spacing: .12em; color: var(--mus-ink);
  background: rgba(10,10,12,.62); border: 1px solid rgba(255,255,255,.22); backdrop-filter: blur(6px);
  padding: 8px 16px; border-radius: 999px; cursor: pointer; transition: background .15s ease;
}
.mus-more:hover { background: rgba(20,20,24,.85); }

/* ── head ── */
.mus-head { max-width: 760px; }
.mus-accent { width: 64px; height: 3px; background: var(--mus-accent); margin-bottom: 16px; border-radius: 2px; }
.mus-title { margin: 0; font-size: clamp(28px, 4vw, 50px); font-weight: 700; letter-spacing: -.02em; line-height: 1.05; }
.mus-sub { margin: 8px 0 0; font-size: clamp(13px, 1.2vw, 16px); color: #b7b3ab; letter-spacing: .03em; text-transform: uppercase; }

/* ── bio ── */
.mus-bio { margin: clamp(22px,3vw,32px) 0 0; max-width: 70ch; border-inline-start: 3px solid var(--mus-accent); padding: 4px 0 4px 18px; color: #d8d4cc; font-size: clamp(15px,1.2vw,18px); line-height: 1.65; font-style: normal; }

/* ── notes (albums table, revealed by SHOW MORE) ── */
.mus-notes { display: none; margin-top: clamp(26px,4vw,40px); }
.mus-card.notes-open .mus-notes { display: block; }
.mus-notes-body { font-size: 15.5px; line-height: 1.7; color: #b7b3ab; }
.mus-notes-body > :first-child { margin-top: 0; }
.mus-notes-body h2 { font-size: 14px; letter-spacing: .06em; text-transform: uppercase; color: #cfcbc3; margin: 0 0 12px; display: flex; align-items: center; gap: 14px; }
.mus-notes-body h2::after { content: ""; flex: 1; height: 1px; background: rgba(255,255,255,.1); }
.mus-notes-body a { color: var(--mus-accent); text-decoration: none; }

/* ── narrow / mobile ── */
@container mus (max-width: 640px) {
  .mus-side { display: none; }
  .mus-portrait { width: clamp(200px, 60vw, 300px); }
}
@media (max-width: 640px) {
  .mus-side { display: none; }
  .mus-portrait { width: clamp(200px, 60vw, 300px); }
}

/* ── computed discography table (replaces the .base embed) ── */
.mus-sec-title { font-size: 13px; letter-spacing: .08em; text-transform: uppercase; color: var(--bu-muted, #9a958c); margin: 0 0 10px; }
.mus-albums { width: 100%; border-collapse: collapse; font-family: var(--bu-font-body); font-size: 14px; }
.mus-albums th {
  background: var(--bu-accent, #b81200); color: var(--bu-on-accent, #fff);
  font-family: var(--bu-font-label); text-transform: uppercase; letter-spacing: .04em;
  font-weight: 700; font-size: 11px; text-align: left; padding: 5px 10px;
}
.mus-albums td { border-bottom: 1px solid var(--bu-border, rgba(255,255,255,.08)); padding: 6px 10px; color: var(--bu-text, #c8c3ba); }
.mus-albums tbody tr:hover { background: color-mix(in srgb, var(--bu-accent, #b81200) 12%, transparent); }
.mus-al-score { color: var(--bu-brass, #ca9a4e); font-variant-numeric: tabular-nums; width: 4em; }
.mus-al-year { width: 5em; color: var(--bu-muted, #9a958c); }
.mus-albums a.internal-link { color: var(--mus-accent, var(--bu-accent, #b81200)); text-decoration: none; }
.mus-albums a.internal-link:hover { text-decoration: underline; }
const _c = this;
(async () => {
  try {
    const root = _c.querySelector(".mus-card");
    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]);

    // --- images: centre portrait + 2 sides (all artist) ---
    const imgs = list(fm.artist_images).map(String).filter(Boolean);
    const centre = imgs[0] || "";
    const pic = root.querySelector(".mus-portrait"); if (pic && centre) pic.src = centre;
    const sl = root.querySelector(".mus-side-l"), sr = root.querySelector(".mus-side-r");
    if (sl) { const u = imgs[1] || imgs[0]; if (u) sl.src = u; else sl.remove(); }
    if (sr) { const u = imgs[2] || imgs[1] || imgs[0]; if (u) sr.src = u; else sr.remove(); }

    // --- title / sub ---
    const title = root.querySelector(".mus-title");
    if (title) title.textContent = (title.getAttribute("data-raw") || "").replace(/^Musician\s*-\s*/, "");
    const sub = [clean(fm.genre), clean(fm.style), clean(fm.country), fm.formed ? String(fm.formed) : ""].filter(Boolean);
    const subEl = root.querySelector(".mus-sub");
    if (subEl) { if (sub.length) subEl.textContent = sub.join("  ·  "); else subEl.remove(); }

    // --- bio ---
    const bio = clean(fm.bio), bioEl = root.querySelector(".mus-bio");
    if (bioEl) { if (bio) { bioEl.textContent = bio; bioEl.removeAttribute("hidden"); } else bioEl.remove(); }

    // --- notes: computed discography table. (.base embeds don't paint rows inside a
    //     detached custom-view render — so we query + score the albums ourselves.) ---
    const more = root.querySelector(".mus-more");
    const notesEl = root.querySelector(".mus-notes");
    let hasNotes = false;
    if (notesEl) {
      try {
        const num = v => (typeof v === "number" && !isNaN(v)) ? v : 0;
        const S = ["singing","sound_quality","album_art","production_quality","replayability","album_cohesiveness","album_tone","lyrics","personal_score"];
        const scoreOf = afm => {
          const pct = afm.number_of_tracks ? (Array.isArray(afm.favorite_tracks) ? afm.favorite_tracks.length : 0) / afm.number_of_tracks * 10 : 0;
          const filled = S.filter(k => num(afm[k])).length + (pct ? 1 : 0);
          const sum = S.reduce((a, k) => a + num(afm[k]), 0) + pct;
          const bonus = (afm.favorite ? 3 : 0) + (afm.relistened_14d ? 2 : 0) + (afm.title_track_good ? 2 : 0);
          return filled >= 6 ? Math.min(100, Math.round(sum / filled * 10) + bonus) : null;
        };
        const myAliases = list(fm.aliases).map(clean);
        const myShort = (file.basename || "").replace(/^Musician\s*-\s*/, "");
        const rows = [];
        for (const f of app.vault.getMarkdownFiles()) {
          const afm = (app.metadataCache.getFileCache(f) || {}).frontmatter; if (!afm) continue;
          if (!list(afm.categories).map(clean).includes("Albums")) continue;
          const match = list(afm.artist).some(a => {
            const mm = String(a).match(/\[\[([^\]|#]+)/);
            const tgt = mm ? mm[1].trim() : clean(a);
            const dest = app.metadataCache.getFirstLinkpathDest(tgt, f.path);
            if (dest && dest.path === file.path) return true;
            const disp = clean(a);
            return tgt === file.basename || disp === myShort || myAliases.includes(disp);
          });
          if (!match) continue;
          rows.push({
            name: f.basename.replace(/^Albums?\s*-\s*/, ""),
            path: f.path,
            score: scoreOf(afm),
            year: afm.year || "",
            genre: list(afm.genre).map(clean).join(", ")
          });
        }
        if (rows.length) {
          rows.sort((a, b) => (b.score == null ? -1 : b.score) - (a.score == null ? -1 : a.score));
          const esc = s => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
          const body = rows.map(r =>
            `<tr><td class="mus-al-name"><a class="internal-link" data-href="${esc(r.path)}">${esc(r.name)}</a></td>` +
            `<td class="mus-al-score">${r.score == null ? "&mdash;" : r.score}</td>` +
            `<td class="mus-al-year">${esc(r.year)}</td><td class="mus-al-genre">${esc(r.genre)}</td></tr>`
          ).join("");
          notesEl.innerHTML = `<p class="mus-sec-title">Discography</p><table class="mus-albums"><thead><tr><th>Album</th><th>Score</th><th>Year</th><th>Genre</th></tr></thead><tbody>${body}</tbody></table>`;
          notesEl.querySelectorAll("a.internal-link").forEach(a => a.addEventListener("click", e => {
            e.preventDefault(); app.workspace.openLinkText(a.dataset.href, file.path, e.ctrlKey || e.metaKey);
          }));
          hasNotes = true;
        } else notesEl.remove();
      } catch (e) { console.error("[Musicians discography]", 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 matched to the centre image (bg stays neutral) ---
    if (centre) {
      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 d=ctx.getImageData(0,0,W,H).data;let wR=0,wG=0,wB=0,ws=0;for(let i=0;i<d.length;i+=4){if(d[i+3]<128)continue;const r=d[i],g=d[i+1],b=d[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);if(s<0.12)return;s=Math.min(Math.max(s,0.55),0.95);l=Math.min(Math.max(l,0.52),0.64);const[r,g,b]=hslToRgb(h,s,l);root.style.setProperty("--mus-accent",hex(r,g,b));}catch(e){}};
      const probe = new Image(); probe.crossOrigin = "anonymous";
      probe.onload = () => sample(probe); probe.src = centre;
    }
  } catch (e) { console.error("[Custom Views: Musicians]", e); }
})();

Templater Template

<%*
/**
 * TheAudioDB → Musician Frontmatter (Obsidian Templater)
 * No API key required. Pulls artist images (for the coverflow card), genre,
 * style, country, formed year and an English bio. The body embeds the artist's
 * albums table. Rendered by the Custom Views "Musicians" view.
 */
const ADB = "https://www.theaudiodb.com/api/v1/json/2/search.php?s=";
const today = tp.date.now("YYYY-MM-DD");
const fileTitle = tp.file.title;

// 1) Ask for artist name -------------------------------------------------
let query = await tp.system.prompt("Enter artist name (leave empty for file name):");
if (!query || !query.trim()) query = fileTitle.replace(/^Musician\s*-\s*/, "");
query = query.trim();

// 2) TheAudioDB lookup ---------------------------------------------------
let artist = null;
try {
  const r = await tp.web.request(`${ADB}${encodeURIComponent(query)}`);
  artist = (r && r.artists && r.artists[0]) || null;
} catch (e) { /* offline / not found — fall through to a minimal note */ }

const name = (artist && artist.strArtist) || query;

// up to 3 artist images: thumb (centre) + fanart (sides)
let images = [];
if (artist) {
  const pool = [artist.strArtistThumb, artist.strArtistFanart, artist.strArtistFanart2, artist.strArtistFanart3, artist.strArtistWideThumb];
  for (const u of pool) { if (u && !images.includes(u)) images.push(u); if (images.length === 3) break; }
}
const genre   = (artist && artist.strGenre)   || "";
const style   = (artist && artist.strStyle)   || "";
const country = (artist && artist.strCountry) || "";
const formed  = (artist && artist.intFormedYear) || "";
let   bio     = (artist && artist.strBiographyEN) || "";
bio = bio.replace(/\r?\n+/g, " ").replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 900);

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

// 4) Build YAML ----------------------------------------------------------
const L = [];
L.push("---");
L.push("connections:");
L.push("categories:");
L.push('  - "[[Musicians]]"');
L.push("type:");
L.push('  - "[[Musicians]]"');
if (images.length) { L.push("artist_images:"); images.forEach(u => L.push(`  - ${u}`)); } else L.push("artist_images: []");
L.push(genre   ? `genre: ${genre}`     : "genre:");
L.push(style   ? `style: ${style}`     : "style:");
L.push(country ? `country: ${country}` : "country:");
L.push(formed  ? `formed: ${formed}`   : "formed:");
L.push(`bio: "${bio}"`);
L.push(`created: ${today}`);
// resolve to this "Musician - Name" note (and the Albums.base#Artist table
// fills via list(artist).contains(this)) without changing the filename scheme.
const aliasSet = [];
[name, sanitized].forEach(n => { n = (n || "").trim(); if (n && !aliasSet.includes(n)) aliasSet.push(n); });
L.push("---");

// 5) Body — the artist's albums table -----------------------------------
const body = ["", "## Albums", "", "![[Albums.base#Artist]]", ""];
tR += L.join("\n") + "\n" + body.join("\n");
-%>
DECLASSIFIED

Field Assembly

This dossier takes two plugins to assemble.

  • Custom Views: the renderer. Make a view, build its rule in the menu to match categories contains Musicians (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