mirror of
https://github.com/rohitg00/ai-engineering-from-scratch.git
synced 2026-10-02 01:54:39 +08:00
feat(site): improve motion, responsive UX, and agent readiness (#426)
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const SITE_ROOT = path.join(__dirname, '..', 'site');
|
||||
const HTML_BY_PATH = {
|
||||
'/': 'index.html',
|
||||
'/about': 'about.html',
|
||||
'/catalog': 'catalog.html',
|
||||
'/glossary': 'glossary.html',
|
||||
'/path': 'prereqs.html',
|
||||
'/roadmap': 'prereqs.html',
|
||||
'/developer': 'developer.html',
|
||||
'/docs': 'developer.html',
|
||||
'/contact': 'contact.html',
|
||||
'/privacy': 'privacy.html',
|
||||
};
|
||||
|
||||
function parseAccept(header) {
|
||||
if (!header) return [{ type: 'text/html', q: 1 }];
|
||||
return header.split(',').map((part, index) => {
|
||||
const [rawType, ...params] = part.trim().toLowerCase().split(';');
|
||||
const qParam = params.find((param) => param.trim().startsWith('q='));
|
||||
const q = qParam ? Number.parseFloat(qParam.trim().slice(2)) : 1;
|
||||
return { type: rawType.trim(), q: Number.isFinite(q) ? Math.max(0, Math.min(1, q)) : 0, index };
|
||||
}).filter((entry) => entry.type && entry.q > 0).sort((a, b) => {
|
||||
if (b.q !== a.q) return b.q - a.q;
|
||||
const specificity = (type) => type === '*/*' ? 0 : type.endsWith('/*') ? 1 : 2;
|
||||
return specificity(b.type) - specificity(a.type) || a.index - b.index;
|
||||
});
|
||||
}
|
||||
|
||||
function qualityFor(accepted, mediaType) {
|
||||
const exact = accepted.find((entry) => entry.type === mediaType);
|
||||
if (exact) return exact.q;
|
||||
const wildcard = accepted.find((entry) => entry.type === '*/*' || entry.type === `${mediaType.split('/')[0]}/*`);
|
||||
return wildcard ? wildcard.q : 0;
|
||||
}
|
||||
|
||||
function markdownFor(requestPath) {
|
||||
const llms = fs.readFileSync(path.join(SITE_ROOT, 'llms.txt'), 'utf8');
|
||||
if (requestPath === '/') return llms;
|
||||
return `# AI Engineering from Scratch\n\nCanonical page: https://aiengineeringfromscratch.com${requestPath}\n\nThe agent-oriented curriculum index is available at https://aiengineeringfromscratch.com/llms.txt.\n\n${llms}`;
|
||||
}
|
||||
|
||||
module.exports = (req, res) => {
|
||||
const requestPath = String((req.query && req.query.path) || '/').split('?')[0] || '/';
|
||||
const accepted = parseAccept(req.headers.accept || '');
|
||||
const markdownQ = qualityFor(accepted, 'text/markdown');
|
||||
const htmlQ = qualityFor(accepted, 'text/html');
|
||||
res.setHeader('Vary', 'Accept, Accept-Encoding');
|
||||
res.setHeader('Cache-Control', 'public, max-age=300, s-maxage=86400, stale-while-revalidate=604800');
|
||||
|
||||
const file = HTML_BY_PATH[requestPath];
|
||||
if (!file) {
|
||||
res.statusCode = 404;
|
||||
if (markdownQ >= htmlQ && markdownQ > 0) {
|
||||
res.setHeader('Content-Type', 'text/markdown; charset=utf-8');
|
||||
res.end('# AI Engineering from Scratch\n\nThis path does not exist.\n\nTry /llms.txt or /sitemap.xml.\n');
|
||||
} else {
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
res.end(fs.readFileSync(path.join(SITE_ROOT, '404.html'), 'utf8'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!markdownQ && !htmlQ && accepted.length && !accepted.some((entry) => entry.type === '*/*')) {
|
||||
res.statusCode = 406;
|
||||
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||
res.end('Not Acceptable\n\nAvailable representations: text/html, text/markdown\n');
|
||||
return;
|
||||
}
|
||||
|
||||
if (markdownQ >= htmlQ && markdownQ > 0) {
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'text/markdown; charset=utf-8');
|
||||
res.end(markdownFor(requestPath));
|
||||
return;
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
res.end(fs.readFileSync(path.join(SITE_ROOT, file), 'utf8'));
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const test = require('node:test');
|
||||
const handler = require('../api/markdown.js');
|
||||
|
||||
function request(accept, requestPath = '/') {
|
||||
const headers = {};
|
||||
let body = '';
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
setHeader(name, value) { headers[name.toLowerCase()] = value; },
|
||||
end(value) { body = value || ''; },
|
||||
};
|
||||
handler({ headers: { accept }, query: { path: requestPath } }, res);
|
||||
return { res, headers, body };
|
||||
}
|
||||
|
||||
test('serves Markdown for the canonical homepage when preferred', () => {
|
||||
const result = request('text/markdown, text/html;q=0.8');
|
||||
assert.equal(result.res.statusCode, 200);
|
||||
assert.match(result.headers['content-type'], /^text\/markdown/);
|
||||
assert.equal(result.headers.vary, 'Accept, Accept-Encoding');
|
||||
assert.match(result.body, /^# AI Engineering from Scratch/);
|
||||
});
|
||||
|
||||
test('keeps HTML when HTML has the higher quality value', () => {
|
||||
const result = request('text/html, text/markdown;q=0.5');
|
||||
assert.equal(result.res.statusCode, 200);
|
||||
assert.match(result.headers['content-type'], /^text\/html/);
|
||||
assert.match(result.body, /<html/i);
|
||||
});
|
||||
|
||||
test('returns 406 when the client rejects both supported types', () => {
|
||||
const result = request('application/json');
|
||||
assert.equal(result.res.statusCode, 406);
|
||||
assert.match(result.body, /Not Acceptable/);
|
||||
});
|
||||
|
||||
test('returns an agent-readable 404 for an unknown negotiated route', () => {
|
||||
const result = request('text/markdown', '/does-not-exist');
|
||||
assert.equal(result.res.statusCode, 404);
|
||||
assert.match(result.headers['content-type'], /^text\/markdown/);
|
||||
assert.match(result.body, /llms\.txt/);
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Contract checks for the site's agent-facing discovery surfaces."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SITE = ROOT / "site"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
config = json.loads((ROOT / "vercel.json").read_text())
|
||||
rewrites = config["rewrites"]
|
||||
markdown_rewrites = [r for r in rewrites if "has" in r and r["destination"] == "/llms.txt"]
|
||||
negotiator_rewrites = [r for r in rewrites if r.get("destination", "").startswith("/api/markdown")]
|
||||
assert negotiator_rewrites, "markdown negotiation rewrite is missing"
|
||||
assert all("accept" in h["key"].lower() for r in negotiator_rewrites for h in r["has"])
|
||||
|
||||
headers = config["headers"]
|
||||
llms_header = next(h for h in headers if h["source"] == "/llms.txt")
|
||||
values = {h["key"].lower(): h["value"] for h in llms_header["headers"]}
|
||||
assert values["content-type"].startswith("text/markdown")
|
||||
assert values["vary"] == "Accept, Accept-Encoding"
|
||||
|
||||
for name in ("404.html", "developer.html", "contact.html", "privacy.html", "openapi.json"):
|
||||
assert (SITE / name).is_file(), f"missing {name}"
|
||||
|
||||
for name in ("developer.html", "contact.html", "privacy.html"):
|
||||
text = (SITE / name).read_text()
|
||||
assert "AI Engineering from Scratch" in text
|
||||
assert len(" ".join(text.split())) > 500, f"{name} is too thin to be a trust page"
|
||||
|
||||
openapi = json.loads((SITE / "openapi.json").read_text())
|
||||
assert openapi["openapi"].startswith("3.")
|
||||
assert "https://aiengineeringfromscratch.com" in openapi["servers"][0]["url"]
|
||||
assert {"/llms.txt", "/sitemap.xml", "/lesson.html"} <= set(openapi["paths"])
|
||||
|
||||
home = (SITE / "index.html").read_text()
|
||||
assert '"@type": "Organization"' in home
|
||||
assert '"url": "https://aiengineeringfromscratch.com"' in home
|
||||
assert '"sameAs"' in home
|
||||
assert '"contactPoint"' in home
|
||||
|
||||
not_found = (SITE / "404.html").read_text()
|
||||
assert "/llms.txt" in not_found and "/sitemap.xml" in not_found
|
||||
assert (ROOT / "api" / "markdown.js").is_file()
|
||||
assert "Vary" in (ROOT / "api" / "markdown.js").read_text()
|
||||
print("agent readiness contracts: ok")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"><head><meta charset="UTF-8"><meta name="robots" content="noindex"><title>404 · AI Engineering from Scratch</title><link rel="stylesheet" href="style.css?v=20260802a"></head>
|
||||
<body><main class="container" style="max-width:760px;padding-top:18vh"><p class="about-eyebrow">404 · AI Engineering from Scratch</p><h1>Page not found</h1><pre style="white-space:pre-wrap;line-height:1.7"># AI Engineering from Scratch
|
||||
|
||||
This path does not exist.
|
||||
|
||||
Try the sitemap: /sitemap.xml
|
||||
Try the agent index: /llms.txt
|
||||
Try the catalog: /catalog.html
|
||||
Try the developer resources: /developer.html</pre></main></body></html>
|
||||
@@ -7,6 +7,7 @@
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' fill='%23fafaf5'/><rect x='2' y='2' width='28' height='28' fill='none' stroke='%233553ff' stroke-width='1.2'/><text x='6' y='22' font-size='14' font-family='monospace' fill='%233553ff'>AI</text></svg>">
|
||||
<meta name="description" content="What AI Engineering from Scratch is, why it exists, who builds it, and how the site is made. A free, open-source, MIT-licensed curriculum.">
|
||||
<link rel="canonical" href="https://aiengineeringfromscratch.com/about.html">
|
||||
<link rel="alternate" type="text/markdown" href="https://aiengineeringfromscratch.com/llms.txt">
|
||||
<meta property="og:title" content="About · AI Engineering from Scratch">
|
||||
<meta property="og:description" content="Why this curriculum exists, who builds it, and how the site is made. Free, open source, MIT.">
|
||||
<meta property="og:image" content="https://aiengineeringfromscratch.com/og-image.png?v=3">
|
||||
@@ -157,6 +158,7 @@
|
||||
"name": "AI Engineering from Scratch",
|
||||
"url": "https://aiengineeringfromscratch.com",
|
||||
"sameAs": ["https://github.com/rohitg00/ai-engineering-from-scratch", "https://github.com/rohitg00"],
|
||||
"contactPoint": { "@type": "ContactPoint", "contactType": "project support", "url": "https://github.com/rohitg00/ai-engineering-from-scratch/issues" },
|
||||
"founder": { "@type": "Person", "name": "Rohit Ghumare", "url": "https://github.com/rohitg00" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1607,6 +1607,10 @@ function writeSitemap(phases, glossaryCount, certifications) {
|
||||
{ loc: '/', priority: '1.0', freq: 'weekly' },
|
||||
{ loc: '/catalog.html', priority: '0.8', freq: 'weekly' },
|
||||
{ loc: '/prereqs.html', priority: '0.7', freq: 'monthly' },
|
||||
{ loc: '/about.html', priority: '0.5', freq: 'yearly' },
|
||||
{ loc: '/developer.html', priority: '0.6', freq: 'monthly' },
|
||||
{ loc: '/contact.html', priority: '0.3', freq: 'yearly' },
|
||||
{ loc: '/privacy.html', priority: '0.3', freq: 'yearly' },
|
||||
];
|
||||
if (glossaryCount > 0) urls.push({ loc: '/glossary.html', priority: '0.6', freq: 'monthly' });
|
||||
if (certifications && certifications.program) {
|
||||
@@ -1647,6 +1651,12 @@ function writeLlms(phases, glossaryCount, artifactCount, certifications) {
|
||||
out += `Canonical site: ${SITE_ORIGIN}\n`;
|
||||
out += `Source: https://github.com/rohitg00/ai-engineering-from-scratch\n`;
|
||||
out += `Glossary terms: ${glossaryCount} · Reusable outputs (prompts/skills/agents): ${artifactCount}\n\n`;
|
||||
out += `## Developer resources\n`;
|
||||
out += `- [Developer documentation](${SITE_ORIGIN}/developer.html) — machine-readable site contracts and integration notes\n`;
|
||||
out += `- [OpenAPI description](${SITE_ORIGIN}/openapi.json) — read-only public resource inventory\n`;
|
||||
out += `- [Sitemap](${SITE_ORIGIN}/sitemap.xml) — canonical URL inventory\n`;
|
||||
out += `- [Contact](${SITE_ORIGIN}/contact.html) — maintainer and project contact route\n`;
|
||||
out += `- [Privacy](${SITE_ORIGIN}/privacy.html) — data and analytics policy\n\n`;
|
||||
out += `Lesson pages render client-side. Agents: fetch each lesson's raw markdown link; it is the full text. Lesson directories may also include code/ (runnable implementation) and quiz.json.\n\n`;
|
||||
for (const phase of phases) {
|
||||
out += `## Phase ${phase.id}: ${phase.name}\n`;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Contact · AI Engineering from Scratch</title>
|
||||
<meta name="description" content="Contact the maintainer and contributors of AI Engineering from Scratch through the public project channels.">
|
||||
<link rel="canonical" href="https://aiengineeringfromscratch.com/contact.html"><link rel="stylesheet" href="style.css?v=20260802a">
|
||||
<style>
|
||||
.trust-page { max-width: 780px; margin: 0 auto; padding: 100px 0 80px; }
|
||||
.trust-page h1, .trust-page h2 { font-family: var(--font-display); color: var(--blueprint); text-transform: uppercase; }
|
||||
.trust-page h1 { font-size: clamp(2.4rem, 6vw, 3.8rem); line-height: 1; }
|
||||
.trust-page h2 { font-family: var(--font-mono); font-size: 1rem; letter-spacing: .12em; margin-top: 42px; }
|
||||
.trust-page p, .trust-page li { font-family: var(--font-body); font-size: 1.05rem; line-height: 1.7; color: var(--ink); }
|
||||
.trust-page a { color: var(--blueprint); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header"><div class="header-inner"><a href="index.html" class="logo"><span class="logo-icon" aria-hidden="true"></span> AI / FROM SCRATCH</a><nav class="header-nav"><a href="index.html#contents">Contents</a><a href="catalog.html">Catalog</a><a href="prereqs.html">Roadmap</a><a href="glossary.html">Glossary</a><a href="about.html">About</a></nav></div></header>
|
||||
<main class="container"><article class="trust-page">
|
||||
<p class="about-eyebrow">AI Engineering from Scratch</p>
|
||||
<h1>Contact</h1>
|
||||
<p>AI Engineering from Scratch is maintained in public. The fastest way to reach the maintainer and contributors is through the project repository, where questions stay attached to the lesson or behavior they describe and can be answered in context.</p>
|
||||
<h2>Project support</h2>
|
||||
<ul><li><a href="https://github.com/rohitg00/ai-engineering-from-scratch/issues/new/choose">Report a bug or propose a lesson</a>.</li><li><a href="https://github.com/rohitg00/ai-engineering-from-scratch/discussions">Start a discussion</a> for learning questions, ideas, and community help.</li><li><a href="https://github.com/rohitg00">Contact the maintainer profile</a> for public project matters.</li></ul>
|
||||
<h2>Responsible reports</h2>
|
||||
<p>Do not publish secrets, private learner data, or credentials in an issue. For a security-sensitive report, open a private GitHub security advisory for the repository when the report involves a vulnerability. For ordinary content, accessibility, curriculum, or site issues, include the URL, expected behavior, actual behavior, and a minimal reproduction.</p>
|
||||
</article></main>
|
||||
<footer class="site-footer"><div class="container footer-inner"><p>© 2026 · open source · free forever</p><div class="footer-links"><a href="about.html">About</a><a href="developer.html">Developer resources</a><a href="privacy.html">Privacy</a></div></div></footer>
|
||||
<script type="application/ld+json">{"@context":"https://schema.org","@type":"ContactPage","name":"Contact AI Engineering from Scratch","url":"https://aiengineeringfromscratch.com/contact.html","isPartOf":{"@type":"WebSite","name":"AI Engineering from Scratch","url":"https://aiengineeringfromscratch.com"}}</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Developer resources · AI Engineering from Scratch</title>
|
||||
<meta name="description" content="Machine-readable developer resources, public URLs, OpenAPI description, sitemap, and agent integration notes for AI Engineering from Scratch.">
|
||||
<link rel="canonical" href="https://aiengineeringfromscratch.com/developer.html">
|
||||
<link rel="stylesheet" href="style.css?v=20260802a">
|
||||
<style>
|
||||
.trust-page { max-width: 780px; margin: 0 auto; padding: 100px 0 80px; }
|
||||
.trust-page h1, .trust-page h2 { font-family: var(--font-display); color: var(--blueprint); text-transform: uppercase; }
|
||||
.trust-page h1 { font-size: clamp(2.4rem, 6vw, 3.8rem); line-height: 1; }
|
||||
.trust-page h2 { font-family: var(--font-mono); font-size: 1rem; letter-spacing: .12em; margin-top: 42px; }
|
||||
.trust-page p, .trust-page li { font-family: var(--font-body); font-size: 1.05rem; line-height: 1.7; color: var(--ink); }
|
||||
.trust-page a { color: var(--blueprint); }
|
||||
.trust-page code { font-family: var(--font-mono); background: var(--bg-surface); border: 1px solid var(--rule-soft); padding: 1px 5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header"><div class="header-inner">
|
||||
<a href="index.html" class="logo"><span class="logo-icon" aria-hidden="true"></span> AI / FROM SCRATCH</a>
|
||||
<nav class="header-nav"><a href="index.html#contents">Contents</a><a href="catalog.html">Catalog</a><a href="prereqs.html">Roadmap</a><a href="glossary.html">Glossary</a><a href="about.html">About</a></nav>
|
||||
</div></header>
|
||||
<main class="container"><article class="trust-page">
|
||||
<p class="about-eyebrow">AI Engineering from Scratch</p>
|
||||
<h1>Developer resources</h1>
|
||||
<p>This is the machine-readable entry point for AI Engineering from Scratch. The project is a static, open-source curriculum rather than a hosted application API. Agents and developer tools should start with <a href="llms.txt">llms.txt</a>, which maps the lessons to their canonical pages and raw Markdown sources.</p>
|
||||
<h2>Stable resources</h2>
|
||||
<ul>
|
||||
<li><a href="llms.txt"><code>/llms.txt</code></a> is the agent-oriented curriculum map.</li>
|
||||
<li><a href="sitemap.xml"><code>/sitemap.xml</code></a> lists canonical public URLs.</li>
|
||||
<li><a href="openapi.json"><code>/openapi.json</code></a> describes the read-only resource surface.</li>
|
||||
<li><a href="catalog.html"><code>/catalog.html</code></a> is the searchable human catalog.</li>
|
||||
<li><a href="https://github.com/rohitg00/ai-engineering-from-scratch"><code>GitHub</code></a> is the source of truth for lessons, code, quizzes, and reusable artifacts.</li>
|
||||
</ul>
|
||||
<h2>Markdown negotiation</h2>
|
||||
<p>Requests for the homepage or major navigation routes with <code>Accept: text/markdown</code> receive the agent map with <code>Content-Type: text/markdown</code>. Responses vary on <code>Accept</code> so a cache cannot mix the HTML and Markdown representations.</p>
|
||||
<h2>MCP status</h2>
|
||||
<p>The website does not currently expose a hosted MCP server or authenticated product API. The curriculum does teach how to build and operate MCP servers, and those lesson artifacts live in the repository. A remote Streamable HTTP MCP endpoint would require a separate product decision, authentication policy, and operational owner.</p>
|
||||
</article></main>
|
||||
<footer class="site-footer"><div class="container footer-inner"><p>© 2026 · open source · free forever</p><div class="footer-links"><a href="about.html">About</a><a href="contact.html">Contact</a><a href="privacy.html">Privacy</a><a href="llms.txt">Agents</a></div></div></footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -7,6 +7,7 @@
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' fill='%23fafaf5'/><rect x='2' y='2' width='28' height='28' fill='none' stroke='%233553ff' stroke-width='1.2'/><text x='6' y='22' font-size='14' font-family='monospace' fill='%233553ff'>AI</text></svg>">
|
||||
<meta name="description" content="AI Engineering from Scratch is a free, open-source curriculum that builds every core AI algorithm by hand. 511 lessons, 20 phases, four languages. The math, the model, the tokenizer, and the agent loop. Once, by hand.">
|
||||
<link rel="canonical" href="https://aiengineeringfromscratch.com/">
|
||||
<link rel="alternate" type="text/markdown" href="https://aiengineeringfromscratch.com/llms.txt">
|
||||
<meta property="og:title" content="AI Engineering from Scratch">
|
||||
<meta property="og:description" content="511 lessons. 20 phases. Write the backprop, the tokenizer, the attention mechanism, and the agent loop by hand before any framework gets imported. Python, TypeScript, Rust, Julia.">
|
||||
<meta property="og:image" content="https://aiengineeringfromscratch.com/og-image.png?v=3">
|
||||
@@ -31,6 +32,11 @@
|
||||
"url": "https://aiengineeringfromscratch.com",
|
||||
"logo": "https://aiengineeringfromscratch.com/og-image.png",
|
||||
"sameAs": ["https://github.com/rohitg00/ai-engineering-from-scratch", "https://github.com/rohitg00"],
|
||||
"contactPoint": {
|
||||
"@type": "ContactPoint",
|
||||
"contactType": "project support",
|
||||
"url": "https://github.com/rohitg00/ai-engineering-from-scratch/issues"
|
||||
},
|
||||
"founder": { "@type": "Person", "name": "Rohit Ghumare", "url": "https://github.com/rohitg00" }
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "AI Engineering from Scratch public resources",
|
||||
"version": "1.0.0",
|
||||
"description": "Read-only, public machine-readable resources for the AI Engineering from Scratch curriculum. Lesson source remains canonical in the GitHub repository."
|
||||
},
|
||||
"servers": [{"url": "https://aiengineeringfromscratch.com"}],
|
||||
"paths": {
|
||||
"/llms.txt": {
|
||||
"get": {
|
||||
"summary": "Read the agent-oriented curriculum map",
|
||||
"operationId": "getAgentIndex",
|
||||
"responses": {"200": {"description": "Markdown lesson and resource index", "content": {"text/markdown": {"schema": {"type": "string"}}}}}
|
||||
}
|
||||
},
|
||||
"/sitemap.xml": {
|
||||
"get": {
|
||||
"summary": "Read canonical public URLs",
|
||||
"operationId": "getSitemap",
|
||||
"responses": {"200": {"description": "XML sitemap", "content": {"application/xml": {"schema": {"type": "string"}}}}}
|
||||
}
|
||||
},
|
||||
"/lesson.html": {
|
||||
"get": {
|
||||
"summary": "Open a rendered lesson",
|
||||
"operationId": "getLesson",
|
||||
"parameters": [{"name": "path", "in": "query", "required": true, "schema": {"type": "string"}, "description": "Repository-relative lesson path, for example phases/01-math-foundations/01-linear-algebra-intuition"}],
|
||||
"responses": {"200": {"description": "Rendered lesson page"}, "404": {"description": "Unknown lesson path"}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Privacy · AI Engineering from Scratch</title>
|
||||
<meta name="description" content="Privacy information for the AI Engineering from Scratch curriculum website.">
|
||||
<link rel="canonical" href="https://aiengineeringfromscratch.com/privacy.html"><link rel="stylesheet" href="style.css?v=20260802a">
|
||||
<style>
|
||||
.trust-page { max-width: 780px; margin: 0 auto; padding: 100px 0 80px; }
|
||||
.trust-page h1, .trust-page h2 { font-family: var(--font-display); color: var(--blueprint); text-transform: uppercase; }
|
||||
.trust-page h1 { font-size: clamp(2.4rem, 6vw, 3.8rem); line-height: 1; }
|
||||
.trust-page h2 { font-family: var(--font-mono); font-size: 1rem; letter-spacing: .12em; margin-top: 42px; }
|
||||
.trust-page p { font-family: var(--font-body); font-size: 1.05rem; line-height: 1.7; color: var(--ink); }
|
||||
.trust-page a { color: var(--blueprint); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header"><div class="header-inner"><a href="index.html" class="logo"><span class="logo-icon" aria-hidden="true"></span> AI / FROM SCRATCH</a><nav class="header-nav"><a href="index.html#contents">Contents</a><a href="catalog.html">Catalog</a><a href="prereqs.html">Roadmap</a><a href="glossary.html">Glossary</a><a href="about.html">About</a></nav></div></header>
|
||||
<main class="container"><article class="trust-page">
|
||||
<p class="about-eyebrow">AI Engineering from Scratch</p>
|
||||
<h1>Privacy</h1>
|
||||
<p>AI Engineering from Scratch is a public, open-source curriculum. The lesson content is available without an account, payment, or learner profile. The site does not ask for passwords, API keys, or private project files.</p>
|
||||
<h2>What the site receives</h2>
|
||||
<p>Hosting and analytics providers may receive ordinary request data such as an IP address, browser user agent, referrer, requested URL, and approximate timing. This information is used for delivery, security, aggregate traffic measurement, and diagnosing broken pages. The project does not sell learner data or use lesson activity to build advertising profiles.</p>
|
||||
<h2>Local progress</h2>
|
||||
<p>Optional reading progress, theme preferences, and quiz state are stored in the browser when the site offers those controls. They are not required to read the curriculum and are not submitted as an account record by the static site. Clearing site storage removes that local state.</p>
|
||||
<h2>Third-party links</h2>
|
||||
<p>Lessons link to specifications, papers, repositories, and other resources. Those destinations have their own policies. Review them before submitting information. Questions about this policy can be raised through the <a href="contact.html">contact page</a> or the public repository.</p>
|
||||
</article></main>
|
||||
<footer class="site-footer"><div class="container footer-inner"><p>© 2026 · open source · free forever</p><div class="footer-links"><a href="about.html">About</a><a href="contact.html">Contact</a><a href="developer.html">Developer resources</a></div></div></footer>
|
||||
<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","name":"Privacy · AI Engineering from Scratch","url":"https://aiengineeringfromscratch.com/privacy.html","isPartOf":{"@type":"WebSite","name":"AI Engineering from Scratch","url":"https://aiengineeringfromscratch.com"}}</script>
|
||||
</body>
|
||||
</html>
|
||||
+29
-4
@@ -4,6 +4,16 @@
|
||||
"framework": null,
|
||||
"installCommand": "echo skip",
|
||||
"rewrites": [
|
||||
{ "source": "/", "has": [{ "type": "header", "key": "accept", "value": ".*" }], "destination": "/api/markdown?path=/" },
|
||||
{ "source": "/about", "has": [{ "type": "header", "key": "accept", "value": ".*" }], "destination": "/api/markdown?path=/about" },
|
||||
{ "source": "/catalog", "has": [{ "type": "header", "key": "accept", "value": ".*" }], "destination": "/api/markdown?path=/catalog" },
|
||||
{ "source": "/glossary", "has": [{ "type": "header", "key": "accept", "value": ".*" }], "destination": "/api/markdown?path=/glossary" },
|
||||
{ "source": "/path", "has": [{ "type": "header", "key": "accept", "value": ".*" }], "destination": "/api/markdown?path=/path" },
|
||||
{ "source": "/roadmap", "has": [{ "type": "header", "key": "accept", "value": ".*" }], "destination": "/api/markdown?path=/roadmap" },
|
||||
{ "source": "/developer", "has": [{ "type": "header", "key": "accept", "value": ".*" }], "destination": "/api/markdown?path=/developer" },
|
||||
{ "source": "/docs", "has": [{ "type": "header", "key": "accept", "value": ".*" }], "destination": "/api/markdown?path=/docs" },
|
||||
{ "source": "/contact", "has": [{ "type": "header", "key": "accept", "value": ".*" }], "destination": "/api/markdown?path=/contact" },
|
||||
{ "source": "/privacy", "has": [{ "type": "header", "key": "accept", "value": ".*" }], "destination": "/api/markdown?path=/privacy" },
|
||||
{ "source": "/glossary", "destination": "/glossary.html" },
|
||||
{ "source": "/catalog", "destination": "/catalog.html" },
|
||||
{ "source": "/certifications", "destination": "/certifications.html" },
|
||||
@@ -11,7 +21,11 @@
|
||||
{ "source": "/assessment", "destination": "/assessment.html" },
|
||||
{ "source": "/path", "destination": "/prereqs.html" },
|
||||
{ "source": "/roadmap", "destination": "/prereqs.html" },
|
||||
{ "source": "/about", "destination": "/about.html" }
|
||||
{ "source": "/about", "destination": "/about.html" },
|
||||
{ "source": "/developer", "destination": "/developer.html" },
|
||||
{ "source": "/docs", "destination": "/developer.html" },
|
||||
{ "source": "/contact", "destination": "/contact.html" },
|
||||
{ "source": "/privacy", "destination": "/privacy.html" }
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
@@ -35,18 +49,29 @@
|
||||
{
|
||||
"source": "/(.*)\\.html",
|
||||
"headers": [
|
||||
{ "key": "Cache-Control", "value": "public, max-age=300, s-maxage=86400, stale-while-revalidate=604800" }
|
||||
{ "key": "Cache-Control", "value": "public, max-age=300, s-maxage=86400, stale-while-revalidate=604800" },
|
||||
{ "key": "Vary", "value": "Accept, Accept-Encoding" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/",
|
||||
"headers": [
|
||||
{ "key": "Cache-Control", "value": "public, max-age=300, s-maxage=86400, stale-while-revalidate=604800" }
|
||||
{ "key": "Cache-Control", "value": "public, max-age=300, s-maxage=86400, stale-while-revalidate=604800" },
|
||||
{ "key": "Vary", "value": "Accept, Accept-Encoding" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/(glossary|catalog|certifications|certification|assessment|path|roadmap)",
|
||||
"source": "/(glossary|catalog|certifications|certification|assessment|path|roadmap|about|developer|docs|contact|privacy)",
|
||||
"headers": [
|
||||
{ "key": "Cache-Control", "value": "public, max-age=300, s-maxage=86400, stale-while-revalidate=604800" },
|
||||
{ "key": "Vary", "value": "Accept, Accept-Encoding" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/llms.txt",
|
||||
"headers": [
|
||||
{ "key": "Content-Type", "value": "text/markdown; charset=utf-8" },
|
||||
{ "key": "Vary", "value": "Accept, Accept-Encoding" },
|
||||
{ "key": "Cache-Control", "value": "public, max-age=300, s-maxage=86400, stale-while-revalidate=604800" }
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user