Feat: documentation, UI/UX
This commit is contained in:
305
website/public/docs.css
Normal file
305
website/public/docs.css
Normal file
@@ -0,0 +1,305 @@
|
||||
body:has(.docs-layout) {
|
||||
max-width: 100vw;
|
||||
padding: 1rem 2rem;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.docs-layout {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
height: calc(100vh - 3.5rem);
|
||||
}
|
||||
|
||||
.docs-sidebar {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.docs-sidebar h3 {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-muted);
|
||||
margin: 1.25rem 0 0.25rem;
|
||||
padding: 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.docs-sidebar h3:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.docs-sidebar button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-family: 'VCR OSD Mono', monospace;
|
||||
font-size: 0.8rem;
|
||||
background: none;
|
||||
border: none;
|
||||
border-left: 2px solid transparent;
|
||||
color: var(--text-dim);
|
||||
padding: 0.2rem 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.docs-sidebar button:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.docs-sidebar button.active {
|
||||
color: var(--text);
|
||||
border-left-color: var(--text);
|
||||
}
|
||||
|
||||
.docs-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
overflow-wrap: break-word;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.docs-content article {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.docs-content article.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.docs-content h1 {
|
||||
font-size: 1.3rem;
|
||||
margin: 0 0 1rem;
|
||||
border-bottom: 1px solid var(--text-muted);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.docs-content h2,
|
||||
.docs-content h3,
|
||||
.docs-content h4,
|
||||
.docs-content h5 {
|
||||
background: var(--surface);
|
||||
margin: 1.5rem 0 0.5rem;
|
||||
padding: 0.3rem 0.5rem;
|
||||
}
|
||||
|
||||
.docs-content h2 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.docs-content h3 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.docs-content h4 {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.docs-content h5 {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.docs-content p {
|
||||
margin: 0.5rem 0;
|
||||
text-align: left;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.docs-content pre {
|
||||
background: var(--surface);
|
||||
border-left: 2px solid var(--text-muted);
|
||||
padding: 0.75rem 1rem;
|
||||
margin: 1rem 0;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.docs-content code {
|
||||
font-family: 'VCR OSD Mono', monospace;
|
||||
}
|
||||
|
||||
.docs-content p code,
|
||||
.docs-content li code,
|
||||
.docs-content td code {
|
||||
background: var(--surface);
|
||||
padding: 0.1rem 0.3rem;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.docs-content table {
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0;
|
||||
width: 100%;
|
||||
table-layout: auto;
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.docs-content th,
|
||||
.docs-content td {
|
||||
padding: 0.25rem 0.75rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.docs-content th {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.docs-content td:first-child {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.docs-content tr:nth-child(even) {
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.docs-content ul,
|
||||
.docs-content ol {
|
||||
padding-left: 1.5rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.docs-content ul {
|
||||
list-style-type: "- ";
|
||||
}
|
||||
|
||||
.docs-content ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.docs-content ul ul {
|
||||
list-style-type: none;
|
||||
border-left: 1px solid var(--text-muted);
|
||||
padding-left: 1rem;
|
||||
margin: 0.15rem 0;
|
||||
}
|
||||
|
||||
.docs-content li {
|
||||
margin: 0.3rem 0;
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.docs-content ul ul li {
|
||||
color: var(--text-dim);
|
||||
margin: 0.15rem 0;
|
||||
}
|
||||
|
||||
.docs-content a {
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.docs-content strong {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.docs-content em {
|
||||
font-style: italic;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.docs-content del {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.docs-content blockquote {
|
||||
border-left: 2px solid var(--text-muted);
|
||||
padding: 0.25rem 1rem;
|
||||
margin: 1rem 0;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.docs-content blockquote p {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
.docs-content hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--text-muted);
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.docs-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.docs-back {
|
||||
font-family: 'VCR OSD Mono', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.docs-back:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.docs-notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.docs-notice.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.docs-notice button {
|
||||
font-family: 'VCR OSD Mono', monospace;
|
||||
font-size: 0.75rem;
|
||||
background: none;
|
||||
border: 1px solid var(--text-muted);
|
||||
color: var(--text);
|
||||
padding: 0.15rem 0.5rem;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.docs-notice button:hover {
|
||||
border-color: var(--text);
|
||||
}
|
||||
|
||||
/* Forth syntax highlighting — monochrome via weight/opacity */
|
||||
.f-emit { font-weight: bold; }
|
||||
.f-com { opacity: 0.5; font-style: italic; }
|
||||
.f-num { opacity: 0.6; }
|
||||
.f-note { font-weight: bold; }
|
||||
.f-snd { text-decoration: underline; }
|
||||
.f-par { opacity: 0.6; font-style: italic; }
|
||||
.f-stack { opacity: 0.6; }
|
||||
.f-prob { opacity: 0.6; }
|
||||
.f-ctx { opacity: 0.5; }
|
||||
.f-var { opacity: 0.6; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body:has(.docs-layout) {
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.docs-layout {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.docs-sidebar {
|
||||
width: 100%;
|
||||
overflow-y: visible;
|
||||
border-bottom: 1px solid var(--text-muted);
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.docs-content {
|
||||
overflow-y: visible;
|
||||
}
|
||||
}
|
||||
27
website/public/docs.js
Normal file
27
website/public/docs.js
Normal file
@@ -0,0 +1,27 @@
|
||||
function dismissNotice() {
|
||||
document.getElementById('docs-notice').classList.add('hidden');
|
||||
}
|
||||
|
||||
function showTopic(s, t) {
|
||||
document.querySelectorAll('.docs-content article').forEach(a => a.classList.remove('visible'));
|
||||
document.querySelectorAll('.docs-sidebar button').forEach(b => b.classList.remove('active'));
|
||||
const article = document.getElementById('topic-s' + s + 't' + t);
|
||||
const btn = document.querySelector('[data-s="' + s + '"][data-t="' + t + '"]');
|
||||
if (article) article.classList.add('visible');
|
||||
if (btn) btn.classList.add('active');
|
||||
history.replaceState(null, '', '#s' + s + 't' + t);
|
||||
document.querySelector('.docs-content').scrollTop = 0;
|
||||
}
|
||||
|
||||
function parseHash() {
|
||||
const m = location.hash.match(/^#s(\d+)t(\d+)$/);
|
||||
return m ? [parseInt(m[1]), parseInt(m[2])] : [0, 0];
|
||||
}
|
||||
|
||||
document.querySelectorAll('.docs-sidebar button').forEach(btn => {
|
||||
btn.addEventListener('click', () => showTopic(+btn.dataset.s, +btn.dataset.t));
|
||||
});
|
||||
|
||||
const [s, t] = parseHash();
|
||||
showTopic(s, t);
|
||||
window.addEventListener('hashchange', () => { const [s, t] = parseHash(); showTopic(s, t); });
|
||||
338
website/src/pages/docs.astro
Normal file
338
website/src/pages/docs.astro
Normal file
@@ -0,0 +1,338 @@
|
||||
---
|
||||
import fs from 'node:fs';
|
||||
|
||||
// --- Forth syntax highlighter ---
|
||||
|
||||
const EMIT = new Set(['.', '.!']);
|
||||
const SOUNDS = new Set([
|
||||
'sound', 's', 'saw', 'sine', 'kick', 'hat', 'snare', 'modal', 'noise',
|
||||
'square', 'tri', 'pulse', 'clap', 'rim', 'crash', 'fm', 'sample', 'plaits',
|
||||
'analog', 'waveshaping', 'granular', 'string', 'chord', 'speech', 'sub',
|
||||
'super', 'wt', 'input', 'hh',
|
||||
]);
|
||||
const PARAMS = new Set([
|
||||
'freq', 'note', 'gain', 'decay', 'attack', 'release', 'lpf', 'hpf', 'bpf',
|
||||
'verb', 'delay', 'pan', 'orbit', 'harmonics', 'distort', 'speed', 'voice',
|
||||
'dur', 'sustain', 'delaytime', 'delayfb', 'chorus', 'phaser', 'flanger',
|
||||
'crush', 'fold', 'wrap', 'resonance', 'begin', 'end', 'velocity', 'chan',
|
||||
'dev', 'ccnum', 'ccout', 'bend', 'pressure', 'program', 'tilt', 'slope',
|
||||
'sub_gain', 'sub_oct', 'feedback', 'depth', 'sweep', 'comb', 'damping',
|
||||
'detune', 'timbre', 'morph', 'color', 'model', 'cutoff',
|
||||
]);
|
||||
const STACK = new Set([
|
||||
'dup', 'drop', 'swap', 'over', 'rot', 'nip', 'tuck', '2dup', '2drop',
|
||||
]);
|
||||
const PROB = new Set([
|
||||
'chance', 'prob', 'choose', 'wchoose', 'cycle', 'pcycle', 'bounce',
|
||||
'rand', 'exprand', 'logrand', 'coin', 'seed', 'often', 'sometimes',
|
||||
'rarely', 'always', 'never', 'almostAlways', 'almostNever', 'every',
|
||||
'except', 'bjork', 'pbjork', 'shuffle',
|
||||
]);
|
||||
const KEYWORDS = new Set([
|
||||
':', ';', 'if', 'then', 'else', 'do', 'loop', '?', '!?', 'ifelse',
|
||||
'select', 'apply', 'times', 'forget', 'all', 'noall', 'clear', 'm.',
|
||||
]);
|
||||
const CONTEXT = new Set([
|
||||
'step', 'beat', 'pattern', 'tempo', 'phase', 'runs', 'iter', 'fill', 'stepdur',
|
||||
]);
|
||||
const NOTE_RE = /^[a-g][#sb]?\d+$/i;
|
||||
const NUM_RE = /^-?\d+(\.\d+)?$/;
|
||||
|
||||
function classifyToken(word: string): string | null {
|
||||
if (EMIT.has(word)) return 'f-emit';
|
||||
if (SOUNDS.has(word)) return 'f-snd';
|
||||
if (PARAMS.has(word)) return 'f-par';
|
||||
if (STACK.has(word)) return 'f-stack';
|
||||
if (PROB.has(word)) return 'f-prob';
|
||||
if (KEYWORDS.has(word)) return 'f-kw';
|
||||
if (CONTEXT.has(word)) return 'f-ctx';
|
||||
if (NOTE_RE.test(word)) return 'f-note';
|
||||
if (NUM_RE.test(word)) return 'f-num';
|
||||
if (word.length > 1 && (word[0] === '@' || word[0] === '!' || word[0] === ',')) return 'f-var';
|
||||
return null;
|
||||
}
|
||||
|
||||
function highlightForth(code: string): string {
|
||||
return code.split('\n').map(line => {
|
||||
let result = '';
|
||||
let i = 0;
|
||||
while (i < line.length) {
|
||||
if (line[i] === ' ' || line[i] === '\t') { result += line[i]; i++; continue; }
|
||||
if (line[i] === ';' && line[i + 1] === ';') {
|
||||
result += `<span class="f-com">${escapeHtml(line.slice(i))}</span>`;
|
||||
break;
|
||||
}
|
||||
if (line[i] === '"') {
|
||||
let end = line.indexOf('"', i + 1);
|
||||
if (end === -1) end = line.length - 1;
|
||||
result += escapeHtml(line.slice(i, end + 1));
|
||||
i = end + 1;
|
||||
continue;
|
||||
}
|
||||
if (line[i] === '(' || line[i] === ')') { result += line[i]; i++; continue; }
|
||||
let end = i;
|
||||
while (end < line.length && line[end] !== ' ' && line[end] !== '\t') end++;
|
||||
const word = line.slice(i, end);
|
||||
const cls = classifyToken(word);
|
||||
result += cls ? `<span class="${cls}">${escapeHtml(word)}</span>` : escapeHtml(word);
|
||||
i = end;
|
||||
}
|
||||
return result;
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
// --- Markdown renderer ---
|
||||
|
||||
function escapeHtml(s: string): string {
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function inline(text: string): string {
|
||||
const codes: string[] = [];
|
||||
text = text.replace(/`([^`]+)`/g, (_, code) => { codes.push(code); return `\x00${codes.length - 1}\x00`; });
|
||||
text = escapeHtml(text);
|
||||
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
text = text.replace(/~~([^~]+)~~/g, '<del>$1</del>');
|
||||
text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
||||
text = text.replace(/_([^_]+)_/g, '<em>$1</em>');
|
||||
text = text.replace(/\x00(\d+)\x00/g, (_, i) => `<code>${escapeHtml(codes[+i])}</code>`);
|
||||
return text;
|
||||
}
|
||||
|
||||
function parseTableCells(line: string, tag: string): string {
|
||||
return line.split('|').slice(1, -1).map(cell => `<${tag}>${inline(cell.trim())}</${tag}>`).join('');
|
||||
}
|
||||
|
||||
function renderMarkdown(md: string): string {
|
||||
const lines = md.split('\n');
|
||||
let html = '';
|
||||
let i = 0;
|
||||
|
||||
while (i < lines.length) {
|
||||
const line = lines[i];
|
||||
if (line.trim() === '') { i++; continue; }
|
||||
|
||||
if (line.startsWith('### ')) { html += `<h3>${inline(line.slice(4))}</h3>`; i++; continue; }
|
||||
if (line.startsWith('## ')) { html += `<h2>${inline(line.slice(3))}</h2>`; i++; continue; }
|
||||
if (line.startsWith('# ')) { html += `<h1>${inline(line.slice(2))}</h1>`; i++; continue; }
|
||||
|
||||
if (/^---+$|^\*\*\*+$/.test(line.trim())) { html += '<hr>'; i++; continue; }
|
||||
|
||||
if (line.startsWith('> ')) {
|
||||
html += '<blockquote>';
|
||||
while (i < lines.length && lines[i].startsWith('> ')) {
|
||||
html += `<p>${inline(lines[i].slice(2))}</p>`;
|
||||
i++;
|
||||
}
|
||||
html += '</blockquote>';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith('```')) {
|
||||
const lang = line.slice(3).trim();
|
||||
i++;
|
||||
let code = '';
|
||||
while (i < lines.length && !lines[i].startsWith('```')) {
|
||||
code += (code ? '\n' : '') + lines[i];
|
||||
i++;
|
||||
}
|
||||
i++;
|
||||
html += lang === 'forth'
|
||||
? `<pre><code>${highlightForth(code)}</code></pre>`
|
||||
: `<pre><code>${escapeHtml(code)}</code></pre>`;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith('|')) {
|
||||
html += '<table>';
|
||||
html += `<tr>${parseTableCells(line, 'th')}</tr>`;
|
||||
i++;
|
||||
if (i < lines.length && /^\|[\s:|-]+\|$/.test(lines[i])) i++;
|
||||
while (i < lines.length && lines[i].startsWith('|')) {
|
||||
html += `<tr>${parseTableCells(lines[i], 'td')}</tr>`;
|
||||
i++;
|
||||
}
|
||||
html += '</table>';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (/^\d+\.\s/.test(line)) {
|
||||
html += '<ol>';
|
||||
while (i < lines.length && (/^\d+\.\s/.test(lines[i]) || /^\s+[*-]\s/.test(lines[i]))) {
|
||||
if (/^\d+\.\s/.test(lines[i])) {
|
||||
html += `<li>${inline(lines[i].replace(/^\d+\.\s/, ''))}`;
|
||||
i++;
|
||||
if (i < lines.length && /^\s+[*-]\s/.test(lines[i])) {
|
||||
html += '<ul>';
|
||||
while (i < lines.length && /^\s+[*-]\s/.test(lines[i])) {
|
||||
html += `<li>${inline(lines[i].replace(/^\s*[*-]\s/, ''))}</li>`;
|
||||
i++;
|
||||
}
|
||||
html += '</ul>';
|
||||
}
|
||||
html += '</li>';
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
html += '</ol>';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (/^(\s*)[*-]\s/.test(line)) {
|
||||
let depth = 0;
|
||||
let first = true;
|
||||
html += '<ul>';
|
||||
while (i < lines.length && /^(\s*)[*-]\s/.test(lines[i])) {
|
||||
const indent = lines[i].match(/^(\s*)/)![1].length;
|
||||
const level = Math.floor(indent / 2);
|
||||
if (level > depth) {
|
||||
while (level > depth) { html += '<ul>'; depth++; }
|
||||
} else if (level < depth) {
|
||||
html += '</li>';
|
||||
while (level < depth) { html += '</ul></li>'; depth--; }
|
||||
} else if (!first) {
|
||||
html += '</li>';
|
||||
}
|
||||
html += `<li>${inline(lines[i].replace(/^\s*[*-]\s/, ''))}`;
|
||||
first = false;
|
||||
i++;
|
||||
}
|
||||
while (depth > 0) { html += '</li></ul>'; depth--; }
|
||||
html += '</li></ul>';
|
||||
continue;
|
||||
}
|
||||
|
||||
let para = '';
|
||||
while (i < lines.length && lines[i].trim() !== ''
|
||||
&& !lines[i].startsWith('#') && !lines[i].startsWith('```')
|
||||
&& !lines[i].startsWith('|') && !/^(\s*)[*-]\s/.test(lines[i])
|
||||
&& !/^\d+\.\s/.test(lines[i])) {
|
||||
para += (para ? ' ' : '') + lines[i];
|
||||
i++;
|
||||
}
|
||||
if (para) html += `<p>${inline(para)}</p>`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
// --- Section/topic manifest — mirrors src/model/docs.rs (keep in sync!) ---
|
||||
|
||||
const SECTIONS = [
|
||||
{
|
||||
title: "Getting Started",
|
||||
topics: [
|
||||
{ title: "Welcome", file: "welcome.md" },
|
||||
{ title: "Navigation", file: "getting-started/navigation.md" },
|
||||
{ title: "The Big Picture", file: "getting-started/big_picture.md" },
|
||||
{ title: "Banks & Patterns", file: "getting-started/banks_patterns.md" },
|
||||
{ title: "Stage / Commit", file: "getting-started/staging.md" },
|
||||
{ title: "Using the Sequencer", file: "getting-started/grid.md" },
|
||||
{ title: "Editing a Step", file: "getting-started/editing.md" },
|
||||
{ title: "The Audio Engine", file: "getting-started/engine.md" },
|
||||
{ title: "Options", file: "getting-started/options.md" },
|
||||
{ title: "Saving & Loading", file: "getting-started/saving.md" },
|
||||
{ title: "The Sample Browser", file: "getting-started/samples.md" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Cagire's Forth",
|
||||
topics: [
|
||||
{ title: "About Forth", file: "forth/about_forth.md" },
|
||||
{ title: "The Dictionary", file: "forth/dictionary.md" },
|
||||
{ title: "The Stack", file: "forth/stack.md" },
|
||||
{ title: "Creating Words", file: "forth/definitions.md" },
|
||||
{ title: "Control Flow", file: "forth/control_flow.md" },
|
||||
{ title: "The Prelude", file: "forth/prelude.md" },
|
||||
{ title: "Cagire vs Classic", file: "forth/oddities.md" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Building sounds",
|
||||
topics: [
|
||||
{ title: "Introduction", file: "engine/intro.md" },
|
||||
{ title: "Settings", file: "engine/settings.md" },
|
||||
{ title: "Sources", file: "engine/sources.md" },
|
||||
{ title: "Samples", file: "engine/samples.md" },
|
||||
{ title: "Wavetables", file: "engine/wavetable.md" },
|
||||
{ title: "Filters", file: "engine/filters.md" },
|
||||
{ title: "Modulation", file: "engine/modulation.md" },
|
||||
{ title: "Distortion", file: "engine/distortion.md" },
|
||||
{ title: "Space & Time", file: "engine/space.md" },
|
||||
{ title: "Audio-Rate Mod", file: "engine/audio_modulation.md" },
|
||||
{ title: "Words & Sounds", file: "engine/words.md" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Branching out",
|
||||
topics: [
|
||||
{ title: "Introduction", file: "midi/intro.md" },
|
||||
{ title: "MIDI Output", file: "midi/output.md" },
|
||||
{ title: "MIDI Input", file: "midi/input.md" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Tutorials",
|
||||
topics: [
|
||||
{ title: "Randomness", file: "tutorials/randomness.md" },
|
||||
{ title: "Notes & Harmony", file: "tutorials/harmony.md" },
|
||||
{ title: "Generators", file: "tutorials/generators.md" },
|
||||
{ title: "Timing with at", file: "tutorials/at.md" },
|
||||
{ title: "Using Variables", file: "tutorials/variables.md" },
|
||||
{ title: "Recording", file: "tutorials/recording.md" },
|
||||
{ title: "Soundfonts", file: "tutorials/soundfont.md" },
|
||||
{ title: "Sharing", file: "tutorials/sharing.md" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Read all doc files at build time
|
||||
for (const section of SECTIONS) {
|
||||
for (const topic of section.topics) {
|
||||
const raw = fs.readFileSync(`../docs/${topic.file}`, 'utf-8');
|
||||
(topic as any).html = renderMarkdown(raw);
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cagire - Documentation</title>
|
||||
<meta name="description" content="Cagire documentation — Forth-based live coding sequencer">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<link rel="stylesheet" href="/docs.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="docs-topbar">
|
||||
<a class="docs-back" href="/">← Back to Cagire</a>
|
||||
<div class="docs-notice" id="docs-notice">
|
||||
<span>You are reading Cagire's built-in documentation. Here it is static, but inside the app the examples are runnable and the docs are interactive.</span>
|
||||
<button onclick="dismissNotice()">Got it</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="docs-layout">
|
||||
<nav class="docs-sidebar">
|
||||
{SECTIONS.map((section, si) => (
|
||||
<Fragment>
|
||||
<h3>{section.title}</h3>
|
||||
{section.topics.map((topic, ti) => (
|
||||
<button data-s={si} data-t={ti}>{topic.title}</button>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</nav>
|
||||
<main class="docs-content">
|
||||
{SECTIONS.map((section, si) =>
|
||||
section.topics.map((topic, ti) => (
|
||||
<article id={`topic-s${si}t${ti}`} set:html={(topic as any).html} />
|
||||
))
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
<script is:inline src="/docs.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -94,6 +94,9 @@ const DL = 'https://dlcagire.raphaelforment.fr';
|
||||
<p class="note">Source code and issue tracker on <a href="https://github.com/Bubobubobubobubo/cagire">GitHub</a>. You can also compile the software yourself from source!</p>
|
||||
|
||||
|
||||
<h2>Documentation</h2>
|
||||
<p>Cagire ships with built-in interactive documentation — browse it inside the app with runnable examples, or <a href="/docs">read the static version here</a>.</p>
|
||||
|
||||
<h2>About</h2>
|
||||
<p>Cagire is a step sequencer where each step contains a Forth script instead of typical note data. When the sequencer reaches a step, it runs the associated script. Scripts can produce sound, trigger samples, apply effects, or do nothing at all. You are free to define what your scripts will do. Cagire includes a built-in audio engine called <a href="https://doux.livecoding.fr">Doux</a>. No external software is needed to make sound. It comes with oscillators, sample players, filters, reverb, delay, distortion, and more.</p>
|
||||
|
||||
@@ -125,7 +128,7 @@ const DL = 'https://dlcagire.raphaelforment.fr';
|
||||
<video src="/mono_cagire.mp4" autoplay muted loop playsinline></video>
|
||||
|
||||
<p class="colophon">
|
||||
<a href="https://raphaelforment.fr">BuboBubo</a> · Audio engine: <a href="https://doux.livecoding.fr">Doux</a> · <a href="https://github.com/Bubobubobubobubo/cagire">GitHub</a> · AGPL-3.0 </p>
|
||||
<a href="https://raphaelforment.fr">BuboBubo</a> · Audio engine: <a href="https://doux.livecoding.fr">Doux</a> · <a href="https://github.com/Bubobubobubobubo/cagire">GitHub</a> · <a href="/docs">Docs</a> · AGPL-3.0 </p>
|
||||
|
||||
<script is:inline src="/script.js"></script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user