mirror of
https://github.com/kennethreitz/kennethreitz.org.git
synced 2026-06-05 22:50:17 +00:00
546 lines
17 KiB
HTML
546 lines
17 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block extra_head %}
|
|
<style>
|
|
/* Custom Tufte-inspired styles with Kenneth's brand colors */
|
|
|
|
/* Override some Tufte defaults with Kenneth's branding */
|
|
.tufte-content {
|
|
margin: 0 auto;
|
|
padding: 0;
|
|
width: 87.5%;
|
|
max-width: 1400px;
|
|
font-family: et-book, 'Crimson Text', Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
|
|
}
|
|
|
|
h1, h2, h3, h4, h5, h6 {
|
|
font-family: et-book, 'Crimson Text', Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
|
|
color: #4e3979;
|
|
}
|
|
|
|
h1 {
|
|
font-weight: 400;
|
|
margin-top: 2rem;
|
|
margin-bottom: 1.5rem;
|
|
font-size: 3.2rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
h2 {
|
|
font-style: italic;
|
|
font-weight: 400;
|
|
margin-top: 2.5rem;
|
|
margin-bottom: 0;
|
|
font-size: 2.2rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
h3 {
|
|
font-style: italic;
|
|
font-weight: 400;
|
|
font-size: 1.7rem;
|
|
margin-top: 2rem;
|
|
margin-bottom: 0;
|
|
line-height: 1;
|
|
}
|
|
|
|
/* Links with Kenneth's branding */
|
|
a:link, a:visited {
|
|
font-family: et-book, 'Crimson Text', Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
|
|
color: #4e3979;
|
|
text-decoration: none;
|
|
background: linear-gradient(#fff, #fff), linear-gradient(#fff, #fff), linear-gradient(#4e3979, #4e3979);
|
|
background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
|
|
background-repeat: no-repeat, no-repeat, repeat-x;
|
|
text-shadow: 0.03em 0 #fff, -0.03em 0 #fff, 0 0.03em #fff, 0 -0.03em #fff, 0.06em 0 #fff, -0.06em 0 #fff, 0.09em 0 #fff, -0.09em 0 #fff, 0.12em 0 #fff, -0.12em 0 #fff, 0.15em 0 #fff, -0.15em 0 #fff;
|
|
background-position: 0% 93%, 100% 93%, 0% 93%;
|
|
transition: color 0.2s ease;
|
|
}
|
|
|
|
a:hover {
|
|
color: #6f52b0;
|
|
}
|
|
|
|
/* Sidenotes and margin notes styling with Kenneth's color scheme */
|
|
.sidenote, .marginnote {
|
|
float: right;
|
|
clear: right;
|
|
margin-right: -60%;
|
|
width: 50%;
|
|
margin-top: 0;
|
|
margin-bottom: 0;
|
|
font-size: 1.1rem;
|
|
line-height: 1.3;
|
|
vertical-align: baseline;
|
|
position: relative;
|
|
color: #5c4394;
|
|
font-family: et-book, 'Crimson Text', Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
|
|
font-style: italic;
|
|
}
|
|
|
|
.sidenote-number {
|
|
color: #4e3979;
|
|
}
|
|
|
|
/* Blockquotes with Kenneth's branding */
|
|
blockquote {
|
|
font-family: et-book, 'Crimson Text', Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
|
|
font-size: 1.4rem;
|
|
margin: 2rem 0;
|
|
padding-left: 2rem;
|
|
border-left: 3px solid #9b86d3;
|
|
font-style: italic;
|
|
line-height: 1.75;
|
|
}
|
|
|
|
/* Figure and caption styling */
|
|
figure {
|
|
padding: 0;
|
|
border: 0;
|
|
font-size: 100%;
|
|
font: inherit;
|
|
vertical-align: baseline;
|
|
max-width: 85%;
|
|
-webkit-margin-start: 0;
|
|
-webkit-margin-end: 0;
|
|
margin: 0 0 3em 0;
|
|
}
|
|
|
|
/* Figure and caption styling */
|
|
figcaption {
|
|
float: right;
|
|
clear: right;
|
|
margin-top: 0;
|
|
margin-bottom: 0;
|
|
font-size: 1.1rem;
|
|
line-height: 1.6;
|
|
vertical-align: baseline;
|
|
position: relative;
|
|
max-width: 40%;
|
|
color: #5c4394;
|
|
font-style: italic;
|
|
font-family: et-book, 'Crimson Text', Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
|
|
}
|
|
|
|
/* Code styling with Kenneth's branding */
|
|
code {
|
|
font-family: 'JetBrains Mono', Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
font-size: 0.9rem;
|
|
line-height: 1.6;
|
|
background-color: #e3e0f4;
|
|
padding: 0.2rem 0.4rem;
|
|
border-radius: 3px;
|
|
color: #4e3979;
|
|
}
|
|
|
|
pre {
|
|
padding: 1.5rem;
|
|
margin: 2rem 0;
|
|
background-color: #faf9fd;
|
|
border: 1px solid #d0c8ec;
|
|
border-radius: 5px;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
pre code {
|
|
background-color: transparent;
|
|
padding: 0;
|
|
}
|
|
|
|
/* Full width figures/tables */
|
|
.fullwidth {
|
|
max-width: 100%;
|
|
clear: both;
|
|
}
|
|
|
|
/* Table styling */
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
border-spacing: 0;
|
|
margin: 2rem 0;
|
|
font-size: 1.1rem;
|
|
font-family: et-book, 'Crimson Text', Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
|
|
}
|
|
|
|
th {
|
|
padding: 0.75rem 1rem;
|
|
font-weight: 600;
|
|
text-align: left;
|
|
border-bottom: 2px solid #4e3979;
|
|
color: #4e3979;
|
|
}
|
|
|
|
td {
|
|
padding: 0.75rem 1rem;
|
|
border-bottom: 1px solid #d0c8ec;
|
|
}
|
|
|
|
tr:nth-child(even) {
|
|
background-color: #f8f7fd;
|
|
}
|
|
|
|
/* Copy button for code blocks */
|
|
.code-block-container {
|
|
position: relative;
|
|
}
|
|
|
|
.copy-button {
|
|
position: absolute;
|
|
top: 0.75rem;
|
|
right: 0.75rem;
|
|
background: #4e3979;
|
|
color: white;
|
|
border: none;
|
|
padding: 0.375rem 0.75rem;
|
|
border-radius: 0.375rem;
|
|
font-size: 0.75rem;
|
|
cursor: pointer;
|
|
opacity: 0;
|
|
transition: opacity 0.2s ease;
|
|
font-family: 'Inter', sans-serif;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.code-block-container:hover .copy-button {
|
|
opacity: 1;
|
|
}
|
|
|
|
.copy-button:hover {
|
|
background: #5c4394;
|
|
}
|
|
|
|
/* Adjustments for mobile responsive design */
|
|
@media (max-width: 760px) {
|
|
body {
|
|
width: 84%;
|
|
padding-left: 8%;
|
|
padding-right: 8%;
|
|
font-family: et-book, 'Crimson Text', Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
|
|
}
|
|
|
|
.tufte-content {
|
|
width: 100%;
|
|
}
|
|
|
|
hr, section > p, section > footer, section > table {
|
|
width: 100%;
|
|
}
|
|
|
|
pre {
|
|
width: 97%;
|
|
}
|
|
|
|
.sidenote, .marginnote {
|
|
display: none;
|
|
}
|
|
|
|
.margin-toggle:checked + .sidenote,
|
|
.margin-toggle:checked + .marginnote {
|
|
display: block;
|
|
float: left;
|
|
left: 1rem;
|
|
clear: both;
|
|
width: 95%;
|
|
margin: 1rem 2.5%;
|
|
vertical-align: baseline;
|
|
position: relative;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<article class="post">
|
|
<!-- Post Header -->
|
|
<header class="mb-12 pb-8 border-b-2 border-primary-200">
|
|
<h1 class="text-5xl font-bold text-gray-900 mb-6 leading-tight tracking-tight page-title">
|
|
{{ title }}
|
|
</h1>
|
|
|
|
{% if metadata %}
|
|
<div class="flex flex-wrap gap-6 text-sm text-gray-600">
|
|
{% if metadata.author %}
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-primary-600">👤</span>
|
|
<span class="font-medium">{{ metadata.author[0] }}</span>
|
|
</div>
|
|
{% endif %}
|
|
{% if metadata.date %}
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-primary-600">📅</span>
|
|
<span>{{ metadata.date[0] }}</span>
|
|
</div>
|
|
{% endif %}
|
|
{% if metadata.tags %}
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-primary-600">🏷️</span>
|
|
<span>{{ metadata.tags | join(', ') }}</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</header>
|
|
|
|
<!-- Post Content with Tufte Typography -->
|
|
<div class="et-book">
|
|
<div class="font-serif content-section">
|
|
{{ content | safe }}
|
|
</div>
|
|
<style>
|
|
/* Style for page title to distinguish from content headers */
|
|
.page-title {
|
|
color: #4e3979 !important;
|
|
font-family: 'Inter', system-ui, sans-serif !important;
|
|
border-bottom: none !important;
|
|
}
|
|
|
|
/* Ensure proper bullet point display in Markdown content */
|
|
.content-section h1,
|
|
.content-section h2,
|
|
.content-section h3,
|
|
.content-section h4,
|
|
.content-section h5,
|
|
.content-section h6 {
|
|
color: #5c4394 !important;
|
|
border-bottom: none !important;
|
|
font-family: et-book, 'Crimson Text', Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif !important;
|
|
}
|
|
|
|
.content-section h1 {
|
|
font-size: 2.5rem !important;
|
|
margin-top: 2.5rem !important;
|
|
}
|
|
|
|
.content-section h2 {
|
|
font-size: 2.0rem !important;
|
|
margin-top: 2.2rem !important;
|
|
}
|
|
|
|
.content-section h3 {
|
|
font-size: 1.6rem !important;
|
|
margin-top: 1.8rem !important;
|
|
}
|
|
|
|
.content-section ul {
|
|
list-style-type: disc !important;
|
|
padding-left: 2.5rem !important;
|
|
margin: 1rem 0 !important;
|
|
}
|
|
|
|
.content-section ol {
|
|
list-style-type: decimal !important;
|
|
padding-left: 2.5rem !important;
|
|
margin: 1rem 0 !important;
|
|
}
|
|
|
|
.content-section ul ul {
|
|
list-style-type: circle !important;
|
|
}
|
|
|
|
.content-section ul ol {
|
|
list-style-type: lower-alpha !important;
|
|
}
|
|
|
|
.content-section ol ul {
|
|
list-style-type: circle !important;
|
|
}
|
|
|
|
.content-section ol ol {
|
|
list-style-type: lower-alpha !important;
|
|
}
|
|
|
|
.content-section li {
|
|
display: list-item !important;
|
|
margin-bottom: 0.5rem !important;
|
|
}
|
|
|
|
@media (max-width: 760px) {
|
|
.content-section ul,
|
|
.content-section ol {
|
|
width: 100% !important;
|
|
padding-left: 1.5rem !important;
|
|
}
|
|
}
|
|
</style>
|
|
</div>
|
|
|
|
<!-- Post Navigation -->
|
|
<nav class="mt-16 pt-8 border-t border-gray-200 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
|
{% if breadcrumbs %}
|
|
{% set parent_path = breadcrumbs[-1].url if breadcrumbs else '/' %}
|
|
{% else %}
|
|
{% set parent_path = '/' %}
|
|
{% endif %}
|
|
|
|
<a href="{{ parent_path }}"
|
|
class="inline-flex items-center gap-3 px-6 py-3 text-primary-700 border-2 border-primary-600 rounded-lg hover:bg-primary-600 hover:text-white transition-all duration-200 font-medium group">
|
|
<span class="transition-transform group-hover:-translate-x-1">←</span>
|
|
<span>Back to Directory</span>
|
|
</a>
|
|
|
|
<div class="text-sm text-gray-500 font-medium">
|
|
Markdown Document
|
|
</div>
|
|
</nav>
|
|
</article>
|
|
{% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Smooth scrolling for anchor links
|
|
const anchorLinks = document.querySelectorAll('.content-section a[href^="#"]');
|
|
anchorLinks.forEach(link => {
|
|
link.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const target = document.querySelector(this.getAttribute('href'));
|
|
if (target) {
|
|
target.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start'
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add copy button to code blocks
|
|
const codeBlocks = document.querySelectorAll('.content-section pre code');
|
|
codeBlocks.forEach(block => {
|
|
const pre = block.parentElement;
|
|
|
|
// Wrap pre in container for positioning
|
|
const container = document.createElement('div');
|
|
container.className = 'code-block-container';
|
|
pre.parentNode.insertBefore(container, pre);
|
|
container.appendChild(pre);
|
|
|
|
// Create copy button
|
|
const button = document.createElement('button');
|
|
button.textContent = 'Copy';
|
|
button.className = 'copy-button';
|
|
|
|
container.appendChild(button);
|
|
|
|
button.addEventListener('click', async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(block.textContent);
|
|
button.textContent = 'Copied!';
|
|
button.style.background = '#10b981';
|
|
setTimeout(() => {
|
|
button.textContent = 'Copy';
|
|
button.style.background = '#4e3979';
|
|
}, 2000);
|
|
} catch (err) {
|
|
console.error('Failed to copy: ', err);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add reading progress indicator
|
|
const progressBar = document.createElement('div');
|
|
progressBar.style.cssText = `
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 0%;
|
|
height: 3px;
|
|
background: linear-gradient(90deg, #4e3979, #6f52b0);
|
|
z-index: 9999;
|
|
transition: width 0.2s ease;
|
|
`;
|
|
document.body.appendChild(progressBar);
|
|
|
|
window.addEventListener('scroll', () => {
|
|
const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
|
|
const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
|
const scrolled = (winScroll / height) * 100;
|
|
progressBar.style.width = scrolled + '%';
|
|
});
|
|
|
|
// Enhance image viewing
|
|
const images = document.querySelectorAll('.content-section img');
|
|
images.forEach(img => {
|
|
// Create figure and figcaption for Tufte-style
|
|
if (!img.parentElement.matches('figure')) {
|
|
const figure = document.createElement('figure');
|
|
img.parentNode.insertBefore(figure, img);
|
|
figure.appendChild(img);
|
|
|
|
// Add caption if alt text exists
|
|
if (img.alt && !img.alt.startsWith('_')) {
|
|
const figcaption = document.createElement('figcaption');
|
|
figcaption.textContent = img.alt;
|
|
figure.appendChild(figcaption);
|
|
}
|
|
}
|
|
|
|
img.style.cursor = 'pointer';
|
|
img.addEventListener('click', () => {
|
|
// Simple lightbox effect
|
|
const overlay = document.createElement('div');
|
|
overlay.style.cssText = `
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.9);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 10000;
|
|
cursor: pointer;
|
|
`;
|
|
|
|
const enlargedImg = img.cloneNode();
|
|
enlargedImg.style.cssText = `
|
|
max-width: 90%;
|
|
max-height: 90%;
|
|
object-fit: contain;
|
|
border-radius: 8px;
|
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
`;
|
|
|
|
overlay.appendChild(enlargedImg);
|
|
document.body.appendChild(overlay);
|
|
|
|
overlay.addEventListener('click', () => {
|
|
document.body.removeChild(overlay);
|
|
});
|
|
});
|
|
});
|
|
|
|
// Transform blockquotes with attribution to Tufte style
|
|
document.querySelectorAll('.content-section blockquote').forEach(blockquote => {
|
|
const lastP = blockquote.querySelector('p:last-child');
|
|
if (lastP && lastP.textContent.startsWith('—')) {
|
|
lastP.classList.add('attribution');
|
|
lastP.style.textAlign = 'right';
|
|
lastP.style.marginTop = '0.5rem';
|
|
}
|
|
});
|
|
|
|
// Add margin toggle buttons for sidenotes in mobile view
|
|
document.querySelectorAll('.content-section .sidenote, .content-section .marginnote').forEach((note, i) => {
|
|
const id = 'sidenote-' + i;
|
|
|
|
// Create the toggle
|
|
const toggle = document.createElement('label');
|
|
toggle.setAttribute('for', id);
|
|
toggle.className = 'margin-toggle sidenote-number';
|
|
toggle.textContent = (i + 1);
|
|
|
|
// Create the checkbox
|
|
const checkbox = document.createElement('input');
|
|
checkbox.type = 'checkbox';
|
|
checkbox.id = id;
|
|
checkbox.className = 'margin-toggle';
|
|
|
|
// Insert them before the note
|
|
note.parentNode.insertBefore(toggle, note);
|
|
note.parentNode.insertBefore(checkbox, note);
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %} |