mirror of
https://github.com/kennethreitz/kennethreitz.org.git
synced 2026-06-05 22:50:17 +00:00
1179 lines
41 KiB
HTML
1179 lines
41 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>{% block title %}{{ title }} - Kenneth Reitz{% endblock %}</title>
|
||
|
||
<!-- SEO and Meta -->
|
||
<meta name="description" content="{% block description %}Kenneth Reitz - Creator of Requests and Certifi, trusted by millions of developers worldwide. Thoughts on technology, philosophy, and building software for humans.{% endblock %}" />
|
||
<meta name="author" content="Kenneth Reitz" />
|
||
|
||
<!-- OpenGraph -->
|
||
<meta property="og:title" content="{% block og_title %}{{ title }} - Kenneth Reitz{% endblock %}" />
|
||
<meta property="og:description" content="{% block og_description %}Creator of Requests and Certifi - libraries trusted by millions of developers worldwide{% endblock %}" />
|
||
<meta property="og:type" content="{% block og_type %}website{% endblock %}" />
|
||
<meta property="og:url" content="{% block og_url %}https://kennethreitz.org{% endblock %}" />
|
||
<meta property="og:image" content="{% block og_image %}https://kennethreitz.org/static/images/social-card.jpg{% endblock %}" />
|
||
<meta property="og:image:width" content="1200" />
|
||
<meta property="og:image:height" content="630" />
|
||
<meta property="og:site_name" content="Kenneth Reitz" />
|
||
|
||
<!-- Twitter Card -->
|
||
<meta name="twitter:card" content="{% block twitter_card %}summary_large_image{% endblock %}" />
|
||
<meta name="twitter:creator" content="@kennethreitz42" />
|
||
<meta name="twitter:title" content="{% block twitter_title %}{{ title }} - Kenneth Reitz{% endblock %}" />
|
||
<meta name="twitter:description" content="{% block twitter_description %}Creator of Requests and Certifi - libraries trusted by millions of developers worldwide{% endblock %}" />
|
||
<meta name="twitter:image" content="{% block twitter_image %}https://kennethreitz.org/static/images/social-card.jpg{% endblock %}" />
|
||
|
||
<!-- RSS Feed -->
|
||
<link rel="alternate" type="application/rss+xml" title="Kenneth Reitz - Essays & AI Writings" href="/feed.xml" />
|
||
|
||
<!-- Tufte CSS -->
|
||
<link rel="stylesheet" href="/static/tufte/tufte.css" />
|
||
|
||
<style>
|
||
/* Fix for ET Book font bold-italic rendering issues */
|
||
/* ET Book doesn't have a true bold-italic variant, so we disable faux bold */
|
||
strong em, em strong, b i, i b,
|
||
.subtitle strong em, .subtitle em strong,
|
||
.subtitle b i, .subtitle i b,
|
||
nav strong em, nav em strong,
|
||
h1 strong em, h1 em strong,
|
||
h2 strong em, h2 em strong,
|
||
h3 strong em, h3 em strong {
|
||
font-weight: normal !important;
|
||
font-style: italic !important;
|
||
}
|
||
|
||
/* For better readability, you could also use the semi-bold variant */
|
||
/* which ET Book does support, though still no bold-italic */
|
||
.subtitle em, .subtitle i,
|
||
nav em, nav i {
|
||
font-weight: 500; /* semi-bold instead of bold for italics */
|
||
}
|
||
|
||
/* Adjust content width for better sidenote spacing */
|
||
section > p,
|
||
section > footer,
|
||
section > table,
|
||
section > blockquote,
|
||
p,
|
||
blockquote,
|
||
.post-content > p,
|
||
.post-content > blockquote,
|
||
.post-content > ul,
|
||
.post-content > ol {
|
||
width: 65% !important;
|
||
}
|
||
|
||
section > dl,
|
||
section > ol,
|
||
section > ul,
|
||
.post-content > dl {
|
||
width: 65%;
|
||
}
|
||
|
||
/* Override for full-width elements when needed */
|
||
.post-content > h1,
|
||
.post-content > h2,
|
||
.post-content > h3,
|
||
.post-content > h4,
|
||
.post-content > h5,
|
||
.post-content > h6 {
|
||
width: 100%;
|
||
}
|
||
|
||
.sidenote,
|
||
.marginnote,
|
||
section .sidenote,
|
||
section .marginnote,
|
||
article .sidenote,
|
||
article .marginnote {
|
||
width: 35% !important;
|
||
margin-right: -39% !important;
|
||
margin-left: 6% !important;
|
||
overflow-wrap: break-word !important;
|
||
word-wrap: break-word !important;
|
||
}
|
||
|
||
/* Responsive adjustments */
|
||
@media (max-width: 1400px) {
|
||
section > p,
|
||
section > footer,
|
||
section > table,
|
||
section > blockquote,
|
||
p,
|
||
blockquote,
|
||
.post-content > p,
|
||
.post-content > blockquote,
|
||
.post-content > ul,
|
||
.post-content > ol {
|
||
width: 80%;
|
||
}
|
||
|
||
.sidenote,
|
||
.marginnote,
|
||
section .sidenote,
|
||
section .marginnote,
|
||
article .sidenote,
|
||
article .marginnote {
|
||
width: 32% !important;
|
||
margin-right: -36% !important;
|
||
margin-left: 6% !important;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
section > p,
|
||
section > footer,
|
||
section > table,
|
||
section > blockquote,
|
||
p,
|
||
blockquote,
|
||
.post-content > p,
|
||
.post-content > blockquote,
|
||
.post-content > ul,
|
||
.post-content > ol {
|
||
width: 80%;
|
||
}
|
||
|
||
.sidenote,
|
||
.marginnote,
|
||
section .sidenote,
|
||
section .marginnote,
|
||
article .sidenote,
|
||
article .marginnote {
|
||
width: 30% !important;
|
||
margin-right: -34% !important;
|
||
margin-left: 6% !important;
|
||
}
|
||
}
|
||
|
||
/* Code blocks - extend wider but respect content margin */
|
||
section > pre,
|
||
.post-content > pre,
|
||
.tufte pre,
|
||
pre {
|
||
width: calc(90vw - 2rem) !important;
|
||
max-width: 1400px !important;
|
||
margin-left: 0 !important;
|
||
margin-right: auto !important;
|
||
position: relative;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* Override Tufte CSS constraint on code within pre */
|
||
pre > code {
|
||
width: 100% !important;
|
||
max-width: none !important;
|
||
}
|
||
|
||
/* Copy button for code blocks */
|
||
.code-block-wrapper {
|
||
position: relative;
|
||
}
|
||
|
||
.copy-button {
|
||
position: absolute;
|
||
top: 8px;
|
||
right: 8px;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
padding: 4px 8px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
opacity: 0;
|
||
transition: opacity 0.2s ease;
|
||
z-index: 10;
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||
}
|
||
|
||
.code-block-wrapper:hover .copy-button,
|
||
.copy-button:focus {
|
||
opacity: 1;
|
||
}
|
||
|
||
.copy-button:hover {
|
||
background: rgba(255, 255, 255, 1);
|
||
border-color: #999;
|
||
}
|
||
|
||
.copy-button:active {
|
||
background: rgba(240, 240, 240, 1);
|
||
}
|
||
|
||
.copy-button.copied {
|
||
background: rgba(76, 175, 80, 0.9);
|
||
color: white;
|
||
border-color: #4CAF50;
|
||
}
|
||
|
||
@media (prefers-color-scheme: dark) {
|
||
.copy-button {
|
||
background: rgba(50, 50, 50, 0.9);
|
||
color: white;
|
||
border-color: #555;
|
||
}
|
||
|
||
.copy-button:hover {
|
||
background: rgba(70, 70, 70, 1);
|
||
border-color: #777;
|
||
}
|
||
|
||
.copy-button:active {
|
||
background: rgba(30, 30, 30, 1);
|
||
}
|
||
}
|
||
|
||
/* Responsive adjustments for code blocks */
|
||
@media (max-width: 768px) {
|
||
section > pre,
|
||
.post-content > pre,
|
||
.tufte pre,
|
||
pre {
|
||
width: calc(100vw - 2rem) !important;
|
||
margin-left: -1rem !important;
|
||
}
|
||
}
|
||
|
||
/* Breadcrumb styling */
|
||
.breadcrumbs {
|
||
font-size: 0.875rem;
|
||
color: #666;
|
||
margin: 1rem 0 2rem 0;
|
||
font-family: et-book, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
|
||
}
|
||
|
||
.breadcrumbs a {
|
||
color: #666;
|
||
text-decoration: none;
|
||
background: none;
|
||
text-shadow: none;
|
||
}
|
||
|
||
.breadcrumbs a:hover {
|
||
color: #111;
|
||
}
|
||
|
||
.breadcrumb-separator {
|
||
margin: 0 0.5rem;
|
||
color: #999;
|
||
}
|
||
|
||
|
||
/* Mobile breadcrumb adjustments */
|
||
@media (max-width: 760px) {
|
||
.breadcrumbs {
|
||
font-size: 0.8rem;
|
||
margin: 0.75rem 0 1.5rem 0;
|
||
}
|
||
|
||
.breadcrumb-separator {
|
||
margin: 0 0.25rem;
|
||
}
|
||
}
|
||
|
||
/* Homepage subtitle spacing adjustment */
|
||
p.subtitle {
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* Navigation spacing adjustment */
|
||
header nav a {
|
||
margin-right: 1rem;
|
||
}
|
||
|
||
header nav a:last-child {
|
||
margin-right: 0;
|
||
}
|
||
|
||
/* Random post button styling */
|
||
.random-link {
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
/* Directory listing styling */
|
||
.directory-listing ul {
|
||
list-style: none;
|
||
padding-left: 0;
|
||
font-size: 0.95rem;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
.directory-listing li {
|
||
margin-bottom: 0.75rem;
|
||
padding-left: 2rem;
|
||
position: relative;
|
||
font-family: et-book, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
|
||
}
|
||
|
||
.directory-listing li::before {
|
||
content: attr(data-icon);
|
||
position: absolute;
|
||
left: 0;
|
||
color: #666;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.directory-listing a {
|
||
color: #444;
|
||
text-decoration: none;
|
||
font-weight: 400;
|
||
background: none;
|
||
text-shadow: none;
|
||
transition: color 0.2s ease;
|
||
}
|
||
|
||
.directory-listing a:hover {
|
||
color: #111;
|
||
}
|
||
|
||
.item-date {
|
||
font-size: 0.8rem;
|
||
color: #888;
|
||
font-style: italic;
|
||
margin-left: 0.75rem;
|
||
font-variant: small-caps;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.directory-listing h3 {
|
||
font-size: 1.3rem;
|
||
color: #333;
|
||
font-weight: 400;
|
||
font-style: italic;
|
||
margin-bottom: 1.5rem;
|
||
margin-top: 2.5rem;
|
||
}
|
||
|
||
/* Hide HR lines to prevent sidenote interference */
|
||
hr {
|
||
display: none;
|
||
}
|
||
|
||
|
||
/* Fix iframe overflow into sidenotes */
|
||
iframe {
|
||
max-width: 55% !important;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
@media (max-width: 1400px) {
|
||
iframe {
|
||
max-width: 60% !important;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
iframe {
|
||
max-width: 70% !important;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 760px) {
|
||
iframe {
|
||
max-width: 100% !important;
|
||
}
|
||
}
|
||
|
||
/* Footer spacing */
|
||
.footer-note {
|
||
margin-top: 3rem;
|
||
}
|
||
|
||
/* Make article paragraphs slightly smaller */
|
||
.post-content p:not(.sidenote):not(.marginnote) {
|
||
font-size: 1.3rem !important;
|
||
width: 70% !important;
|
||
}
|
||
|
||
/* Make lists wider for better readability */
|
||
.post-content > ul,
|
||
.post-content > ol {
|
||
width: 72.5% !important;
|
||
}
|
||
|
||
@media (max-width: 1400px) {
|
||
.post-content > ul,
|
||
.post-content > ol {
|
||
width: 72.5% !important;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
.post-content > ul,
|
||
.post-content > ol {
|
||
width: 72.5% !important;
|
||
}
|
||
}
|
||
|
||
/* Make blockquote paragraphs wider */
|
||
.post-content blockquote p,
|
||
article .post-content blockquote p,
|
||
section .post-content blockquote p,
|
||
blockquote .post-content p,
|
||
blockquote p,
|
||
blockquote,
|
||
.post-content blockquote,
|
||
article blockquote,
|
||
section blockquote,
|
||
main blockquote,
|
||
body blockquote {
|
||
width: 75% !important;
|
||
}
|
||
|
||
/* Make list item paragraphs wider */
|
||
ol li p,
|
||
ul li p,
|
||
.post-content ol li p,
|
||
.post-content ul li p {
|
||
width: 75% !important;
|
||
}
|
||
|
||
/* Adjust heading sizes */
|
||
h2 {
|
||
font-size: 2.1rem !important;
|
||
}
|
||
|
||
h3 {
|
||
font-size: 1.7rem !important;
|
||
font-weight: 500 !important;
|
||
}
|
||
|
||
/* Reduce article padding */
|
||
article {
|
||
padding: 3rem 0rem !important;
|
||
}
|
||
|
||
/* === PREMIUM TYPOGRAPHY ENHANCEMENTS === */
|
||
|
||
/* Advanced font rendering */
|
||
html {
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
text-rendering: optimizeLegibility;
|
||
}
|
||
|
||
body {
|
||
font-kerning: normal;
|
||
text-rendering: optimizeLegibility;
|
||
font-weight: 420;
|
||
}
|
||
|
||
/* Enhanced link rendering */
|
||
a {
|
||
text-decoration-skip-ink: auto;
|
||
}
|
||
|
||
/* Beautiful text selection */
|
||
::selection {
|
||
background: rgba(0, 0, 0, 0.1);
|
||
color: inherit;
|
||
text-shadow: none;
|
||
}
|
||
|
||
::-moz-selection {
|
||
background: rgba(0, 0, 0, 0.1);
|
||
color: inherit;
|
||
text-shadow: none;
|
||
}
|
||
|
||
/* === SUBTLE CODE SYNTAX HIGHLIGHTING === */
|
||
|
||
/* Make comments lighter in code blocks */
|
||
pre code .hljs-comment,
|
||
pre .hljs-comment,
|
||
code .comment,
|
||
pre .comment {
|
||
color: #888 !important;
|
||
font-style: italic;
|
||
}
|
||
|
||
/* Handle various comment patterns */
|
||
pre code:has-text("//"),
|
||
pre code:has-text("#"),
|
||
pre code:has-text("/*"),
|
||
pre code:has-text("<!--") {
|
||
/* Base styling handled by specific selectors below */
|
||
}
|
||
|
||
/* Simple regex-based comment highlighting for common patterns */
|
||
pre {
|
||
/* This will be handled by JavaScript for dynamic highlighting */
|
||
}
|
||
|
||
|
||
|
||
/* Fix excessive right padding on mobile */
|
||
@media (max-width: 760px) {
|
||
.post-content p:not(.sidenote):not(.marginnote) {
|
||
width: 100% !important;
|
||
}
|
||
body {
|
||
padding-left: 8% !important;
|
||
padding-right: 8% !important;
|
||
width: 84% !important;
|
||
}
|
||
|
||
/* Hide sidenotes and their labels on narrow screens */
|
||
.sidenote,
|
||
.marginnote {
|
||
display: none;
|
||
}
|
||
|
||
.margin-toggle,
|
||
label.margin-toggle {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Make content full width when sidenotes are hidden */
|
||
section > p,
|
||
section > footer,
|
||
section > table,
|
||
section > blockquote,
|
||
p,
|
||
blockquote,
|
||
.post-content > p,
|
||
.post-content > blockquote,
|
||
.post-content > ul,
|
||
.post-content > ol {
|
||
width: 100%;
|
||
}
|
||
|
||
section > dl,
|
||
section > ol,
|
||
section > ul,
|
||
.post-content > dl {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
/* Mobile font fixes */
|
||
@media (max-width: 760px) {
|
||
body, p, h1, h2, h3, h4, h5, h6 {
|
||
text-shadow: none !important;
|
||
-webkit-text-stroke: 0 !important;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
text-rendering: optimizeLegibility;
|
||
}
|
||
|
||
/* Fix Tufte's complex link underlines on mobile */
|
||
a, a:link, a:visited,
|
||
.tufte-underline,
|
||
.hover-tufte-underline:hover {
|
||
text-shadow: none !important;
|
||
background: none !important;
|
||
text-decoration: underline !important;
|
||
text-decoration-thickness: 1px !important;
|
||
text-underline-offset: 2px !important;
|
||
}
|
||
|
||
/* Ensure no double font rendering */
|
||
* {
|
||
-webkit-transform: translateZ(0);
|
||
transform: translateZ(0);
|
||
}
|
||
}
|
||
|
||
/* Print Styles for Tufte CSS */
|
||
@media print {
|
||
* {
|
||
background: transparent !important;
|
||
color: black !important;
|
||
box-shadow: none !important;
|
||
text-shadow: none !important;
|
||
}
|
||
|
||
body {
|
||
font-size: 12pt;
|
||
line-height: 1.3;
|
||
margin: 0;
|
||
padding: 0;
|
||
background: white;
|
||
color: black;
|
||
width: 100% !important;
|
||
max-width: none !important;
|
||
}
|
||
|
||
/* Hide navigation and non-essential elements */
|
||
header nav,
|
||
.breadcrumbs,
|
||
.post-navigation,
|
||
.related-posts,
|
||
.footer-content,
|
||
.search-container {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Optimize content for print */
|
||
section > p,
|
||
section > footer,
|
||
section > table,
|
||
section > blockquote,
|
||
p,
|
||
blockquote,
|
||
.post-content > p,
|
||
.post-content > blockquote,
|
||
.post-content > ul,
|
||
.post-content > ol {
|
||
width: 100% !important;
|
||
max-width: none !important;
|
||
}
|
||
|
||
section > dl,
|
||
section > ol,
|
||
section > ul,
|
||
.post-content > dl {
|
||
width: 100% !important;
|
||
}
|
||
|
||
/* Convert sidenotes to footnotes for print */
|
||
.sidenote,
|
||
.marginnote {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Show sidenote content as footnotes at bottom */
|
||
.sidenote::before {
|
||
content: "["counter(sidenote-counter) "] ";
|
||
counter-increment: sidenote-counter;
|
||
}
|
||
|
||
/* Print-specific typography */
|
||
h1 {
|
||
font-size: 18pt;
|
||
margin: 24pt 0 12pt 0;
|
||
page-break-after: avoid;
|
||
}
|
||
|
||
h2 {
|
||
font-size: 16pt;
|
||
margin: 20pt 0 10pt 0;
|
||
page-break-after: avoid;
|
||
}
|
||
|
||
h3 {
|
||
font-size: 14pt;
|
||
margin: 16pt 0 8pt 0;
|
||
page-break-after: avoid;
|
||
}
|
||
|
||
p {
|
||
font-size: 12pt;
|
||
margin: 12pt 0;
|
||
text-align: justify;
|
||
orphans: 3;
|
||
widows: 3;
|
||
}
|
||
|
||
/* Print links with URLs */
|
||
a[href]:after {
|
||
content: " (" attr(href) ")";
|
||
font-size: 10pt;
|
||
color: #666;
|
||
}
|
||
|
||
a[href^="#"]:after,
|
||
a[href^="/"]:after {
|
||
content: "";
|
||
}
|
||
|
||
/* Blockquotes */
|
||
blockquote {
|
||
margin: 12pt 24pt;
|
||
padding: 0;
|
||
font-style: italic;
|
||
border-left: 2pt solid #ccc;
|
||
padding-left: 12pt;
|
||
}
|
||
|
||
/* Code blocks */
|
||
pre, code {
|
||
font-family: "Courier New", Courier, monospace;
|
||
font-size: 10pt;
|
||
background: #f5f5f5;
|
||
border: 1pt solid #ccc;
|
||
padding: 6pt;
|
||
}
|
||
|
||
pre {
|
||
white-space: pre-wrap;
|
||
page-break-inside: avoid;
|
||
}
|
||
|
||
/* Override width constraints for code blocks */
|
||
section > pre,
|
||
.post-content > pre,
|
||
pre {
|
||
width: 85% !important;
|
||
max-width: none !important;
|
||
margin-left: 0 !important;
|
||
}
|
||
|
||
/* Tables */
|
||
table {
|
||
border-collapse: collapse;
|
||
width: 100%;
|
||
font-size: 10pt;
|
||
}
|
||
|
||
th, td {
|
||
border: 1pt solid #ccc;
|
||
padding: 6pt;
|
||
text-align: left;
|
||
}
|
||
|
||
th {
|
||
background: #f0f0f0;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* Images */
|
||
img {
|
||
max-width: 100%;
|
||
height: auto;
|
||
page-break-inside: avoid;
|
||
}
|
||
|
||
/* Page breaks */
|
||
.page-break {
|
||
page-break-before: always;
|
||
}
|
||
|
||
/* Hide toggle inputs for sidenotes */
|
||
input[type="checkbox"].margin-toggle {
|
||
display: none !important;
|
||
}
|
||
|
||
label.margin-toggle {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Header styling for print */
|
||
header h1 {
|
||
font-size: 20pt;
|
||
margin-bottom: 6pt;
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: 14pt;
|
||
margin-bottom: 20pt;
|
||
font-style: italic;
|
||
}
|
||
|
||
/* Ensure proper spacing */
|
||
section {
|
||
margin-bottom: 20pt;
|
||
}
|
||
|
||
/* Lists */
|
||
ul, ol {
|
||
margin: 12pt 0;
|
||
padding-left: 20pt;
|
||
}
|
||
|
||
li {
|
||
margin: 6pt 0;
|
||
}
|
||
}
|
||
|
||
/* Heading anchor links */
|
||
h1, h2, h3, h4, h5, h6 {
|
||
position: relative;
|
||
}
|
||
|
||
.heading-anchor {
|
||
position: absolute;
|
||
left: -1.2em;
|
||
top: 0;
|
||
opacity: 0;
|
||
color: #ccc;
|
||
text-decoration: none;
|
||
font-weight: normal;
|
||
font-style: italic;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
padding: 0 0.2em;
|
||
}
|
||
|
||
.heading-anchor:hover {
|
||
color: #999;
|
||
text-decoration: none;
|
||
}
|
||
|
||
h1:hover .heading-anchor,
|
||
h2:hover .heading-anchor,
|
||
h3:hover .heading-anchor,
|
||
h4:hover .heading-anchor,
|
||
h5:hover .heading-anchor,
|
||
h6:hover .heading-anchor {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* Mobile adjustments for anchor links */
|
||
@media (max-width: 768px) {
|
||
.heading-anchor {
|
||
left: -0.8em;
|
||
font-size: 0.9em;
|
||
}
|
||
}
|
||
|
||
</style>
|
||
|
||
|
||
<!-- Structured Data (JSON-LD) -->
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "{% block schema_type %}WebSite{% endblock %}",
|
||
"name": "{% block schema_name %}Kenneth Reitz{% endblock %}",
|
||
"url": "https://kennethreitz.org{% block schema_url %}/{% endblock %}",
|
||
"description": "{% block schema_description %}Kenneth Reitz - Creator of Requests and Certifi, trusted by millions of developers worldwide. Thoughts on technology, philosophy, and building software for humans.{% endblock %}",
|
||
"author": {
|
||
"@type": "Person",
|
||
"name": "Kenneth Reitz",
|
||
"url": "https://kennethreitz.org",
|
||
"sameAs": [
|
||
"https://github.com/kennethreitz",
|
||
"https://twitter.com/kennethreitz42"
|
||
],
|
||
"jobTitle": "Python Developer",
|
||
"worksFor": {
|
||
"@type": "Organization",
|
||
"name": "Independent"
|
||
},
|
||
"knowsAbout": [
|
||
"Python Programming",
|
||
"API Design",
|
||
"Software Architecture",
|
||
"Open Source Development",
|
||
"Artificial Intelligence",
|
||
"Mental Health Advocacy"
|
||
]
|
||
},
|
||
"publisher": {
|
||
"@type": "Person",
|
||
"name": "Kenneth Reitz",
|
||
"url": "https://kennethreitz.org"
|
||
}{% block schema_extra %}{% endblock %}
|
||
}
|
||
</script>
|
||
|
||
{% block extra_head %}{% endblock %}
|
||
|
||
<!-- Analytics -->
|
||
{% if not (config.get('DISABLE_ANALYTICS') or request.environ.get('DISABLE_ANALYTICS')) %}
|
||
<script type="text/javascript">
|
||
var _gauges = _gauges || [];
|
||
(function() {
|
||
var t = document.createElement('script');
|
||
t.type = 'text/javascript';
|
||
t.async = true;
|
||
t.id = 'gauges-tracker';
|
||
t.setAttribute('data-site-id', '65529a9abd1a3b3101979d52');
|
||
t.setAttribute('data-track-path', 'https://track.gaug.es/track.gif');
|
||
t.src = 'https://d2fuc4clr7gvcn.cloudfront.net/track.js';
|
||
var s = document.getElementsByTagName('script')[0];
|
||
s.parentNode.insertBefore(t, s);
|
||
})();
|
||
</script>
|
||
{% endif %}
|
||
</head>
|
||
<body>
|
||
<article>
|
||
<header>
|
||
<nav>
|
||
<a href="/">Home</a>
|
||
<a href="/archive">Archive</a>
|
||
<a href="/themes">Themes</a>
|
||
<a href="/directory">Directory</a>
|
||
<a href="/search">[search]</a>
|
||
<a href="/random" class="random-link">[random]</a>
|
||
</nav>
|
||
|
||
{% if current_path or breadcrumbs %}
|
||
<div class="breadcrumbs">
|
||
<a href="/">Home</a>
|
||
{% if breadcrumbs %}
|
||
{% for crumb in breadcrumbs %}
|
||
<span class="breadcrumb-separator">›</span>
|
||
<a href="{{ crumb.url }}">{{ crumb.name }}</a>
|
||
{% endfor %}
|
||
{% elif current_path %}
|
||
{% set path_parts = current_path.strip('/').split('/') %}
|
||
{% for part in path_parts if part %}
|
||
<span class="breadcrumb-separator">›</span>
|
||
{% set partial_path = '/' + path_parts[:loop.index]|join('/') %}
|
||
<a href="{{ partial_path }}">{{ part }}</a>
|
||
{% endfor %}
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
</header>
|
||
|
||
<main>
|
||
{% block content %}{% endblock %}
|
||
</main>
|
||
|
||
<footer>
|
||
<div class="footer-content">
|
||
<div class="footer-note">
|
||
<p>© {{ "now"|strftime("%Y") }} Kenneth Reitz. Made with <a href="https://kjvstudy.org/book/1%20Corinthians/chapter/13">love</a>.</p>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
</article>
|
||
|
||
{% block extra_scripts %}{% endblock %}
|
||
|
||
<!-- Force light mode, especially on mobile -->
|
||
<style>
|
||
@media (prefers-color-scheme: dark) {
|
||
body {
|
||
background-color: rgb(255, 255, 248) !important;
|
||
color: #111 !important;
|
||
}
|
||
|
||
h1, h2, h3, h4, h5, h6 {
|
||
color: #333 !important;
|
||
}
|
||
|
||
a {
|
||
color: #111 !important;
|
||
}
|
||
|
||
/* Force light backgrounds on all elements */
|
||
* {
|
||
background-color: transparent !important;
|
||
color: inherit !important;
|
||
}
|
||
|
||
/* Override any dark mode styling */
|
||
body, html {
|
||
background: rgb(255, 255, 248) !important;
|
||
color: #111 !important;
|
||
}
|
||
}
|
||
|
||
/* Mobile-specific light mode enforcement */
|
||
@media (max-width: 760px) {
|
||
body {
|
||
background-color: rgb(255, 255, 248) !important;
|
||
color: #111 !important;
|
||
}
|
||
|
||
* {
|
||
background-color: transparent !important;
|
||
}
|
||
|
||
body, html {
|
||
background: rgb(255, 255, 248) !important;
|
||
color: #111 !important;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<!-- Simplified Color System (light themes only) -->
|
||
<script>
|
||
(function() {
|
||
// Check if user prefers light mode or is on mobile
|
||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||
const isMobile = window.innerWidth <= 760;
|
||
|
||
// Force light mode on mobile or if user prefers dark (override their dark preference)
|
||
if (isMobile || prefersDark) {
|
||
document.body.style.backgroundColor = 'rgb(255, 255, 248)';
|
||
document.body.style.color = '#111';
|
||
return; // Skip color schemes entirely
|
||
}
|
||
|
||
const lightColorSchemes = [
|
||
'scheme-ocean',
|
||
'scheme-forest',
|
||
'scheme-sunset',
|
||
'scheme-lavender',
|
||
'scheme-rose',
|
||
'scheme-sage',
|
||
'scheme-amber'
|
||
];
|
||
|
||
// Get or generate a scheme based on the current page
|
||
let scheme = localStorage.getItem('current-color-scheme');
|
||
|
||
// Change scheme occasionally (20% chance on page load)
|
||
if (!scheme || Math.random() < 0.2) {
|
||
scheme = lightColorSchemes[Math.floor(Math.random() * lightColorSchemes.length)];
|
||
localStorage.setItem('current-color-scheme', scheme);
|
||
}
|
||
|
||
// Apply the scheme
|
||
document.body.className = (document.body.className + ' ' + scheme).trim();
|
||
})();
|
||
</script>
|
||
|
||
<script>
|
||
// Add copy buttons to code blocks and subtle syntax highlighting
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Find all pre elements (code blocks)
|
||
const codeBlocks = document.querySelectorAll('pre');
|
||
|
||
// Function to add subtle comment highlighting
|
||
function highlightComments(pre) {
|
||
const code = pre.querySelector('code') || pre;
|
||
let html = code.innerHTML;
|
||
|
||
// Python/Shell comments (# comment) - both line start and inline
|
||
html = html.replace(/(^|\s+)(#.*)$/gm, '$1<span style="color: #888; font-style: italic;">$2</span>');
|
||
|
||
// Python docstrings (""" or ''')
|
||
html = html.replace(/("""[\s\S]*?""")/g, '<span style="color: #888; font-style: italic;">$1</span>');
|
||
html = html.replace(/('''[\s\S]*?''')/g, '<span style="color: #888; font-style: italic;">$1</span>');
|
||
|
||
// Python class names (class ClassName)
|
||
html = html.replace(/(\bclass\s+)([A-Za-z_][A-Za-z0-9_]*)/g, '$1<span style="font-weight: bold;">$2</span>');
|
||
|
||
// JavaScript/C++/Java comments (// comment)
|
||
html = html.replace(/(\s*\/\/.*)$/gm, '<span style="color: #888; font-style: italic;">$1</span>');
|
||
|
||
// CSS/C/Java block comments (/* comment */)
|
||
html = html.replace(/(\/\*[\s\S]*?\*\/)/g, '<span style="color: #888; font-style: italic;">$1</span>');
|
||
|
||
// HTML comments (<!-- comment -->)
|
||
html = html.replace(/(<!--[\s\S]*?-->)/g, '<span style="color: #888; font-style: italic;">$1</span>');
|
||
|
||
code.innerHTML = html;
|
||
}
|
||
|
||
codeBlocks.forEach(function(pre) {
|
||
// Add subtle comment highlighting first
|
||
highlightComments(pre);
|
||
|
||
// Skip if already wrapped
|
||
if (pre.parentElement.classList.contains('code-block-wrapper')) {
|
||
return;
|
||
}
|
||
|
||
// Create wrapper div
|
||
const wrapper = document.createElement('div');
|
||
wrapper.className = 'code-block-wrapper';
|
||
|
||
// Create copy button
|
||
const copyButton = document.createElement('button');
|
||
copyButton.className = 'copy-button';
|
||
copyButton.textContent = 'Copy';
|
||
copyButton.setAttribute('aria-label', 'Copy code to clipboard');
|
||
|
||
// Add click handler
|
||
copyButton.addEventListener('click', async function() {
|
||
try {
|
||
// Get the text content of the pre element
|
||
const codeText = pre.textContent || pre.innerText;
|
||
|
||
// Use modern clipboard API if available
|
||
if (navigator.clipboard && window.isSecureContext) {
|
||
await navigator.clipboard.writeText(codeText);
|
||
} else {
|
||
// Fallback for older browsers
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = codeText;
|
||
textArea.style.position = 'fixed';
|
||
textArea.style.left = '-999999px';
|
||
textArea.style.top = '-999999px';
|
||
document.body.appendChild(textArea);
|
||
textArea.focus();
|
||
textArea.select();
|
||
document.execCommand('copy');
|
||
textArea.remove();
|
||
}
|
||
|
||
// Show feedback
|
||
const originalText = copyButton.textContent;
|
||
copyButton.textContent = 'Copied!';
|
||
copyButton.classList.add('copied');
|
||
|
||
setTimeout(function() {
|
||
copyButton.textContent = originalText;
|
||
copyButton.classList.remove('copied');
|
||
}, 2000);
|
||
|
||
} catch (err) {
|
||
console.error('Failed to copy code: ', err);
|
||
|
||
// Show error feedback
|
||
const originalText = copyButton.textContent;
|
||
copyButton.textContent = 'Failed';
|
||
setTimeout(function() {
|
||
copyButton.textContent = originalText;
|
||
}, 2000);
|
||
}
|
||
});
|
||
|
||
// Wrap the pre element and add the button
|
||
pre.parentNode.insertBefore(wrapper, pre);
|
||
wrapper.appendChild(pre);
|
||
wrapper.appendChild(copyButton);
|
||
});
|
||
});
|
||
|
||
// Add heading anchor links
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const headings = document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]');
|
||
|
||
headings.forEach(function(heading) {
|
||
const anchor = document.createElement('a');
|
||
anchor.className = 'heading-anchor';
|
||
anchor.innerHTML = '#';
|
||
anchor.href = '#' + heading.id;
|
||
anchor.title = 'Copy link to this heading';
|
||
|
||
// Handle click to copy URL with anchor
|
||
anchor.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
const url = window.location.protocol + '//' + window.location.host + window.location.pathname + '#' + heading.id;
|
||
|
||
// Copy to clipboard
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
navigator.clipboard.writeText(url).then(function() {
|
||
// Briefly show feedback
|
||
const originalText = anchor.innerHTML;
|
||
anchor.innerHTML = '✓';
|
||
anchor.style.color = '#4CAF50';
|
||
setTimeout(function() {
|
||
anchor.innerHTML = originalText;
|
||
anchor.style.color = '';
|
||
}, 1000);
|
||
});
|
||
} else {
|
||
// Fallback for older browsers
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = url;
|
||
document.body.appendChild(textArea);
|
||
textArea.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(textArea);
|
||
|
||
// Show feedback
|
||
const originalText = anchor.innerHTML;
|
||
anchor.innerHTML = '✓';
|
||
anchor.style.color = '#4CAF50';
|
||
setTimeout(function() {
|
||
anchor.innerHTML = originalText;
|
||
anchor.style.color = '';
|
||
}, 1000);
|
||
}
|
||
|
||
// Also update the URL in the browser
|
||
window.history.replaceState({}, '', url);
|
||
});
|
||
|
||
heading.insertBefore(anchor, heading.firstChild);
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|