Files

219 lines
8.5 KiB
HTML

<script>
// Inject subtitle + framework version chip under sidebar logo.
// The chip's version literal is sourced from tinytorch/settings.ini and
// kept in sync by the tinytorch-publish-live workflow's UPDATE_VERSION
// step on each release. Update marker for sed: TINYTORCH_VERSION_CHIP.
document.addEventListener('DOMContentLoaded', function() {
const sidebarLogo = document.querySelector('.sidebar-logo');
if (!sidebarLogo) return;
// The logo <img> sits inside Quarto's <a class="sidebar-logo-link"> wrapper.
// Inserting our subtitle as a sibling of the <img> would nest the subtitle's
// link INSIDE that anchor — invalid HTML, so the browser fires the outer logo
// link (./index.html) instead of navigating to the textbook. Insert AFTER the
// logo anchor, as a child of .sidebar-header, so our links are not nested.
const logoLink = sidebarLogo.closest('.sidebar-logo-link') || sidebarLogo;
const anchorHost = logoLink.parentElement;
// 1. Existing subtitle — what TinyTorch IS in relation to the textbook.
const subtitle = document.createElement('div');
subtitle.className = 'sidebar-subtitle';
subtitle.innerHTML = 'A Build-It-Yourself Companion to the<br><a href="https://mlsysbook.ai" style="color: #D4740C; text-decoration: none;">Machine Learning Systems</a> textbook';
anchorHost.insertBefore(subtitle, logoLink.nextSibling);
// 2. Framework version chip — dynamically themed by version hash so each
// release cycle gets an assigned, distinctive high-contrast accent.
const chipWrap = document.createElement('div');
chipWrap.className = 'tinytorch-version-chip-wrap';
const chip = document.createElement('a');
chip.className = 'tinytorch-version-chip';
chip.href = 'https://github.com/harvard-edge/cs249r_book/releases?q=tinytorch';
chip.target = '_blank';
chip.rel = 'noopener';
chip.innerHTML = '<span class="tinytorch-version-dot"></span><span class="tinytorch-version-fire">🔥 </span><span class="tinytorch-chip-label">TinyTorch&nbsp;</span><span class="tinytorch-version-tag">v0.2.2</span><span class="tinytorch-version-arrow">&nbsp;→</span>'; // TINYTORCH_VERSION_CHIP
// Curated palette of WCAG AA-compliant accent colors
const PALETTE = [
{ name: 'amber', light: '#D4740C', bg: 'rgba(212, 116, 12, 0.08)', bgHover: 'rgba(212, 116, 12, 0.18)', dark: '#F59E0B', darkBg: 'rgba(245, 158, 11, 0.12)', darkBgHover: 'rgba(245, 158, 11, 0.22)' },
{ name: 'emerald', light: '#059669', bg: 'rgba(5, 150, 105, 0.08)', bgHover: 'rgba(5, 150, 105, 0.18)', dark: '#10B981', darkBg: 'rgba(16, 185, 129, 0.12)', darkBgHover: 'rgba(16, 185, 129, 0.22)' },
{ name: 'cobalt', light: '#2563EB', bg: 'rgba(37, 99, 235, 0.08)', bgHover: 'rgba(37, 99, 235, 0.18)', dark: '#60A5FA', darkBg: 'rgba(96, 165, 250, 0.12)', darkBgHover: 'rgba(96, 165, 250, 0.22)' },
{ name: 'violet', light: '#7C3AED', bg: 'rgba(124, 58, 237, 0.08)', bgHover: 'rgba(124, 58, 237, 0.18)', dark: '#A78BFA', darkBg: 'rgba(167, 139, 250, 0.12)', darkBgHover: 'rgba(167, 139, 250, 0.22)' },
{ name: 'rose', light: '#E11D48', bg: 'rgba(225, 29, 72, 0.08)', bgHover: 'rgba(225, 29, 72, 0.18)', dark: '#FB7185', darkBg: 'rgba(251, 113, 133, 0.12)', darkBgHover: 'rgba(251, 113, 133, 0.22)' },
{ name: 'cyan', light: '#0891B2', bg: 'rgba(8, 145, 178, 0.08)', bgHover: 'rgba(8, 145, 178, 0.18)', dark: '#22D3EE', darkBg: 'rgba(34, 211, 238, 0.12)', darkBgHover: 'rgba(34, 211, 238, 0.22)' },
{ name: 'coral', light: '#EA580C', bg: 'rgba(234, 88, 12, 0.08)', bgHover: 'rgba(234, 88, 12, 0.18)', dark: '#FB923C', darkBg: 'rgba(251, 146, 60, 0.12)', darkBgHover: 'rgba(251, 146, 60, 0.22)' },
{ name: 'purple', light: '#9333EA', bg: 'rgba(147, 51, 234, 0.08)', bgHover: 'rgba(147, 51, 234, 0.18)', dark: '#C084FC', darkBg: 'rgba(192, 132, 252, 0.12)', darkBgHover: 'rgba(192, 132, 252, 0.22)' },
];
function applyTheme(verText) {
const versionMatch = (verText || '').match(/v?(\d+\.\d+\.\d+)/);
const versionStr = versionMatch ? versionMatch[1] : '0.1.16';
let hash = 0;
for (let i = 0; i < versionStr.length; i++) {
hash = ((hash << 5) - hash) + versionStr.charCodeAt(i);
hash |= 0;
}
const theme = PALETTE[Math.abs(hash) % PALETTE.length];
chip.style.setProperty('--pill-color', theme.light);
chip.style.setProperty('--pill-bg', theme.bg);
chip.style.setProperty('--pill-bg-hover', theme.bgHover);
chip.style.setProperty('--pill-dark-color', theme.dark);
chip.style.setProperty('--pill-dark-bg', theme.darkBg);
chip.style.setProperty('--pill-dark-bg-hover', theme.darkBgHover);
}
applyTheme(chip.innerText);
chipWrap.appendChild(chip);
subtitle.parentNode.insertBefore(chipWrap, subtitle.nextSibling);
// Live version revalidation: fetches release manifest with cache busting to
// ensure Safari and browsers serving cached HTML/bfcache update dynamically.
function refreshLiveVersion() {
fetch('/tinytorch/release-manifest.json?_t=' + Date.now(), { cache: 'no-store' })
.then(function(res) { return res.ok ? res.json() : null; })
.then(function(manifest) {
if (!manifest || !manifest.releaseId) return;
const liveTag = 'v' + manifest.releaseId.replace(/^v/, '');
const tagEl = chip.querySelector('.tinytorch-version-tag');
if (tagEl && tagEl.textContent !== liveTag) {
tagEl.textContent = liveTag;
applyTheme(liveTag);
}
})
.catch(function() {});
}
refreshLiveVersion();
window.addEventListener('pageshow', function(evt) {
if (evt.persisted) {
refreshLiveVersion();
}
});
});
</script>
<style>
.sidebar-subtitle {
text-align: center;
font-size: 0.8rem;
color: #6b7280;
line-height: 1.4;
padding: 0 1rem 0.5rem 1rem;
margin-top: 0.5rem;
}
.sidebar-subtitle a:hover {
color: #f97316 !important;
}
/* Version chip — divider above + dynamic release-colored pill */
.tinytorch-version-chip-wrap {
container-type: inline-size;
text-align: center;
margin: 0 0.5rem;
padding: 0.4rem 0.2rem 0.65rem;
border-top: 1px solid #e5e7eb;
}
.tinytorch-version-chip {
display: inline-flex;
align-items: center;
justify-content: center;
max-width: 100%;
box-sizing: border-box;
padding: 0.16rem 0.5rem;
border: 1px solid var(--pill-color, #D4740C);
border-radius: 999px;
background: var(--pill-bg, rgba(212, 116, 12, 0.08));
color: var(--pill-color, #D4740C) !important;
font-size: 0.70rem;
font-weight: 600;
text-decoration: none !important;
white-space: nowrap;
letter-spacing: 0.01em;
transition: all 0.2s ease;
overflow: hidden;
text-overflow: ellipsis;
}
.tinytorch-version-chip:hover {
background: var(--pill-bg-hover, rgba(212, 116, 12, 0.18));
color: var(--pill-color, #D4740C) !important;
text-decoration: none;
}
.tinytorch-version-dot {
display: inline-block;
flex-shrink: 0;
width: 6px;
height: 6px;
margin-right: 5px;
border-radius: 50%;
background-color: var(--pill-color, #D4740C);
box-shadow: 0 0 6px var(--pill-color, #D4740C);
animation: pulse-dot 2.5s infinite ease-in-out;
}
.tinytorch-version-fire {
flex-shrink: 0;
margin-right: 2px;
}
.tinytorch-chip-label {
white-space: nowrap;
}
.tinytorch-version-tag {
white-space: nowrap;
}
.tinytorch-version-arrow {
flex-shrink: 0;
}
@container (max-width: 215px) {
.tinytorch-version-chip {
font-size: 0.66rem;
padding: 0.14rem 0.42rem;
}
}
@container (max-width: 185px) {
.tinytorch-chip-label {
display: none;
}
.tinytorch-version-chip {
font-size: 0.68rem;
}
}
@media (max-width: 1200px) {
.tinytorch-version-chip-wrap {
margin: 0 0.25rem;
}
}
@media (max-width: 1050px) {
.tinytorch-chip-label {
display: none;
}
}
@keyframes pulse-dot {
0% { transform: scale(0.9); opacity: 0.75; }
50% { transform: scale(1.3); opacity: 1; box-shadow: 0 0 8px var(--pill-color, #D4740C); }
100% { transform: scale(0.9); opacity: 0.75; }
}
/* Dark-mode overrides */
[data-bs-theme="dark"] .tinytorch-version-chip-wrap {
border-top-color: #454d55;
}
[data-bs-theme="dark"] .tinytorch-version-chip {
border-color: var(--pill-dark-color, #F59E0B);
color: var(--pill-dark-color, #F59E0B) !important;
background: var(--pill-dark-bg, rgba(245, 158, 11, 0.12));
}
[data-bs-theme="dark"] .tinytorch-version-chip:hover {
background: var(--pill-dark-bg-hover, rgba(245, 158, 11, 0.22));
color: var(--pill-dark-color, #F59E0B) !important;
}
[data-bs-theme="dark"] .tinytorch-version-dot {
background-color: var(--pill-dark-color, #F59E0B);
box-shadow: 0 0 6px var(--pill-dark-color, #F59E0B);
}
</style>