Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Docs site

on:
push:
branches: [main]
paths:
- "docs/site/**"
- ".github/workflows/docs.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Build site
working-directory: docs/site
run: python build.py
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/site/dist

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
4 changes: 4 additions & 0 deletions docs/site/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist/
__pycache__/
*.pyc
.venv/
30 changes: 30 additions & 0 deletions docs/site/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Optune docs site

Static documentation site, generated by `build.py` from Markdown content under `pages/`.
Inspired by [peekaboo.sh](https://peekaboo.sh) — same shell, our content and palette.

## Build

```bash
cd docs/site
python3 build.py
open dist/index.html
```

## Add a page

1. Create `pages/<slug>.md` with frontmatter:
```
---
title: My Page
section: GUIDES
order: 10
description: Short SEO blurb.
---
# My Page
```
2. Re-run `build.py`. The sidebar regenerates automatically from `section` + `order`.

## Deploy

CI publishes `dist/` to GitHub Pages on every push to `main` that touches `docs/site/**`.
13 changes: 13 additions & 0 deletions docs/site/_assets/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
103 changes: 103 additions & 0 deletions docs/site/_assets/site.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Optune docs site — Peekaboo-inspired interactions.

(() => {
// Theme toggle
const root = document.documentElement;
const toggle = document.querySelector('[data-theme-toggle]');
const label = toggle?.querySelector('.theme-toggle__label');
const setLabel = () => {
if (!label) return;
label.textContent = root.dataset.theme === 'light' ? 'Light' : 'Dark';
};
setLabel();
toggle?.addEventListener('click', () => {
const next = root.dataset.theme === 'light' ? 'dark' : 'light';
root.dataset.theme = next;
try { localStorage.setItem('optune-theme', next); } catch {}
setLabel();
});

// Sidebar mobile
const navToggle = document.querySelector('.nav-toggle');
const sidebar = document.querySelector('.sidebar');
navToggle?.addEventListener('click', () => {
const open = sidebar.classList.toggle('open');
navToggle.setAttribute('aria-expanded', open ? 'true' : 'false');
});
document.addEventListener('click', (e) => {
if (!sidebar?.classList.contains('open')) return;
if (sidebar.contains(e.target) || navToggle.contains(e.target)) return;
sidebar.classList.remove('open');
navToggle.setAttribute('aria-expanded', 'false');
});

// Sidebar search filter
const search = document.querySelector('.search input');
const navLinks = Array.from(document.querySelectorAll('.nav-link'));
search?.addEventListener('input', () => {
const q = search.value.trim().toLowerCase();
navLinks.forEach(a => {
const match = !q || a.textContent.toLowerCase().includes(q);
a.style.display = match ? '' : 'none';
});
// Hide section headings that have no visible children
document.querySelectorAll('nav section').forEach(sec => {
const visible = Array.from(sec.querySelectorAll('.nav-link')).some(a => a.style.display !== 'none');
sec.style.display = visible ? '' : 'none';
});
});

// Copy buttons on pre blocks
document.querySelectorAll('.doc pre').forEach(pre => {
const btn = document.createElement('button');
btn.className = 'copy';
btn.type = 'button';
btn.textContent = 'Copy';
btn.addEventListener('click', async () => {
const text = pre.querySelector('code')?.textContent ?? pre.textContent;
try {
await navigator.clipboard.writeText(text.replace(/\n$/, ''));
btn.textContent = 'Copied';
btn.classList.add('copied');
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 1400);
} catch {
btn.textContent = 'Press ⌘C';
}
});
pre.appendChild(btn);
});

// Anchor links on headings
document.querySelectorAll('.doc :is(h2,h3,h4)').forEach(h => {
if (!h.id) return;
const a = document.createElement('a');
a.className = 'anchor';
a.href = `#${h.id}`;
a.setAttribute('aria-label', `Anchor link to ${h.textContent}`);
a.textContent = '#';
h.prepend(a);
});

// TOC active highlight via IntersectionObserver
const tocLinks = Array.from(document.querySelectorAll('.toc a'));
if (tocLinks.length) {
const tocMap = new Map(tocLinks.map(a => [a.getAttribute('href').slice(1), a]));
const headings = Array.from(document.querySelectorAll('.doc :is(h2,h3,h4)')).filter(h => tocMap.has(h.id));
const visible = new Set();
const setActive = () => {
tocLinks.forEach(a => a.classList.remove('active'));
// Pick the topmost visible heading
const ordered = headings.filter(h => visible.has(h.id));
const winner = ordered[0] || headings.find(h => visible.has(h.id));
if (winner) tocMap.get(winner.id)?.classList.add('active');
};
const obs = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) visible.add(e.target.id);
else visible.delete(e.target.id);
});
setActive();
}, { rootMargin: '-15% 0% -75% 0%', threshold: 0 });
headings.forEach(h => obs.observe(h));
}
})();
Loading