Files
kennethreitz ce93f429a2 Add Knowledge: the vault's reference shelf, crosslinked across the site
Publish the Obsidian vault's Knowledge folder as /knowledge (881 notes,
67 folders), with a Knowledge item in the nav and footer.

- scripts/import_knowledge.py regenerates data/knowledge from the vault:
  slugged paths, folder notes as index pages, wikilinks resolved to site
  links (or plain text), callouts, highlights, and Dataview tables
  rendered statically. Personal notes are withheld via an exclusion
  list plus a privacy guard.
- tuftecms/core/knowledge.py builds a concept index from note titles and
  aliases. First mentions on ordinary pages link to their Knowledge note
  (capped, never in headings, code, links, or sidenotes); Knowledge pages
  list related notes and where they're mentioned across the site; other
  pages get a Related Knowledge block.
- Knowledge URLs join sitemap.xml (so the sweep test covers them), never
  displace existing legacy slugs, list alphabetically, and mermaid
  diagrams render client-side only on pages that have them.
2026-10-18 20:08:46 -04:00

778 lines
23 KiB
HTML

{% extends "base.html" %}
{% block og_title %}{{ title }}{% endblock %}
{% block og_description %}{% if description %}{{ description[:160] }}{% else %}{{ content | striptags | truncate(160) }}{% endif %}{% endblock %}
{% block og_type %}article{% endblock %}
{% block og_url %}https://kennethreitz.org{{ request.path }}{% endblock %}
{% block og_image %}https://kennethreitz.org/og-image{{ request.path }}.png{% endblock %}
{% block twitter_title %}{{ title }}{% endblock %}
{% block twitter_description %}{% if description %}{{ description[:160] }}{% else %}{{ content | striptags | truncate(160) }}{% endif %}{% endblock %}
{% block twitter_image %}https://kennethreitz.org/og-image{{ request.path }}.png{% endblock %}
{% block og_image_alt %}{{ title }} - An article by Kenneth Reitz{% endblock %}
{% block extra_meta %}{% if published_iso %}<meta property="article:published_time" content="{{ published_iso }}" />
{% endif %}{% if modified_iso %}<meta property="article:modified_time" content="{{ modified_iso }}" />{% endif %}{% endblock %}
{% block description %}{% if description %}{{ description }}{% else %}{{ content | striptags | truncate(200) }}{% endif %}{% endblock %}
{% block schema_type %}Article{% endblock %}
{% block schema_name %}{{ title }}{% endblock %}
{% block schema_url %}{{ current_path }}{% endblock %}
{% block schema_description %}{% if metadata.description %}{{ metadata.description }}{% else %}{{ title }} - An essay by Kenneth Reitz{% endif %}{% endblock %}
{% block schema_extra %},
"headline": "{{ title }}",
"articleSection": "{% if 'artificial-intelligence' in current_path %}AI & Consciousness{% elif 'essays' in current_path %}Essays{% elif 'poetry' in current_path %}Poetry{% elif 'software' in current_path %}Software{% elif 'music' in current_path %}Music{% else %}Writing{% endif %}",
"wordCount": {{ (content | length / 5) | int }},
{% if metadata and metadata.date %}
"datePublished": "{{ metadata.date[0] if metadata.date is iterable and metadata.date is not string else metadata.date }}",
"dateModified": "{{ metadata.date[0] if metadata.date is iterable and metadata.date is not string else metadata.date }}",
{% endif %}
"keywords": {% if 'artificial-intelligence' in current_path %}["artificial intelligence", "consciousness", "AI philosophy", "human-AI collaboration", "machine learning"]{% elif 'mental-health' in current_path %}["mental health", "bipolar disorder", "neurodivergent", "tech industry wellness", "psychology"]{% elif 'poetry' in current_path %}["poetry", "creative writing", "verse", "contemplation", "language"]{% elif 'software' in current_path %}["software development", "Python", "open source", "programming", "API design"]{% elif 'music' in current_path %}["music", "audio", "creative arts", "sound design"]{% elif 'essays' in current_path %}["technology", "software development", "Python", "philosophy", "programming"]{% else %}["writing", "thoughts", "contemplation", "technology"]{% endif %},
"inLanguage": "en-US",
"isAccessibleForFree": true,
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://kennethreitz.org{{ current_path }}"
},
"image": {
"@type": "ImageObject",
"url": "https://kennethreitz.org/og-image{{ current_path }}.png",
"width": 1200,
"height": 630
}{% endblock %}
{% block extra_head %}
<style>
.post-shell {
padding: 0 !important;
}
.post-header {
margin-bottom: 2.75rem;
padding-bottom: 1.4rem;
border-bottom: 1px solid var(--line-soft);
width: 70%;
}
.post-header--no-breadcrumb {
margin-top: 1.35rem;
}
/* Post header: Tufte-style, colored entirely by the design tokens. */
.post-title-container {
display: flex;
align-items: flex-start;
gap: 1.1rem;
margin-bottom: 0.5rem;
width: 100%;
}
.post-title-text {
flex: 1;
display: flex;
flex-direction: column;
}
.post-title {
font-size: 3.2rem;
font-style: italic;
font-weight: 400;
line-height: 1.05;
margin: 0;
color: var(--ink);
text-rendering: optimizeLegibility;
text-wrap: balance;
}
.post-title-icon {
width: 48px;
height: 48px;
flex-shrink: 0;
margin-left: -4rem;
margin-top: 0.45rem;
}
.post-description {
color: var(--ink-muted);
font-size: 1.25rem;
font-style: italic;
line-height: 1.45;
margin: 1rem 0 0;
width: 92% !important;
text-align: left !important;
}
/* A fleuron closes each dated essay. */
.dated-post::after {
content: "❦";
display: block;
width: 65%;
text-align: center;
margin-top: 3rem;
color: var(--ink-faint);
font-size: 1.3rem;
line-height: 1;
}
/* Chronological neighbors */
.post-nav {
display: flex;
justify-content: space-between;
gap: 2rem;
width: 65%;
margin-top: 3rem;
padding-top: 1.5rem;
border-top: 1px solid var(--line-soft);
}
.post-nav-item {
display: flex;
flex-direction: column;
gap: 0.35rem;
max-width: 46%;
}
.post-nav-newer {
text-align: right;
margin-left: auto;
}
.post-nav-label {
font-variant: small-caps;
letter-spacing: 0.06em;
font-size: 0.85rem;
color: var(--ink-faint);
}
.post-nav a {
font-size: 1.05rem;
line-height: 1.25;
text-decoration-thickness: 0.05em;
}
.post-shell .related-essays {
width: 65%;
margin-top: 3.75rem;
padding-top: 1.5rem;
border-top: 1px solid var(--line-soft);
}
.post-shell .related-essays h2 {
color: var(--ink-muted);
font-size: 1.15rem !important;
font-style: normal;
font-variant: small-caps;
font-weight: 400;
letter-spacing: 0.06em;
margin: 0 0 1rem !important;
}
.post-shell .related-essays li {
gap: 0.7rem;
margin: 0.65rem 0;
line-height: 1.25;
}
.post-shell .related-essays a {
text-decoration-thickness: 0.05em;
}
.post-subtitle {
font-size: 0.98rem;
color: var(--ink-muted);
margin-top: 1.25rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
font-family: et-book-roman-old-style, et-book, Palatino, "Palatino Linotype", "Book Antiqua", Georgia, serif;
}
.post-meta-primary,
.post-actions {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.55rem;
}
.post-date {
color: var(--ink-muted);
font-variant: small-caps;
letter-spacing: 0.08em;
}
/* Quiet badges: date companions. */
.reading-time-badge,
.themes-badge,
.pdf-badge,
.markdown-badge {
font-variant: normal;
font-size: 0.85rem;
letter-spacing: normal;
font-weight: 400;
font-family: et-book, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
color: var(--ink-muted);
background: color-mix(in srgb, var(--surface) 82%, transparent);
border: 1px solid var(--line-soft);
border-radius: 999px;
padding: 0.18rem 0.68rem;
text-decoration: none;
display: inline-flex;
align-items: center;
min-height: 1.5rem;
transition: color 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
}
.themes-badge {
cursor: pointer;
}
.themes-badge:hover,
.pdf-badge:hover,
.markdown-badge:hover {
color: var(--accent);
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 7%, transparent);
}
.pdf-badge,
.markdown-badge {
margin-left: 0;
}
.formats-label {
font-variant: small-caps;
font-size: 0.85rem;
color: var(--ink-muted);
margin-left: 0.5rem;
letter-spacing: 0.05em;
font-family: et-book, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
}
.themes-dropdown {
position: relative;
display: inline-block;
}
.themes-dropdown-content {
display: none;
position: absolute;
background-color: var(--surface);
min-width: 220px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border: 1px solid var(--line);
border-radius: 6px;
z-index: 9999;
margin-top: 0.25rem;
top: 100%;
left: 0;
}
.themes-dropdown-content a {
color: var(--ink-muted);
padding: 0.5rem 1rem;
text-decoration: none;
display: block;
transition: background 0.2s ease;
font-variant: normal;
letter-spacing: normal;
font-size: 0.9rem;
}
.themes-dropdown-content a:hover {
background-color: var(--line-soft);
color: var(--ink);
}
@media (max-width: 768px) {
.post-subtitle {
flex-wrap: wrap;
gap: 0.75rem;
}
.reading-time-badge,
.themes-badge,
.pdf-badge,
.markdown-badge {
font-size: 0.8rem;
padding: 0.1rem 0.55rem;
}
.pdf-badge,
.markdown-badge {
display: none;
}
.themes-dropdown-content {
min-width: 150px;
font-size: 0.85rem;
}
}
.themes-dropdown-content.show {
display: block;
}
.sidenote-highlighted {
background: color-mix(in srgb, var(--accent) 11%, transparent);
border-left: 2px solid color-mix(in srgb, var(--accent) 50%, var(--line));
color: var(--ink);
padding-left: 0.75rem;
transition: background-color 0.2s ease, border-color 0.2s ease;
}
/* Reading body */
.post-body p,
.post-body li,
.post-body blockquote {
line-height: 1.55;
}
.post-body p,
.post-shell p:not(.sidenote):not(.marginnote) {
text-align: justify;
text-justify: inter-word;
hyphens: auto;
-webkit-hyphens: auto;
-ms-hyphens: auto;
word-break: normal;
overflow-wrap: break-word;
margin-left: 0 !important;
margin-right: auto !important;
}
.post-body > p:first-child {
margin-top: 0;
}
.post-body > p:first-child:not(:has(img)) {
font-size: 1.45rem;
line-height: 1.48;
}
.post-body h2,
.post-body h3,
.post-body h4 {
text-wrap: balance;
}
/* Asterism HR styling */
.post-shell hr {
width: 65%;
max-width: 65%;
margin: 4rem 0;
}
/* Directory listing - appears at top when items exist */
.directory-listing {
margin-bottom: 3rem;
padding-bottom: 2rem;
border-bottom: 1px solid var(--line-soft);
width: 65%;
}
.directory-listing h2,
.directory-about h2 {
font-size: 1.1rem;
margin-bottom: 1.25rem;
font-weight: 400;
font-variant: small-caps;
letter-spacing: 0.05em;
color: var(--ink-muted);
}
.items-list {
list-style: none;
padding-left: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 0.75rem 1.5rem;
}
.items-list li {
margin-bottom: 0;
}
.items-list a {
display: flex;
align-items: center;
gap: 0.6rem;
text-decoration: none;
color: var(--ink-muted);
transition: color 0.2s ease;
padding: 0.4rem 0;
}
.items-list a:hover {
color: var(--ink);
}
.item-icon {
width: 22px;
height: 22px;
flex-shrink: 0;
}
.item-name {
line-height: 1.3;
font-size: 0.95rem;
}
/* About section that follows directory listing */
.directory-about {
margin-top: 0;
}
/* Parent navigation */
.parent-navigation {
margin-top: 4rem;
padding-top: 2rem;
border-top: 1px solid var(--line-soft);
width: 65%;
}
.parent-link {
display: flex;
align-items: center;
gap: 0.75rem;
text-decoration: none;
color: var(--ink-muted);
font-size: 0.95rem;
font-style: italic;
transition: color 0.2s ease;
}
.parent-link:hover {
color: var(--ink);
}
.parent-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
}
.parent-text {
line-height: 1.2;
}
@media (max-width: 760px) {
.post-shell {
padding: 0 !important;
}
.post-header {
width: 100%;
margin-bottom: 2.2rem;
padding-bottom: 1.15rem;
}
.post-title {
font-size: 2.25rem;
width: 100%;
}
.post-subtitle {
align-items: flex-start;
font-size: 0.92rem;
margin-top: 1rem;
}
.post-title-container {
display: block;
width: 100%;
}
.post-title-icon {
margin-left: 0;
margin-top: 0;
margin-bottom: 0.85rem;
width: 30px;
height: 30px;
}
.post-description {
font-size: 1.08rem;
width: 100% !important;
}
.post-shell hr {
width: 100%;
max-width: 100%;
}
.post-body p,
.post-body li,
.post-shell p:not(.sidenote):not(.marginnote) {
width: 100% !important;
max-width: 100% !important;
text-align: left !important;
hyphens: none;
-webkit-hyphens: none;
}
.post-body > p:first-child:not(:has(img)) {
font-size: 1.22rem;
line-height: 1.5;
}
.directory-listing {
width: 100%;
margin-bottom: 2rem;
padding-bottom: 1.5rem;
}
.directory-listing h2,
.directory-about h2 {
font-size: 1rem;
}
.items-list {
grid-template-columns: 1fr;
}
.parent-navigation {
width: 100%;
margin-top: 3rem;
padding-top: 1.5rem;
}
.dated-post::after,
.post-nav,
.post-shell .related-essays {
width: 100%;
}
.post-nav {
flex-direction: column;
gap: 1rem;
}
.post-nav-newer {
text-align: left;
margin-left: 0;
}
.post-nav-item {
max-width: 100%;
}
.parent-link {
font-size: 0.9rem;
}
.parent-icon {
width: 18px;
height: 18px;
}
}
</style>
<script>
// When arriving at a page with a hash pointing to a sidenote, auto-open it
document.addEventListener('DOMContentLoaded', function() {
if (window.location.hash) {
const sidenoteId = window.location.hash.substring(1);
const checkbox = document.getElementById(sidenoteId);
if (checkbox && checkbox.type === 'checkbox') {
// Check the checkbox to reveal the sidenote
checkbox.checked = true;
// Find the label or parent paragraph to scroll to
const label = document.querySelector(`label[for="${sidenoteId}"]`);
const scrollTarget = label ? label : checkbox.parentElement;
// Scroll to the sidenote after a brief delay
setTimeout(() => {
if (scrollTarget) {
scrollTarget.scrollIntoView({behavior: 'smooth', block: 'center'});
}
// Highlight the sidenote permanently
const sidenote = checkbox.nextElementSibling;
if (sidenote && sidenote.classList.contains('sidenote')) {
sidenote.classList.add('sidenote-highlighted');
}
}, 100);
}
}
// Themes dropdown
const themesButton = document.querySelector('.themes-badge');
const themesDropdown = document.querySelector('.themes-dropdown-content');
if (themesButton && themesDropdown) {
themesButton.addEventListener('click', function(e) {
e.stopPropagation();
const isOpen = themesDropdown.classList.toggle('show');
themesButton.setAttribute('aria-expanded', String(isOpen));
});
// Close dropdown when clicking outside
document.addEventListener('click', function() {
themesDropdown.classList.remove('show');
themesButton.setAttribute('aria-expanded', 'false');
});
}
});
</script>
{% endblock %}
{% block content %}
{% set post_path_parts = current_path.strip('/').split('/') if current_path else [] %}
{% set has_visible_breadcrumbs = post_path_parts|length > 1 %}
<article class="post-shell">
<header class="post-header{% if not has_visible_breadcrumbs %} post-header--no-breadcrumb{% endif %}">
<div class="post-title-container{% if unique_icon %} has-icon{% endif %}">
{% if unique_icon %}
<img src="{{ unique_icon }}" alt="Icon for {{ title }}" class="post-title-icon">
{% endif %}
<div class="post-title-text">
<h1 class="post-title">{{ title }}</h1>
{% if description %}
<p class="post-description">{{ description }}</p>
{% endif %}
</div>
</div>
{% if metadata and metadata.date %}
{% set post_date = metadata.date if metadata.date is string else metadata.date[0] %}
<div class="post-subtitle" aria-label="Article metadata">
<div class="post-meta-primary">
<time class="post-date" datetime="{{ published_iso or post_date }}">{{ post_date }}</time>
{% if reading_time %}
<span class="reading-time-badge">{{ reading_time }} min read</span>
{% endif %}
</div>
<div class="post-actions">
{% if article_themes and article_themes|length > 0 %}
<div class="themes-dropdown">
<button class="themes-badge" type="button" aria-expanded="false" aria-haspopup="true">{{ article_themes|length }} theme{{ 's' if article_themes|length != 1 else '' }}</button>
<div class="themes-dropdown-content">
{% for theme in article_themes %}
<a href="/archive/themes#{{ theme.name|replace(' ', '-') }}">{{ theme.name }}</a>
{% endfor %}
</div>
</div>
{% endif %}
{% if pdf_available %}
<a href="{{ request.path }}.pdf" class="pdf-badge" title="Download as PDF">PDF</a>
{% endif %}
<a href="{{ request.path }}.md" class="markdown-badge" title="View Markdown source">Markdown</a>
</div>
</div>
{% endif %}
</header>
{% if items %}
<div class="directory-listing">
<h2>In This Section</h2>
<ul class="items-list">
{% for item in items %}
<li class="item {% if item.is_dir %}folder{% else %}file{% endif %}">
<a href="{{ item.url_path }}">
{% if item.unique_icon %}
<img src="{{ item.unique_icon }}" alt="{{ item.name }}" class="item-icon">
{% endif %}
<span class="item-name">{{ item.display_name or item.name }}</span>
</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if items and content and content|striptags|trim %}
<div class="directory-about">
<h2>About</h2>
</div>
{% endif %}
<section class="post-body{% if metadata and metadata.date %} dated-post{% endif %}">
{{ content | safe }}
</section>
{% if knowledge_related or knowledge_mentions %}
<section class="knowledge-connections">
{% if knowledge_related %}
<h2>Related Knowledge</h2>
<ul class="knowledge-list">
{% for k in knowledge_related %}
<li><a href="{{ k.url }}">{{ k.title }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% if knowledge_mentions %}
<h2>Mentioned Across the Site</h2>
<ul class="knowledge-mentions">
{% for p in knowledge_mentions %}
<li><a href="{{ p.url }}">{{ p.title }}</a>{% if p.date %}<span class="recent-post-date">{{ p.date[:4] }}</span>{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</section>
{% endif %}
{% if related_knowledge %}
<section class="knowledge-connections related-knowledge">
<h2>Related Knowledge</h2>
<ul class="knowledge-list">
{% for k in related_knowledge %}
<li><a href="{{ k.url }}">{{ k.title }}</a></li>
{% endfor %}
</ul>
</section>
{% endif %}
{% if related_posts %}
<section class="related-essays">
<h2>Keep Reading</h2>
<ul>
{% for p in related_posts %}
<li>
{% if p.unique_icon %}<img src="{{ p.unique_icon }}" alt="" class="related-icon">{% endif %}
<a href="{{ p.url }}">{{ p.title }}</a>{% if p.date %}<span class="recent-post-date">{{ p.date[:4] }}</span>{% endif %}
</li>
{% endfor %}
</ul>
</section>
{% endif %}
{% if newer_post or older_post %}
<nav class="post-nav" aria-label="Chronological navigation">
<div class="post-nav-item post-nav-older">
{% if older_post %}
<span class="post-nav-label">← Older</span>
<a href="{{ older_post.url }}">{{ older_post.title }}</a>
{% endif %}
</div>
<div class="post-nav-item post-nav-newer">
{% if newer_post %}
<span class="post-nav-label">Newer →</span>
<a href="{{ newer_post.url }}">{{ newer_post.title }}</a>
{% endif %}
</div>
</nav>
{% endif %}
<!-- Comments disabled for now -->
{% if parent_directory %}
<div class="parent-navigation">
<a href="{{ parent_directory.url }}" class="parent-link">
{% if parent_directory.icon %}
<img src="{{ parent_directory.icon }}" alt="Parent directory icon" class="parent-icon">
{% endif %}
<span class="parent-text">← {{ parent_directory.display_name }}</span>
</a>
</div>
{% endif %}
</article>
{% endblock %}