mirror of
https://github.com/kennethreitz/kennethreitz.org.git
synced 2026-06-05 22:50:17 +00:00
781 lines
42 KiB
HTML
781 lines
42 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 %}kennethreitz.org — {% endblock %}</title>
|
||
|
||
<!-- Import fonts -->
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:ital,wght@0,400;0,500;0,600;1,400&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
|
||
|
||
<!-- CSS -->
|
||
<link rel="stylesheet" href="/static/custom.css">
|
||
|
||
<!-- Favicon -->
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>💻</text></svg>">
|
||
|
||
<!-- Prism.js for Syntax Highlighting - Using VS Code-inspired Theme -->
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/themes/prism-tomorrow.min.css">
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/plugins/line-numbers/prism-line-numbers.min.css">
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/prism.min.js" defer></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/plugins/line-numbers/prism-line-numbers.min.js" defer></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/plugins/toolbar/prism-toolbar.min.js" defer></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/components/prism-python.min.js" defer></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/components/prism-javascript.min.js" defer></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/components/prism-css.min.js" defer></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/components/prism-bash.min.js" defer></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/components/prism-json.min.js" defer></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/components/prism-markdown.min.js" defer></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/components/prism-yaml.min.js" defer></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/components/prism-jsx.min.js" defer></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/components/prism-typescript.min.js" defer></script>
|
||
|
||
{% block head %}{% endblock %}
|
||
</head>
|
||
<body class="bg-background dark:bg-background-dark text-text dark:text-text-light">
|
||
<header id="site-header">
|
||
<div class="container">
|
||
<h2 class="site-title"><a href="/">kennethreitz.org</a></h2>
|
||
<nav>
|
||
<ul>
|
||
<li><a href="/software" {% if '/software' in request.path %}class="active"{% endif %}>Software</a></li>
|
||
<li><a href="/essays" {% if '/essays' in request.path %}class="active"{% endif %}>Essays</a></li>
|
||
<li><a href="/talks" {% if '/talks' in request.path %}class="active"{% endif %}>Talks</a></li>
|
||
<li><a href="/music" {% if '/music' in request.path %}class="active"{% endif %}>Music</a></li>
|
||
<li><a href="/poetry" {% if '/poetry' in request.path %}class="active"{% endif %}>Poetry</a></li>
|
||
<li><a href="/artificial-intelligence" {% if '/artificial-intelligence' in request.path %}class="active"{% endif %}>AI</a></li>
|
||
<li><a href="/contact" {% if '/contact' in request.path %}class="active"{% endif %}>Contact</a></li>
|
||
</ul>
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="py-8">
|
||
<div class="container">
|
||
<div class="content-narrow">
|
||
{% block content %}{% endblock %}
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- VS Code-inspired Explorer Button -->
|
||
<div id="explorer-button" class="explorer-fab">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M3 3h18v18H3zM9 3v18M3 9h6M3 15h6"></path>
|
||
</svg>
|
||
<span class="shortcut-hint">Ctrl+E</span>
|
||
</div>
|
||
|
||
<!-- Explorer Panel -->
|
||
<div id="explorer-panel" class="explorer-panel">
|
||
<div class="explorer-header">
|
||
<h3>EXPLORER</h3>
|
||
<button id="explorer-close">×</button>
|
||
</div>
|
||
<div class="explorer-body">
|
||
<div class="explorer-section">
|
||
<div class="explorer-section-header">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"></polyline>
|
||
<path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path>
|
||
</svg>
|
||
<span>PROJECTS</span>
|
||
</div>
|
||
<div class="explorer-section-content">
|
||
<a href="/software/requests" class="explorer-item">requests</a>
|
||
<a href="/software/pipenv" class="explorer-item">pipenv</a>
|
||
<a href="/software/tablib" class="explorer-item">tablib</a>
|
||
<a href="/software/responder" class="explorer-item">responder</a>
|
||
</div>
|
||
</div>
|
||
<div class="explorer-section">
|
||
<div class="explorer-section-header">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
|
||
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
|
||
</svg>
|
||
<span>POSTS</span>
|
||
</div>
|
||
<div class="explorer-section-content">
|
||
<a href="/essays" class="explorer-item">Essays</a>
|
||
<a href="/poetry" class="explorer-item">Poetry</a>
|
||
<a href="/artificial-intelligence" class="explorer-item">AI</a>
|
||
</div>
|
||
</div>
|
||
<div class="explorer-section">
|
||
<div class="explorer-section-header">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="10"></circle>
|
||
<path d="M12 8l4 4-4 4M8 12h8"></path>
|
||
</svg>
|
||
<span>LINKS</span>
|
||
</div>
|
||
<div class="explorer-section-content">
|
||
<a href="https://github.com/kennethreitz" class="explorer-item external">GitHub</a>
|
||
<a href="https://twitter.com/kennethreitz42" class="explorer-item external">Twitter</a>
|
||
<a href="https://www.linkedin.com/in/kennethreitz/" class="explorer-item external">LinkedIn</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Command Palette -->
|
||
<div id="command-palette" class="command-palette">
|
||
<div class="command-palette-header">
|
||
<input type="text" class="command-palette-search" id="command-search" placeholder="Type a command or search..." autocomplete="off">
|
||
</div>
|
||
<div class="command-palette-results" id="command-results">
|
||
<!-- Results will be populated dynamically -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Keyboard Shortcuts Panel -->
|
||
<div id="shortcuts-panel" class="shortcuts-panel">
|
||
<div class="shortcuts-header">
|
||
<h3 class="shortcuts-title">Keyboard Shortcuts</h3>
|
||
<button id="shortcuts-close" class="shortcuts-close">×</button>
|
||
</div>
|
||
<div class="shortcuts-content">
|
||
<div class="shortcuts-section">
|
||
<h4 class="shortcuts-section-title">Navigation</h4>
|
||
<div class="shortcuts-list">
|
||
<div class="shortcut-item">
|
||
<span class="shortcut-label">Open Explorer</span>
|
||
<div class="shortcut-combo">
|
||
<span class="shortcut-key">Ctrl</span>
|
||
<span class="shortcut-key">E</span>
|
||
</div>
|
||
</div>
|
||
<div class="shortcut-item">
|
||
<span class="shortcut-label">Command Palette</span>
|
||
<div class="shortcut-combo">
|
||
<span class="shortcut-key">Ctrl</span>
|
||
<span class="shortcut-key">P</span>
|
||
</div>
|
||
</div>
|
||
<div class="shortcut-item">
|
||
<span class="shortcut-label">Go Home</span>
|
||
<div class="shortcut-combo">
|
||
<span class="shortcut-key">Alt</span>
|
||
<span class="shortcut-key">Home</span>
|
||
</div>
|
||
</div>
|
||
<div class="shortcut-item">
|
||
<span class="shortcut-label">Keyboard Shortcuts</span>
|
||
<div class="shortcut-combo">
|
||
<span class="shortcut-key">?</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="shortcuts-section">
|
||
<h4 class="shortcuts-section-title">Content</h4>
|
||
<div class="shortcuts-list">
|
||
<div class="shortcut-item">
|
||
<span class="shortcut-label">Copy Code</span>
|
||
<div class="shortcut-combo">
|
||
<span class="shortcut-key">Alt</span>
|
||
<span class="shortcut-key">C</span>
|
||
</div>
|
||
</div>
|
||
<div class="shortcut-item">
|
||
<span class="shortcut-label">Toggle Dark Mode</span>
|
||
<div class="shortcut-combo">
|
||
<span class="shortcut-key">Alt</span>
|
||
<span class="shortcut-key">T</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="shortcuts-section">
|
||
<h4 class="shortcuts-section-title">Quick Access</h4>
|
||
<div class="shortcuts-list">
|
||
<div class="shortcut-item">
|
||
<span class="shortcut-label">Software</span>
|
||
<div class="shortcut-combo">
|
||
<span class="shortcut-key">G</span>
|
||
<span class="shortcut-key">S</span>
|
||
</div>
|
||
</div>
|
||
<div class="shortcut-item">
|
||
<span class="shortcut-label">Essays</span>
|
||
<div class="shortcut-combo">
|
||
<span class="shortcut-key">G</span>
|
||
<span class="shortcut-key">E</span>
|
||
</div>
|
||
</div>
|
||
<div class="shortcut-item">
|
||
<span class="shortcut-label">Contact</span>
|
||
<div class="shortcut-combo">
|
||
<span class="shortcut-key">G</span>
|
||
<span class="shortcut-key">C</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Overlay for modals -->
|
||
<div id="shortcuts-overlay" class="shortcuts-overlay"></div>
|
||
|
||
<footer class="mt-auto py-10 bg-primary-light/50 dark:bg-primary-dark/10 border-t border-border dark:border-border-dark">
|
||
<div class="container flex flex-col md:flex-row justify-between items-center gap-6">
|
||
<div class="flex gap-6">
|
||
<a href="/software" class="text-text dark:text-text-light no-underline hover:text-primary transition-colors">Software</a>
|
||
<a href="/essays" class="text-text dark:text-text-light no-underline hover:text-primary transition-colors">Essays</a>
|
||
<a href="/talks" class="text-text dark:text-text-light no-underline hover:text-primary transition-colors">Talks</a>
|
||
<a href="/values" class="text-text dark:text-text-light no-underline hover:text-primary transition-colors">Values</a>
|
||
<a href="/contact" class="text-text dark:text-text-light no-underline hover:text-primary transition-colors">Contact</a>
|
||
</div>
|
||
<div class="text-center p-4 bg-white/10 dark:bg-white/5 rounded-lg max-w-[600px] mx-auto">
|
||
<p id="kenneth-quote" class="italic relative cursor-pointer inline-block px-2 transition-all">"Attention is the only currency we have in life. Thanks for gifting me yours."</p>
|
||
</div>
|
||
<div class="flex items-center gap-4">
|
||
<div id="shortcuts-indicator" class="cursor-pointer text-sm flex items-center gap-1 py-1 px-2 rounded bg-white/10 dark:bg-white/5 text-text-secondary dark:text-text-light/60 font-mono hover:bg-white/20 dark:hover:bg-white/10">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||
</svg>
|
||
<span>Press ? for shortcuts</span>
|
||
</div>
|
||
<div id="elevenlabs-audionative-widget" data-height="90" data-width="100%" data-frameborder="no" data-scrolling="no" data-publicuserid="09af4c720273252ac5bcddb53500ed22b7c59ef815a710a03ea48b18e295c24f" data-playerurl="https://elevenlabs.io/player/index.html" class="mt-6 w-full rounded overflow-hidden">Loading the <a href="https://elevenlabs.io/text-to-speech" target="_blank" rel="noopener">Elevenlabs Text to Speech</a> AudioNative Player...</div>
|
||
<script src="https://elevenlabs.io/player/audioNativeHelper.js" type="text/javascript"></script>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
<script>
|
||
// Handle header scroll effect
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const header = document.getElementById('site-header');
|
||
const scrollWatcher = () => {
|
||
if (window.scrollY > 10) {
|
||
header.classList.add('shadow-sm');
|
||
} else {
|
||
header.classList.remove('shadow-sm');
|
||
}
|
||
};
|
||
|
||
window.addEventListener('scroll', scrollWatcher);
|
||
|
||
// IDE-style initialization animation effect - only on first visit
|
||
const mainContent = document.querySelector('main');
|
||
if (mainContent) {
|
||
// Check if this is the user's first visit to the site in this session
|
||
const hasVisited = sessionStorage.getItem('has_visited_site');
|
||
|
||
if (!hasVisited) {
|
||
// Set the flag that the user has visited
|
||
sessionStorage.setItem('has_visited_site', 'true');
|
||
|
||
// Add terminal loading animation
|
||
const loadingTerminal = document.createElement('div');
|
||
loadingTerminal.className = 'terminal-loader';
|
||
loadingTerminal.innerHTML = `
|
||
<div class="terminal-header">
|
||
<span class="terminal-title">initializing kennethreitz.org</span>
|
||
</div>
|
||
<div class="terminal-content" id="terminal-content">
|
||
<p class="command-line">Loading environment variables...</p>
|
||
<p class="command-output">✓ Environment configured</p>
|
||
<p class="command-line">Setting up developer experience...</p>
|
||
<p class="command-output">✓ Dependencies installed</p>
|
||
<p class="command-line">Applying syntax highlighting...</p>
|
||
<p class="command-output">✓ Prism.js activated</p>
|
||
<p class="command-line">Initializing code editor interface...</p>
|
||
<p class="command-output">✓ Visual improvements applied</p>
|
||
<p class="command-line">Rendering content...</p>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(loadingTerminal);
|
||
|
||
// Simulate typing effect
|
||
setTimeout(() => {
|
||
const finalLine = document.createElement('p');
|
||
finalLine.className = 'command-output success';
|
||
finalLine.textContent = '✓ kennethreitz.org loaded successfully!';
|
||
document.getElementById('terminal-content').appendChild(finalLine);
|
||
|
||
setTimeout(() => {
|
||
loadingTerminal.classList.add('fade-out');
|
||
|
||
setTimeout(() => {
|
||
loadingTerminal.remove();
|
||
mainContent.classList.add('animate-in');
|
||
}, 500);
|
||
}, 1000);
|
||
}, 800);
|
||
} else {
|
||
// If not first visit, just add the animation class
|
||
mainContent.classList.add('animate-in');
|
||
}
|
||
}
|
||
|
||
// Kenneth's quotes rotation
|
||
const kennethQuotes = [
|
||
"Attention is the only currency we have in life. Thanks for gifting me yours.",
|
||
"Be cordial or be on your way.",
|
||
"Reality exists regardless of your emotional support of it.",
|
||
"Everything is an expression of its opposite.",
|
||
"Documentation is king.",
|
||
"Trust and verify.",
|
||
"I develop things for humans.",
|
||
"Optimize for simplicity, then performance.",
|
||
"Python for humans.",
|
||
"The power of a clean API.",
|
||
"Clarity over cleverness.",
|
||
"Life is not a race, but there's no speed limit either."
|
||
];
|
||
|
||
const quoteElement = document.getElementById('kenneth-quote');
|
||
if (quoteElement) {
|
||
// Set initial quote to avoid flicker
|
||
const initialQuote = quoteElement.textContent;
|
||
|
||
// Filter out the initial quote
|
||
const filteredQuotes = kennethQuotes.filter(quote =>
|
||
quote !== initialQuote.replace(/^"|"$/g, ''));
|
||
|
||
// Show a random quote on page reload
|
||
const randomIndex = Math.floor(Math.random() * filteredQuotes.length);
|
||
quoteElement.textContent = `"${filteredQuotes[randomIndex]}"`;
|
||
|
||
// Add hover effect for quote change
|
||
quoteElement.addEventListener('mouseover', function() {
|
||
const randomIndex = Math.floor(Math.random() * kennethQuotes.length);
|
||
this.classList.add('opacity-0');
|
||
|
||
setTimeout(() => {
|
||
this.textContent = `"${kennethQuotes[randomIndex]}"`;
|
||
this.classList.remove('opacity-0');
|
||
}, 300);
|
||
});
|
||
}
|
||
|
||
// Animate directory items
|
||
const directoryItems = document.querySelectorAll('.directory-item');
|
||
if (directoryItems.length) {
|
||
directoryItems.forEach((item, index) => {
|
||
item.classList.add('opacity-0');
|
||
setTimeout(() => {
|
||
item.classList.remove('opacity-0');
|
||
item.classList.add('transition-opacity', 'duration-500');
|
||
}, index * 50);
|
||
});
|
||
}
|
||
|
||
// Add copy buttons to code blocks and enhance them
|
||
function setupCodeBlocks() {
|
||
// Wait for Prism.js to finish highlighting
|
||
setTimeout(() => {
|
||
const codeBlocks = document.querySelectorAll('pre:not(.copied-button-added)');
|
||
|
||
codeBlocks.forEach(pre => {
|
||
// Mark as processed
|
||
pre.classList.add('copied-button-added');
|
||
|
||
// Add line numbers to all code blocks
|
||
pre.classList.add('line-numbers');
|
||
|
||
// Create copy button
|
||
const copyButton = document.createElement('button');
|
||
copyButton.className = 'copy-code-button';
|
||
copyButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> Copy';
|
||
|
||
// Add language tag if possible
|
||
const code = pre.querySelector('code');
|
||
if (code && code.className) {
|
||
const match = code.className.match(/language-([a-z]+)/);
|
||
if (match && match[1]) {
|
||
pre.setAttribute('data-language', match[1]);
|
||
|
||
// Add a title attribute with full language name
|
||
const languageMap = {
|
||
'py': 'Python',
|
||
'js': 'JavaScript',
|
||
'jsx': 'React JSX',
|
||
'ts': 'TypeScript',
|
||
'json': 'JSON',
|
||
'css': 'CSS',
|
||
'html': 'HTML',
|
||
'bash': 'Bash',
|
||
'shell': 'Shell',
|
||
'md': 'Markdown',
|
||
'yaml': 'YAML',
|
||
'yml': 'YAML'
|
||
};
|
||
|
||
const langName = languageMap[match[1]] || match[1].charAt(0).toUpperCase() + match[1].slice(1);
|
||
pre.setAttribute('data-language', langName);
|
||
}
|
||
}
|
||
|
||
// Add copy functionality
|
||
copyButton.addEventListener('click', () => {
|
||
const code = pre.querySelector('code');
|
||
const textToCopy = code.textContent;
|
||
|
||
// Copy to clipboard
|
||
navigator.clipboard.writeText(textToCopy).then(() => {
|
||
copyButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"></path></svg> Copied!';
|
||
copyButton.style.backgroundColor = 'rgba(var(--color-success), 0.2)';
|
||
copyButton.style.borderColor = 'rgba(var(--color-success), 0.5)';
|
||
|
||
setTimeout(() => {
|
||
copyButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> Copy';
|
||
copyButton.style.backgroundColor = '';
|
||
copyButton.style.borderColor = '';
|
||
}, 2000);
|
||
})
|
||
.catch(err => {
|
||
copyButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg> Error';
|
||
copyButton.style.backgroundColor = 'rgba(var(--color-error), 0.2)';
|
||
copyButton.style.borderColor = 'rgba(var(--color-error), 0.5)';
|
||
|
||
setTimeout(() => {
|
||
copyButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> Copy';
|
||
copyButton.style.backgroundColor = '';
|
||
copyButton.style.borderColor = '';
|
||
}, 2000);
|
||
|
||
console.error('Failed to copy code: ', err);
|
||
});
|
||
});
|
||
|
||
pre.appendChild(copyButton);
|
||
|
||
// Add editor info bar
|
||
const infoBar = document.createElement('div');
|
||
infoBar.className = 'code-editor-info';
|
||
infoBar.innerHTML = `
|
||
<div class="editor-tabs">
|
||
<div class="editor-tab active">
|
||
${pre.getAttribute('data-language') || 'Code'}
|
||
<span class="close-icon">×</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
pre.parentNode.insertBefore(infoBar, pre);
|
||
|
||
// Wrap pre in a container for styling
|
||
const wrapper = document.createElement('div');
|
||
wrapper.className = 'code-editor-wrapper';
|
||
pre.parentNode.insertBefore(wrapper, pre);
|
||
wrapper.appendChild(pre);
|
||
});
|
||
}, 500); // Wait for Prism.js to initialize
|
||
}
|
||
|
||
// Initial setup
|
||
setupCodeBlocks();
|
||
|
||
// Re-run setup when content changes (for dynamic content)
|
||
const observer = new MutationObserver(setupCodeBlocks);
|
||
observer.observe(document.body, {
|
||
childList: true,
|
||
subtree: true
|
||
});
|
||
|
||
// Explorer panel functionality
|
||
const explorerButton = document.getElementById('explorer-button');
|
||
const explorerPanel = document.getElementById('explorer-panel');
|
||
const explorerClose = document.getElementById('explorer-close');
|
||
|
||
if (explorerButton && explorerPanel && explorerClose) {
|
||
// Open explorer panel
|
||
explorerButton.addEventListener('click', () => {
|
||
explorerPanel.classList.add('show');
|
||
});
|
||
|
||
// Close explorer panel
|
||
explorerClose.addEventListener('click', () => {
|
||
explorerPanel.classList.remove('show');
|
||
});
|
||
|
||
// Close panel when clicking outside
|
||
document.addEventListener('click', (e) => {
|
||
if (!explorerPanel.contains(e.target) && e.target !== explorerButton) {
|
||
explorerPanel.classList.remove('show');
|
||
}
|
||
});
|
||
|
||
// Handle section toggle
|
||
const sectionHeaders = document.querySelectorAll('.explorer-section-header');
|
||
sectionHeaders.forEach(header => {
|
||
header.addEventListener('click', () => {
|
||
const content = header.nextElementSibling;
|
||
if (content.style.display === 'none') {
|
||
content.style.display = 'block';
|
||
} else {
|
||
content.style.display = 'none';
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// Command palette setup
|
||
const commandPalette = document.getElementById('command-palette');
|
||
const commandSearch = document.getElementById('command-search');
|
||
const commandResults = document.getElementById('command-results');
|
||
const shortcutsPanel = document.getElementById('shortcuts-panel');
|
||
const shortcutsClose = document.getElementById('shortcuts-close');
|
||
const shortcutsOverlay = document.getElementById('shortcuts-overlay');
|
||
|
||
// Define available commands
|
||
const commands = [
|
||
{
|
||
id: 'explorer',
|
||
label: 'Toggle Explorer',
|
||
shortcut: 'Ctrl+E',
|
||
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3h18v18H3zM9 3v18M3 9h6M3 15h6"></path></svg>',
|
||
action: () => toggleExplorer()
|
||
},
|
||
{
|
||
id: 'home',
|
||
label: 'Go to Home',
|
||
shortcut: 'Alt+Home',
|
||
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>',
|
||
action: () => window.location.href = '/'
|
||
},
|
||
{
|
||
id: 'software',
|
||
label: 'Software Projects',
|
||
shortcut: 'G S',
|
||
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V8L16 3H8z"></path><path d="M17 21v-8H7v8M7 3v5h8"></path></svg>',
|
||
action: () => window.location.href = '/software'
|
||
},
|
||
{
|
||
id: 'essays',
|
||
label: 'Essays',
|
||
shortcut: 'G E',
|
||
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg>',
|
||
action: () => window.location.href = '/essays'
|
||
},
|
||
{
|
||
id: 'contact',
|
||
label: 'Contact',
|
||
shortcut: 'G C',
|
||
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>',
|
||
action: () => window.location.href = '/contact'
|
||
},
|
||
{
|
||
id: 'shortcuts',
|
||
label: 'Keyboard Shortcuts',
|
||
shortcut: '?',
|
||
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>',
|
||
action: () => toggleShortcutsPanel()
|
||
}
|
||
];
|
||
|
||
// Function to render commands in palette
|
||
function renderCommands(filterText = '') {
|
||
commandResults.innerHTML = '';
|
||
|
||
const filteredCommands = filterText
|
||
? commands.filter(cmd =>
|
||
cmd.label.toLowerCase().includes(filterText.toLowerCase()) ||
|
||
cmd.id.toLowerCase().includes(filterText.toLowerCase()))
|
||
: commands;
|
||
|
||
if (filteredCommands.length === 0) {
|
||
const noResults = document.createElement('div');
|
||
noResults.className = 'command-palette-item';
|
||
noResults.innerHTML = `
|
||
<div class="command-palette-icon">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="10"></circle>
|
||
<line x1="12" y1="8" x2="12" y2="12"></line>
|
||
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
||
</svg>
|
||
</div>
|
||
<span class="command-palette-label">No matching commands found</span>
|
||
`;
|
||
commandResults.appendChild(noResults);
|
||
return;
|
||
}
|
||
|
||
filteredCommands.forEach((cmd, index) => {
|
||
const item = document.createElement('div');
|
||
item.className = 'command-palette-item';
|
||
if (index === 0) item.classList.add('selected');
|
||
|
||
item.innerHTML = `
|
||
<div class="command-palette-icon">${cmd.icon}</div>
|
||
<span class="command-palette-label">${cmd.label}</span>
|
||
<div class="command-palette-shortcut">
|
||
${cmd.shortcut.split(' ').map(key =>
|
||
`<span class="command-palette-key">${key}</span>`
|
||
).join('')}
|
||
</div>
|
||
`;
|
||
|
||
item.addEventListener('click', () => {
|
||
cmd.action();
|
||
toggleCommandPalette(false);
|
||
});
|
||
|
||
commandResults.appendChild(item);
|
||
});
|
||
}
|
||
|
||
// Toggle command palette
|
||
function toggleCommandPalette(show = true) {
|
||
if (show) {
|
||
commandPalette.classList.add('show');
|
||
shortcutsOverlay.classList.add('show');
|
||
commandSearch.value = '';
|
||
renderCommands();
|
||
setTimeout(() => commandSearch.focus(), 100);
|
||
} else {
|
||
commandPalette.classList.remove('show');
|
||
shortcutsOverlay.classList.remove('show');
|
||
}
|
||
}
|
||
|
||
// Toggle shortcuts panel
|
||
function toggleShortcutsPanel(show = true) {
|
||
if (show) {
|
||
shortcutsPanel.classList.add('show');
|
||
shortcutsOverlay.classList.add('show');
|
||
} else {
|
||
shortcutsPanel.classList.remove('show');
|
||
shortcutsOverlay.classList.remove('show');
|
||
}
|
||
}
|
||
|
||
// Toggle explorer
|
||
function toggleExplorer(show) {
|
||
if (show === undefined) {
|
||
explorerPanel.classList.toggle('show');
|
||
} else if (show) {
|
||
explorerPanel.classList.add('show');
|
||
} else {
|
||
explorerPanel.classList.remove('show');
|
||
}
|
||
}
|
||
|
||
// Command palette search functionality
|
||
if (commandSearch) {
|
||
commandSearch.addEventListener('input', (e) => {
|
||
renderCommands(e.target.value);
|
||
});
|
||
|
||
commandSearch.addEventListener('keydown', (e) => {
|
||
const items = commandResults.querySelectorAll('.command-palette-item');
|
||
const selected = commandResults.querySelector('.selected');
|
||
let index = Array.from(items).indexOf(selected);
|
||
|
||
switch (e.key) {
|
||
case 'ArrowDown':
|
||
e.preventDefault();
|
||
if (index < items.length - 1) {
|
||
if (selected) selected.classList.remove('selected');
|
||
items[index + 1].classList.add('selected');
|
||
items[index + 1].scrollIntoView({ block: 'nearest' });
|
||
}
|
||
break;
|
||
|
||
case 'ArrowUp':
|
||
e.preventDefault();
|
||
if (index > 0) {
|
||
if (selected) selected.classList.remove('selected');
|
||
items[index - 1].classList.add('selected');
|
||
items[index - 1].scrollIntoView({ block: 'nearest' });
|
||
}
|
||
break;
|
||
|
||
case 'Enter':
|
||
e.preventDefault();
|
||
if (selected) {
|
||
selected.click();
|
||
}
|
||
break;
|
||
|
||
case 'Escape':
|
||
e.preventDefault();
|
||
toggleCommandPalette(false);
|
||
break;
|
||
}
|
||
});
|
||
}
|
||
|
||
// Close shortcuts panel
|
||
if (shortcutsClose) {
|
||
shortcutsClose.addEventListener('click', () => {
|
||
toggleShortcutsPanel(false);
|
||
});
|
||
}
|
||
|
||
// Close modals when clicking on overlay
|
||
if (shortcutsOverlay) {
|
||
shortcutsOverlay.addEventListener('click', () => {
|
||
toggleCommandPalette(false);
|
||
toggleShortcutsPanel(false);
|
||
});
|
||
}
|
||
|
||
// Handle shortcuts indicator click
|
||
const shortcutsIndicator = document.getElementById('shortcuts-indicator');
|
||
if (shortcutsIndicator) {
|
||
shortcutsIndicator.addEventListener('click', () => {
|
||
toggleShortcutsPanel(true);
|
||
});
|
||
}
|
||
|
||
// Global keyboard shortcuts
|
||
document.addEventListener('keydown', (e) => {
|
||
// Toggle explorer (Ctrl+E or Cmd+E)
|
||
if ((e.ctrlKey || e.metaKey) && e.key === 'e') {
|
||
e.preventDefault();
|
||
toggleExplorer();
|
||
}
|
||
|
||
// Command palette (Ctrl+P or Cmd+P)
|
||
if ((e.ctrlKey || e.metaKey) && e.key === 'p') {
|
||
e.preventDefault();
|
||
toggleCommandPalette(true);
|
||
}
|
||
|
||
// Shortcuts panel (?)
|
||
if (e.key === '?' && !e.ctrlKey && !e.metaKey && !e.altKey &&
|
||
!(document.activeElement instanceof HTMLInputElement) &&
|
||
!(document.activeElement instanceof HTMLTextAreaElement)) {
|
||
e.preventDefault();
|
||
toggleShortcutsPanel(true);
|
||
}
|
||
|
||
// Quick navigation with g prefix
|
||
if (e.key === 'g' && !shortcutsPanel.classList.contains('show') &&
|
||
!commandPalette.classList.contains('show') &&
|
||
!(document.activeElement instanceof HTMLInputElement) &&
|
||
!(document.activeElement instanceof HTMLTextAreaElement)) {
|
||
|
||
const handleSecondKey = (e2) => {
|
||
document.removeEventListener('keydown', handleSecondKey);
|
||
|
||
if (e2.key === 's') {
|
||
window.location.href = '/software';
|
||
} else if (e2.key === 'e') {
|
||
window.location.href = '/essays';
|
||
} else if (e2.key === 'c') {
|
||
window.location.href = '/contact';
|
||
}
|
||
};
|
||
|
||
document.addEventListener('keydown', handleSecondKey);
|
||
}
|
||
|
||
// Home shortcut (Alt+Home)
|
||
if (e.altKey && e.key === 'Home') {
|
||
e.preventDefault();
|
||
window.location.href = '/';
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |