diff --git a/docs/Website/Assets/pride.svg b/docs/Website/Assets/pride.svg
new file mode 100644
index 0000000..be1b140
--- /dev/null
+++ b/docs/Website/Assets/pride.svg
@@ -0,0 +1,22 @@
+
diff --git a/docs/Website/README.md b/docs/Website/README.md
index 4007a92..c67ad36 100644
--- a/docs/Website/README.md
+++ b/docs/Website/README.md
@@ -5,12 +5,49 @@ Three files and a folder of marks. No build step, no dependencies, no
framework — open `index.html` and it works, including from a `file://` URL.
```
-index.html the page
+index.html the front page
+docs.html the documentation reader
style.css the palette, transcribed from include/theme.h
-app.js two behaviours: the boot log types, sections fade in
-Assets/ shift.svg (the logo), uberblock.svg (SchFS), guru.svg (PSOGM)
+app.js the front page: the boot log types, sections fade in
+docs.js the Markdown renderer and the doc index
+Assets/ shift.svg (logo), uberblock.svg (SchFS), guru.svg (PSOGM),
+ pride.svg
```
+## The docs reader
+
+`docs.html` renders the Markdown in `docs/` in the site's own theme, with the
+index down the left and a table of contents down the right. Links between
+documents are rewritten to stay inside the reader, so `../history/schfs.md`
+in an architecture page navigates rather than downloads.
+
+The parser is about 150 lines in `docs.js` and handles what `docs/` actually
+uses: headings, fenced code, tables, lists, quotes, rules and inline marks.
+A Markdown library would be larger than everything else here put together.
+Every document is escaped before a single tag is emitted — the only HTML that
+reaches the page is HTML `docs.js` wrote.
+
+**This one page needs a server.** Browsers refuse `fetch()` on `file://` for
+the same reason they refuse it cross-origin, so opening `docs.html` from a
+file gives a panel explaining that instead of the docs. Everything else works
+from a file. To read them locally:
+
+```sh
+cd /home/nexoniarz/Dokumenty/Projects/SchOS
+python3 -m http.server 8000
+# then http://localhost:8000/docs/Website/docs.html
+```
+
+Adding a document means adding one line to the `DOCS` array in `docs.js`. The
+reader will only load a path that appears there.
+
+## The flag
+
+`Assets/pride.svg` is in the top bar and in the footer, on both pages, and it
+is not seasonal. Support with an end date is marketing; this is a project that
+belongs to whoever writes it. It is also deliberately small — the site is
+about an operating system, and the flag is a statement, not a subject.
+
## The rules it follows
**The palette is not a design decision.** It is `include/theme.h`, copied. A
diff --git a/docs/Website/docs.html b/docs/Website/docs.html
new file mode 100644
index 0000000..77a0481
--- /dev/null
+++ b/docs/Website/docs.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/Website/docs.js b/docs/Website/docs.js
new file mode 100644
index 0000000..10061d8
--- /dev/null
+++ b/docs/Website/docs.js
@@ -0,0 +1,362 @@
+/* SPDX-License-Identifier: Apache-2.0
+ *
+ * The documentation reader.
+ *
+ * Renders the project's Markdown in the site's own theme, so the docs can be
+ * read without leaving the page and without a build step. No dependency: the
+ * parser below handles exactly what docs/ actually uses -- headings, fenced
+ * code, tables, lists, quotes, and inline marks. Pulling in a 40 KB Markdown
+ * library to render 29 files would outweigh every other asset here combined.
+ *
+ * Everything is escaped before any tag is emitted. The only HTML that reaches
+ * the page is HTML this file wrote.
+ */
+(function () {
+ "use strict";
+
+ /* ---- the index ---------------------------------------------------- */
+
+ var DOCS = [
+ { group: "Start here", items: [
+ ["README.md", "Overview"],
+ ["plan/1.0.md", "The 1.0 definition"],
+ ["licensing.md", "Licensing"]
+ ]},
+ { group: "Architecture", items: [
+ ["architecture/README.md", "The system, layer by layer"],
+ ["architecture/boot.md", "Boot"],
+ ["architecture/kernel.md", "Kernel"],
+ ["architecture/syscalls.md", "Syscalls"],
+ ["architecture/filesystem.md", "The VFS"],
+ ["architecture/schfs.md", "SchFS"],
+ ["architecture/devices.md", "Device model"],
+ ["architecture/usb.md", "USB"],
+ ["architecture/graphics.md", "Graphics"],
+ ["architecture/networking.md", "Networking"],
+ ["architecture/init.md", "init"]
+ ]},
+ { group: "Guides", items: [
+ ["guides/building.md", "Building"],
+ ["guides/testing.md", "Testing"],
+ ["guides/layout.md", "Tree layout"],
+ ["guides/adding-a-driver.md", "Adding a driver"]
+ ]},
+ { group: "Plan", items: [
+ ["plan/README.md", "What plan/ is for"],
+ ["plan/roadmap.md", "Roadmap"],
+ ["plan/foundations.md", "Foundations"],
+ ["plan/init.md", "schinit"],
+ ["plan/wayland.md", "Wayland"],
+ ["plan/hybrid.md", "Hybrid kernel"],
+ ["plan/schapi.md", "SchAPI"]
+ ]},
+ { group: "History", items: [
+ ["history/README.md", "Why history/ exists"],
+ ["history/kernel.md", "Kernel"],
+ ["history/schfs.md", "SchFS"],
+ ["history/moving-to-linux.md", "Moving to Linux"]
+ ]},
+ { group: "Branding", items: [
+ ["branding/README.md", "The three marks"]
+ ]}
+ ];
+
+ /* ---- helpers ------------------------------------------------------- */
+
+ function esc(s) {
+ return s.replace(/&/g, "&").replace(//g, ">");
+ }
+
+ /* Resolve a doc-relative link against the doc it appeared in, so
+ "../history/schfs.md" inside architecture/ lands on history/schfs.md. */
+ function resolve(from, href) {
+ var base = from.split("/").slice(0, -1);
+ var parts = href.split("/");
+ for (var i = 0; i < parts.length; i++) {
+ if (parts[i] === "." || parts[i] === "") continue;
+ if (parts[i] === "..") base.pop();
+ else base.push(parts[i]);
+ }
+ return base.join("/");
+ }
+
+ /* encodeURIComponent turns every "/" into %2F, which works but makes a
+ shared link unreadable. Slashes are legal in a query value, so keep them. */
+ function docHref(p) {
+ return "?doc=" + encodeURIComponent(p).replace(/%2F/g, "/");
+ }
+
+ function slug(s) {
+ return s.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
+ }
+
+ /* ---- inline marks --------------------------------------------------- */
+
+ /* U+0001 as the placeholder sentinel: it cannot occur in a Markdown source
+ file, so a code span lifted out here can never be confused with document
+ text when it is put back. */
+ var SENTINEL = "\u0001";
+
+ function inline(s, from) {
+ /* Code spans come out first and go back last, so nothing inside a backtick
+ pair is mistaken for emphasis or for a link. */
+ var spans = [];
+ s = s.replace(/`([^`]+)`/g, function (_, code) {
+ spans.push("" + esc(code) + "");
+ return SENTINEL + (spans.length - 1) + SENTINEL;
+ });
+
+ s = esc(s);
+
+ s = s.replace(/!\[([^\]]*)\]\(([^)\s]+)[^)]*\)/g, function (_, alt, src) {
+ return '';
+ });
+
+ s = s.replace(/\[([^\]]+)\]\(([^)\s]+)[^)]*\)/g, function (_, text, href) {
+ if (/^https?:/.test(href))
+ return '' + text + "";
+ if (href.charAt(0) === "#")
+ return '' + text + "";
+ if (/\.md($|#)/.test(href)) {
+ var cut = href.indexOf("#");
+ var hash = cut >= 0 ? href.slice(cut) : "";
+ var path = cut >= 0 ? href.slice(0, cut) : href;
+ return '' + text + "";
+ }
+ /* Anything else is a file in the repo rather than a document. Link at it
+ relative to docs/, which is where this page's ../ points. */
+ return '' + text + "";
+ });
+
+ s = s.replace(/\*\*([^*]+)\*\*/g, "$1");
+ s = s.replace(/(^|[^*])\*([^*\n]+)\*/g, "$1$2");
+
+ var restore = new RegExp(SENTINEL + "(\\d+)" + SENTINEL, "g");
+ return s.replace(restore, function (_, n) { return spans[+n]; });
+ }
+
+ /* ---- blocks --------------------------------------------------------- */
+
+ function render(md, from, headings) {
+ md = md.replace(/\r\n?/g, "\n").replace(//g, "");
+ var L = md.split("\n"), out = [], i = 0, m;
+
+ function listBlock(ordered) {
+ var tag = ordered ? "ol" : "ul", items = [];
+ while (i < L.length) {
+ var li = L[i].match(ordered ? /^\s*\d+\.\s+(.*)$/ : /^\s*[-*+]\s+(.*)$/);
+ if (!li) {
+ /* an indented continuation line belongs to the item above it */
+ if (items.length && /^\s+\S/.test(L[i])) {
+ items[items.length - 1] += " " + L[i].trim();
+ i++;
+ continue;
+ }
+ break;
+ }
+ items.push(li[1]);
+ i++;
+ }
+ out.push("<" + tag + ">");
+ items.forEach(function (t) { out.push("
" + inline(t, from) + "
"); });
+ out.push("" + tag + ">");
+ }
+
+ function cells(row) {
+ return row.replace(/^\s*\|/, "").replace(/\|\s*$/, "").split("|")
+ .map(function (c) { return c.trim(); });
+ }
+
+ while (i < L.length) {
+ var line = L[i];
+
+ if (!line.trim()) { i++; continue; }
+
+ /* fenced code */
+ if (/^\s*```/.test(line)) {
+ var lang = line.replace(/^\s*```/, "").trim(), buf = [];
+ i++;
+ while (i < L.length && !/^\s*```/.test(L[i])) buf.push(L[i++]);
+ i++;
+ out.push('
" + esc(buf.join("\n")) + "
");
+ continue;
+ }
+
+ /* table: a pipe row followed by a separator row */
+ if (/^\s*\|/.test(line) && /^\s*\|?[\s:|-]+$/.test(L[i + 1] || "") && /-/.test(L[i + 1] || "")) {
+ var head = cells(line);
+ i += 2;
+ out.push('
');
+ head.forEach(function (c) { out.push("
" + inline(c, from) + "
"); });
+ out.push("
");
+ while (i < L.length && /^\s*\|/.test(L[i])) {
+ out.push("
");
+ continue;
+ }
+
+ /* heading */
+ if ((m = line.match(/^(#{1,6})\s+(.*?)\s*#*$/))) {
+ var lvl = m[1].length, text = m[2], id = slug(text);
+ if (lvl <= 3) headings.push({ level: lvl, text: text, id: id });
+ out.push("' + inline(text, from) + "");
+ i++;
+ continue;
+ }
+
+ /* rule */
+ if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) { out.push(""); i++; continue; }
+
+ /* blockquote */
+ if (/^\s*>/.test(line)) {
+ var q = [];
+ while (i < L.length && /^\s*>/.test(L[i])) q.push(L[i++].replace(/^\s*>\s?/, ""));
+ out.push("
" + render(q.join("\n"), from, []) + "
");
+ continue;
+ }
+
+ if (/^\s*[-*+]\s+/.test(line)) { listBlock(false); continue; }
+ if (/^\s*\d+\.\s+/.test(line)) { listBlock(true); continue; }
+
+ /* paragraph: runs to the next blank line or block opener */
+ var para = [];
+ while (i < L.length && L[i].trim() &&
+ !/^\s*(```|>|#{1,6}\s|[-*+]\s|\d+\.\s|\|)/.test(L[i]) &&
+ !/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(L[i])) {
+ para.push(L[i++]);
+ }
+ if (para.length) out.push("
" + inline(para.join(" "), from) + "
");
+ else i++; /* a block opener we do not handle: never spin on it */
+ }
+ return out.join("\n");
+ }
+
+ /* ---- the page ------------------------------------------------------- */
+
+ var nav = document.getElementById("docnav");
+ var article = document.getElementById("doc");
+ var tocEl = document.getElementById("toc");
+
+ function buildNav() {
+ DOCS.forEach(function (g) {
+ var h = document.createElement("p");
+ h.className = "navgroup";
+ h.textContent = g.group;
+ nav.appendChild(h);
+ var ul = document.createElement("ul");
+ g.items.forEach(function (it) {
+ var li = document.createElement("li");
+ var a = document.createElement("a");
+ a.href = docHref(it[0]);
+ a.textContent = it[1];
+ a.dataset.path = it[0];
+ li.appendChild(a);
+ ul.appendChild(li);
+ });
+ nav.appendChild(ul);
+ });
+ }
+
+ function markCurrent(path) {
+ Array.prototype.forEach.call(nav.querySelectorAll("a"), function (a) {
+ a.classList.toggle("on", a.dataset.path === path);
+ });
+ }
+
+ function buildToc(headings) {
+ tocEl.innerHTML = "";
+ var real = headings.filter(function (h) { return h.level >= 2; });
+ if (real.length < 2) { tocEl.hidden = true; return; }
+ tocEl.hidden = false;
+ var t = document.createElement("p");
+ t.className = "navgroup";
+ t.textContent = "On this page";
+ tocEl.appendChild(t);
+ var ul = document.createElement("ul");
+ real.forEach(function (h) {
+ var li = document.createElement("li");
+ li.className = "lvl" + h.level;
+ var a = document.createElement("a");
+ a.href = "#" + h.id;
+ a.textContent = h.text;
+ li.appendChild(a);
+ ul.appendChild(li);
+ });
+ tocEl.appendChild(ul);
+ }
+
+ function fail(title, body) {
+ article.innerHTML = "
" + title + "
" + body;
+ tocEl.hidden = true;
+ }
+
+ function load(path) {
+ markCurrent(path);
+ document.title = "Docs — SchOS";
+ article.setAttribute("aria-busy", "true");
+
+ fetch("../" + path).then(function (r) {
+ if (!r.ok) throw new Error(r.status + " " + r.statusText);
+ return r.text();
+ }).then(function (text) {
+ var headings = [];
+ article.innerHTML = render(text, path, headings);
+ buildToc(headings);
+ article.removeAttribute("aria-busy");
+ if (headings.length) document.title = headings[0].text + " — SchOS docs";
+ var hash = location.hash.slice(1);
+ var target = hash && document.getElementById(hash);
+ if (target) target.scrollIntoView();
+ else window.scrollTo(0, 0);
+ }).catch(function (e) {
+ article.removeAttribute("aria-busy");
+ /* The common case, and worth naming precisely: browsers refuse fetch()
+ on file:// for the same reason they refuse it cross-origin, so this
+ one page needs a server even though nothing here is dynamic. */
+ if (location.protocol === "file:") {
+ fail("This page needs a local server",
+ "
Browsers block fetch() on file:// URLs, so the " +
+ "reader cannot load the Markdown. The rest of the site works from a file; " +
+ "this page does not. Serve the repository root:
" +
+ '
cd /home/nexoniarz/Dokumenty/Projects/SchOS\n' +
+ "python3 -m http.server 8000
" +
+ "
Then open http://localhost:8000/docs/Website/docs.html.
SchOS — kernel SchIFT, filesystem SchFS.Apache-2.0, with the xv6-derived core under MIT.
+
+
+ Built by, and for, people of every orientation and gender identity.
+ The flag stays up all year.
+