docs: a website, in three files

index.html, style.css, app.js and the three marks. No build step, no
dependencies, no framework -- it opens from a file:// URL.

The palette is not a design decision: it is include/theme.h transcribed,
including the rule that header enforces. PSOGM purple appears exactly once,
on the crash-screen section, because branding/README.md says a colour used
everywhere means nothing. The typefaces are Roboto and JetBrains Mono, both
already in ports/ and both requested from the system rather than downloaded --
a font file would outweigh the rest of the site.

The boot log in the hero is real lines from a real boot. The script is
decoration and the page is complete without it; it does nothing at all under
prefers-reduced-motion.

The feature tags (runs / in progress / designed) are a promise, and the
README says so: anything marked `runs` is in the tree and works today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
Nexoniarz 2026-09-12 21:38:47 +02:00
commit 81af69bf53
7 changed files with 890 additions and 0 deletions

View file

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<title>Guru — the PSOGM face</title>
<desc>The crash-screen face. Chamfered corners quote the old SchOS letterforms.
Drawn here for reference only: kernel/core/panic.c builds it from primitives
rather than from a bitmap, so it scales with the display and costs nothing in
the kernel image.</desc>
<path fill="#7C5CFF" d="M58 24 L142 24 L176 58 L176 142 L142 176 L58 176 L24 142 L24 58 Z"/>
<path fill="#2B0F52" d="M74 82 h16 v22 h-16 Z"/>
<path fill="#2B0F52" d="M110 82 h16 v22 h-16 Z"/>
<path fill="#2B0F52" d="M72 128 h56 v11 h-56 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 655 B

View file

@ -0,0 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<title>SchOS — Shift</title>
<desc>Three slabs, displaced. The layer diagram in docs/architecture/README.md
as a solid object, and the kernel's name (SchIFT) as a shape.
Each slab shows three faces: the top, the front edge under its lower side, and
the right-end wall under its right diagonal. The right wall is not optional --
without it the slab has no thickness where it is widest, and the corner reads
as a hole rather than an edge.
Geometry is shared with "shift-mask.svg"; see that file.</desc>
<!-- bottom slab -->
<path fill="#3C2E85" d="M54 126 L158 126 L128 156 L24 156 Z"/>
<path fill="#31256E" d="M24 156 L128 156 L128 167 L24 167 Z"/>
<path fill="#291F5C" d="M158 126 L158 137 L128 167 L128 156 Z"/>
<!-- middle slab -->
<path fill="#5B44C4" d="M72 84 L176 84 L146 114 L42 114 Z"/>
<path fill="#4B37A6" d="M42 114 L146 114 L146 125 L42 125 Z"/>
<path fill="#3F2D8C" d="M176 84 L176 95 L146 125 L146 114 Z"/>
<!-- top slab -->
<path fill="#7C5CFF" d="M90 42 L194 42 L164 72 L60 72 Z"/>
<path fill="#6949E0" d="M60 72 L164 72 L164 83 L60 83 Z"/>
<path fill="#5B3FC9" d="M194 42 L194 53 L164 83 L164 72 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<title>SchFS — Uberblock</title>
<desc>The uberblock ring: slots written round-robin, one of them newest.
Mount takes the highest-numbered slot that validates, which is the whole
consistency argument for the format.
Every slot is an explicit annular sector. An earlier version drew them with
stroke-dasharray on a circle, which is shorter to write and wrong in two ways:
a dash centred on the path start straddles the closure and renders as TWO
pieces with a seam between them, and the normal slot underneath the marked one
still drew, showing through. Neither can happen here -- the marked slot is one
shape and the slot it replaces is simply not emitted.
12 slots at 20 degrees of ink and 10 of gap, which closes at 360 exactly. The
marked slot is index 1, counting clockwise from the top.
It is marked twice over: it carries the accent, the same colour as the root at
the centre, and it reaches outward past the ring while sharing the inner edge
with every other slot. The colour is the reading on screen; the size is what
survives include/logo.h, which is a single coverage channel and keeps no
colour at all.
Stands alone, with no reference to SchOS, as projects/schfs-linux/README.md
requires.</desc>
<g fill="#3C2E85">
<path d="M85.93 20.23 A81.00 81.00 0 0 1 114.07 20.23 L110.25 41.90 A59.00 59.00 0 0 0 89.75 41.90 Z"/>
<path d="M162.05 47.93 A81.00 81.00 0 0 1 176.12 72.30 L155.44 79.82 A59.00 59.00 0 0 0 145.20 62.08 Z"/>
<path d="M179.77 85.93 A81.00 81.00 0 0 1 179.77 114.07 L158.10 110.25 A59.00 59.00 0 0 0 158.10 89.75 Z"/>
<path d="M176.12 127.70 A81.00 81.00 0 0 1 162.05 152.07 L145.20 137.92 A59.00 59.00 0 0 0 155.44 120.18 Z"/>
<path d="M152.07 162.05 A81.00 81.00 0 0 1 127.70 176.12 L120.18 155.44 A59.00 59.00 0 0 0 137.92 145.20 Z"/>
<path d="M114.07 179.77 A81.00 81.00 0 0 1 85.93 179.77 L89.75 158.10 A59.00 59.00 0 0 0 110.25 158.10 Z"/>
<path d="M72.30 176.12 A81.00 81.00 0 0 1 47.93 162.05 L62.08 145.20 A59.00 59.00 0 0 0 79.82 155.44 Z"/>
<path d="M37.95 152.07 A81.00 81.00 0 0 1 23.88 127.70 L44.56 120.18 A59.00 59.00 0 0 0 54.80 137.92 Z"/>
<path d="M20.23 114.07 A81.00 81.00 0 0 1 20.23 85.93 L41.90 89.75 A59.00 59.00 0 0 0 41.90 110.25 Z"/>
<path d="M23.88 72.30 A81.00 81.00 0 0 1 37.95 47.93 L54.80 62.08 A59.00 59.00 0 0 0 44.56 79.82 Z"/>
<path d="M47.93 37.95 A81.00 81.00 0 0 1 72.30 23.88 L79.82 44.56 A59.00 59.00 0 0 0 62.08 54.80 Z"/>
</g>
<g fill="#7C5CFF">
<path d="M132.15 11.67 A94.00 94.00 0 0 1 160.42 27.99 L137.92 54.80 A59.00 59.00 0 0 0 120.18 44.56 Z"/>
<circle cx="100" cy="100" r="20"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

50
docs/Website/README.md Normal file
View file

@ -0,0 +1,50 @@
<!-- SPDX-License-Identifier: Apache-2.0 -->
# The website
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
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)
```
## The rules it follows
**The palette is not a design decision.** It is `include/theme.h`, copied. A
site describing an operating system should look like the operating system, and
copying the header means the two cannot drift apart by accident.
**PSOGM purple appears exactly once.** `#4B1D9A` is on the crash-screen section
and nowhere else, because [../branding/README.md](../branding/README.md) says
it means *the machine stopped* and that a colour used everywhere means nothing.
If it shows up in a second place, that is a bug in the page.
**The typefaces are the ones the OS ships.** Roboto for text and JetBrains Mono
for the system voice — both already in `ports/`, both already in `NOTICE`. They
are requested from the system rather than downloaded: a font file would be
larger than the entire rest of the site.
**The script is optional.** If `app.js` fails to load the page is still
complete and still readable. Everything it does is decoration, and it honours
`prefers-reduced-motion` by doing none of it.
**The boot log is real.** The lines in `app.js` are from an actual boot, not
invented. The timer calibration line is there deliberately; see section 13 of
[../history/kernel.md](../history/kernel.md) for what it cost.
## Editing it
The numbers in the *Numbers* section are measured, not estimated, and they go
stale. To refresh them:
```sh
find kernel include -name '*.c' -o -name '*.h' | xargs wc -l | tail -1
grep -c '^SYSCALL' kernel/sys/table.inc
```
The feature cards carry a tag — `runs`, `in progress`, or `designed` — and the
tag is a promise. Anything marked `runs` is in the tree and works today. When
that stops being true, change the tag rather than the wording.

92
docs/Website/app.js Normal file
View file

@ -0,0 +1,92 @@
/* SPDX-License-Identifier: Apache-2.0
*
* Two behaviours, no framework:
* 1. the hero console types a real boot log
* 2. sections fade in once, as they arrive
*
* Both are decoration. If this file fails to load the page is still complete
* and still readable, which is the test any script on a documentation site
* should have to pass.
*/
(function () {
"use strict";
var reduced = window.matchMedia &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
/* ---- 1. the boot log ------------------------------------------------
* These are real lines from a real boot, not invented ones. The timer
* calibration line in particular is there because of what it cost: the
* clock ran seven times slow until a graphics driver was made to
* acknowledge its own interrupt. See docs/history/kernel.md.
*/
var LINES = [
{ t: "[ 0.000]", k: "SchIFT", s: " 64-bit, 2 cpus" },
{ t: "[ 0.004]", k: "cpu0", s: ": lapic timer 999949260 Hz, tsc 3792 MHz" },
{ t: "[ 0.061]", k: "virtio-gpu", s: ": 1280x800, 4000 KB framebuffer, irq 11" },
{ t: "[ 0.088]", k: "e1000", s: ": 52:54:00:12:34:56, link up, irq 11" },
{ t: "[ 0.143]", k: "schfs0", s: ": mounted, generation 41" },
{ t: "[ 0.169]", k: "net", s: ": eth0 10.0.2.15/255.255.255.0" },
{ t: "[ 0.204]", k: "schinit", s: ": 4 jobs", g: true },
{ t: "", k: "", s: "# " }
];
var out = document.getElementById("bootlog");
function span(cls, text) {
var e = document.createElement("span");
if (cls) e.className = cls;
e.textContent = text;
return e;
}
function emit(line) {
if (line.t) {
out.appendChild(span("t", line.t + " "));
out.appendChild(span("k", line.k));
}
out.appendChild(span(line.g ? "g" : "", line.s));
out.appendChild(document.createTextNode("\n"));
}
function cursor() {
var c = document.createElement("i");
c.className = "cursor";
out.appendChild(c);
}
if (!out) {
/* nothing to do */
} else if (reduced) {
LINES.forEach(emit); // the whole log at once, no animation
cursor();
} else {
var i = 0;
(function step() {
if (i >= LINES.length) { cursor(); return; }
emit(LINES[i++]);
/* Uneven on purpose: a boot that ticks out at a metronome's pace reads
as a progress bar rather than as a machine doing different work. */
setTimeout(step, 180 + Math.random() * 220);
})();
}
/* ---- 2. reveal on scroll ------------------------------------------ */
var targets = document.querySelectorAll(".reveal");
if (reduced || !("IntersectionObserver" in window)) {
Array.prototype.forEach.call(targets, function (el) { el.classList.add("in"); });
return;
}
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
entry.target.classList.add("in");
io.unobserve(entry.target); // once, not on every scroll past
});
}, { rootMargin: "0px 0px -12% 0px", threshold: 0.05 });
Array.prototype.forEach.call(targets, function (el) { io.observe(el); });
})();

294
docs/Website/index.html Normal file
View file

@ -0,0 +1,294 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SchOS — a 64-bit operating system written from scratch</title>
<meta name="description" content="SchOS is a from-scratch x86-64 operating system: the SchIFT kernel, the SchFS copy-on-write filesystem, a UEFI loader, and a userland that runs unmodified musl and toybox.">
<link rel="icon" href="Assets/shift.svg" type="image/svg+xml">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="top">
<div class="wrap">
<a class="brand" href="#top">
<img src="Assets/shift.svg" alt="">
SchOS
</a>
<nav>
<a href="#origin">Origin</a>
<a href="#stack">The stack</a>
<a href="#what">What it has</a>
<a href="#numbers">Numbers</a>
<a href="#docs">Docs</a>
</nav>
</div>
</header>
<main id="top">
<section class="hero">
<div class="wrap">
<div>
<h1><img class="mark" src="Assets/shift.svg" alt="">SchOS</h1>
<p>
A 64-bit operating system written from scratch — its own kernel, its own
filesystem, its own bootloader. It boots on UEFI, drives real hardware,
and runs unmodified <code>musl</code> and <code>toybox</code> on top.
</p>
<div class="cta">
<a class="btn primary" href="#what">What it has</a>
<a class="btn" href="#origin">Where it started</a>
</div>
</div>
<div class="console" aria-label="Boot log">
<div class="console-bar">
<i class="dot live"></i> ttyS0
<span>SchIFT</span>
</div>
<pre id="bootlog" aria-live="polite"></pre>
</div>
</div>
</section>
<section id="origin" class="reveal">
<div class="wrap">
<p class="eyebrow">Origin</p>
<h2>It started as a teaching kernel and stopped being one</h2>
<p class="lede">
SchOS began as <a href="https://github.com/mit-pdos/xv6-public">xv6</a>, MIT's
teaching operating system: 32-bit, single core, one filesystem, a few dozen
syscalls. Almost none of that survives. What follows is what replaced it.
</p>
<div class="origin-grid">
<div class="step">
<span class="when">First</span>
<h3>64-bit, and more than one core</h3>
<p>
Four-level paging, a real SMP bring-up, per-CPU state and a trap path
that survives both. This is the part that invalidates most of what a
32-bit uniprocessor kernel assumes.
</p>
</div>
<div class="step">
<span class="when">Then</span>
<h3>Its own filesystem</h3>
<p>
xv6's log-structured filesystem was replaced by <b>SchFS</b>: a
copy-on-write B-tree with an uberblock ring, extents and checksummed
metadata. A crash leaves either the old tree or the new one.
</p>
</div>
<div class="step">
<span class="when">Then</span>
<h3>A Linux-shaped ABI</h3>
<p>
The syscall table adopted Linux numbering, which is what makes an
unmodified musl and an unmodified toybox run. The table is one
<code>.inc</code> file, and it is the seam the whole system is built around.
</p>
</div>
<div class="step">
<span class="when">Now</span>
<h3>Hardware, not emulation</h3>
<p>
A UEFI loader, xHCI and USB storage, an e1000 with a real TCP/IP stack,
virtio-gpu and VBE framebuffers, and a device model where a driver never
names its consumer.
</p>
</div>
</div>
</div>
</section>
<section id="stack" class="reveal">
<div class="wrap">
<p class="eyebrow">Architecture</p>
<h2>Two seams hold the whole thing together</h2>
<p class="lede">
Above the syscall table, software is ordinary and portable. Below the device
model, a driver never names its consumer and a consumer never names a driver.
Everything else is arrangement.
</p>
<div class="stack">
<div class="layer"><span class="name">userland</span><span class="what">toybox, mksh, musl, and the first-party programs in <code>cmd/</code></span></div>
<div class="layer"><span class="name">schinit</span><span class="what">PID 1 — job manifests and supervision</span></div>
<div class="layer seam"><span class="name">syscall table</span><span class="what">Linux numbering, one <code>.inc</code> file — the ABI seam</span></div>
<div class="layer"><span class="name">kernel services</span><span class="what">VFS, sockets, signals, mmap, poll, procfs</span></div>
<div class="layer"><span class="name">subsystems</span><span class="what">SchFS, lwIP, text and graphics, the buffer cache</span></div>
<div class="layer seam"><span class="name">device model</span><span class="what">class vtables and bus binding — the driver seam</span></div>
<div class="layer"><span class="name">drivers</span><span class="what">xHCI, e1000, virtio-gpu, VBE, IDE, PS/2</span></div>
<div class="layer"><span class="name">SchIFT core</span><span class="what">scheduler, paging, traps, IRQ, SMP, DMA</span></div>
<div class="layer"><span class="name">boot</span><span class="what">the UEFI loader in <code>boot/uefi</code></span></div>
</div>
<p class="seam-note">
<code>uname -s</code> reports <b>SchIFT</b> — the kernel — the way every Unix
reports its kernel rather than its distribution.
</p>
</div>
</section>
<section id="what" class="reveal">
<div class="wrap">
<p class="eyebrow">What it has</p>
<h2>Built and running, and honest about the rest</h2>
<p class="lede">
Every card marked <b>runs</b> is in the tree and works today. The tags are
not aspirational; the things that are not finished say so.
</p>
<div class="cards">
<div class="card">
<span class="tag runs">runs</span>
<h3><img class="cardmark" src="Assets/uberblock.svg" alt="">SchFS</h3>
<p>
Copy-on-write B-tree, a 128-slot uberblock ring, extents with refcounts
and reflink, CRC32c on metadata, and free space derived from the tree
rather than stored beside it — so there is no second structure to
disagree with the first. There is nothing to repair after a crash.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>SMP x86-64 kernel</h3>
<p>
Four-level paging, multiple cores, preemptive scheduling, a calibrated
LAPIC timer, real signal delivery and job control. 205 syscalls on
Linux numbering.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>Networking</h3>
<p>
An e1000 driver under lwIP, with sockets for AF_INET, AF_UNIX and
AF_NETLINK. DHCP brings the interface up at boot; <code>ping</code>
reaches the outside world.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>UEFI boot</h3>
<p>
A PE/COFF loader built with clang and lld-link that sets up its own page
tables and hands the kernel a framebuffer. No BIOS path, no chainloading.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>USB</h3>
<p>
xHCI, hubs, HID and mass storage — a keyboard and a USB disk that the
kernel can mount, through the same device model as everything else.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>A real userland</h3>
<p>
musl and toybox, both unmodified, plus mksh as <code>/bin/sh</code>.
<b>fastfetch</b> builds from upstream source with an empty
<code>patches/</code> directory — when it could not detect something, the
answer was to give the kernel what it was looking for.
</p>
</div>
<div class="card">
<span class="tag partial">in progress</span>
<h3>Schcom</h3>
<p>
The compositor that will own the display. v0 runs. The desktop above it
is the next large piece of work, and it is deliberately Wayland-shaped.
</p>
</div>
<div class="card">
<span class="tag planned">designed</span>
<h3>SchAPI</h3>
<p>
The native programming interface — specified, not built, and deliberately
so. It should be designed <i>from</i> the compositor's experience rather
than before it.
</p>
</div>
</div>
</div>
</section>
<section id="numbers" class="reveal">
<div class="wrap">
<p class="eyebrow">Numbers</p>
<h2>The size of it</h2>
<div class="figures">
<div class="figure"><b>43,701</b><span>lines in <code>kernel/</code> and <code>include/</code></span></div>
<div class="figure"><b>205</b><span>syscalls, Linux numbering</span></div>
<div class="figure"><b>128</b><span>uberblock ring slots</span></div>
<div class="figure"><b>0</b><span>patches carried against fastfetch</span></div>
</div>
</div>
</section>
<section class="crash reveal">
<div class="wrap">
<img src="Assets/guru.svg" alt="Guru, the SchOS crash-screen icon">
<div>
<p class="eyebrow">When it stops</p>
<h2>PSOGM</h2>
<p>
When the kernel panics it does not scroll a stack trace past you. It draws
a full-screen report — what failed, where, and the state that mattered —
and encodes the whole thing as a QR code so it can leave the machine
without being retyped.
</p>
<p>
This purple is <code>#4B1D9A</code>, and the theme reserves it for exactly
this. Normal states are calm and dark; only failure is loud. It is the one
colour in the system that means something, which is why it appears once on
this page and nowhere else.
</p>
</div>
</div>
</section>
<section id="docs" class="reveal">
<div class="wrap">
<p class="eyebrow">Reading</p>
<h2>Where to look</h2>
<p class="lede">
The documentation is written to outlive the work. Anything finished is
described in <code>architecture/</code>; anything intended lives in
<code>plan/</code>; and the bugs worth remembering are kept in
<code>history/</code>, in full, including the wrong turns.
</p>
<div class="links">
<a class="link-card" href="../architecture/README.md">
<b>Architecture</b><span>What the system is, layer by layer</span>
</a>
<a class="link-card" href="../plan/1.0.md">
<b>The 1.0 definition</b><span>What is in, what is out, and why</span>
</a>
<a class="link-card" href="../history/kernel.md">
<b>Kernel history</b><span>The bugs, and what each one taught</span>
</a>
<a class="link-card" href="../guides/building.md">
<b>Building it</b><span>Toolchain, ports, and running under QEMU</span>
</a>
</div>
</div>
</section>
</main>
<footer>
<div class="wrap">
<span>SchOS — kernel <b>SchIFT</b>, filesystem <b>SchFS</b>.</span>
<span class="mono">Apache-2.0, with the xv6-derived core under MIT.</span>
</div>
</footer>
<script src="app.js"></script>
</body>
</html>

371
docs/Website/style.css Normal file
View file

@ -0,0 +1,371 @@
/* SPDX-License-Identifier: Apache-2.0
*
* The palette is not chosen here. It is include/theme.h, transcribed, because
* a site that describes an operating system should look like the operating
* system. That includes the rule theme.h enforces:
*
* normal states are calm and dark; only failure is loud.
*
* PSOGM purple (--psogm) therefore appears exactly once on this page, in the
* crash-screen section. If it ever appears anywhere else, delete it there.
*/
:root {
/* include/theme.h */
--bg: #0C0E13; /* SCH_BG */
--fg: #E6E8EC; /* SCH_FG */
--dim: #707784; /* SCH_DIM */
--accent: #7C5CFF; /* SCH_ACCENT */
--accent-dim: #2A2440; /* SCH_ACCENT_DIM */
--ok: #4ADE80; /* SCH_OK */
--warn: #FBBF24; /* SCH_WARN */
--psogm: #4B1D9A; /* PSOGM_BG — failure, and nothing else */
/* Surfaces lifted off the ground, keeping its blue bias rather than
sliding toward neutral grey. */
--surface: #12151C;
--surface-2: #171B24;
--line: #212632;
--sans: "Roboto", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "Cascadia Code",
Menlo, Consolas, monospace;
--measure: 66ch;
--gap: clamp(1rem, 2.5vw, 1.75rem);
--section: clamp(4rem, 10vw, 7.5rem);
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
}
body {
margin: 0;
background: var(--bg);
color: var(--fg);
font-family: var(--sans);
font-size: clamp(1rem, 0.95rem + 0.2vw, 1.0625rem);
line-height: 1.65;
-webkit-font-smoothing: antialiased;
}
/* ---- shared shells ------------------------------------------------- */
.wrap {
width: min(100% - 2.5rem, 68rem);
margin-inline: auto;
}
section { padding-block: var(--section); }
.eyebrow {
font-family: var(--mono);
font-size: 0.75rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--accent);
margin: 0 0 0.75rem;
}
h1, h2, h3 { text-wrap: balance; line-height: 1.15; margin: 0; }
h2 {
font-size: clamp(1.75rem, 1.3rem + 2vw, 2.75rem);
font-weight: 700;
letter-spacing: -0.02em;
}
.lede {
max-width: var(--measure);
color: var(--dim);
font-size: clamp(1.05rem, 1rem + 0.35vw, 1.2rem);
margin: 1.25rem 0 0;
}
p { max-width: var(--measure); }
a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--fg); }
a:focus-visible,
button:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
border-radius: 2px;
}
code, .mono { font-family: var(--mono); font-size: 0.9em; }
code { color: var(--fg); background: var(--surface-2); padding: 0.1em 0.4em; border-radius: 3px; }
/* ---- header -------------------------------------------------------- */
header.top {
position: sticky;
top: 0;
z-index: 10;
background: color-mix(in srgb, var(--bg) 88%, transparent);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--line);
}
.top .wrap {
display: flex;
align-items: center;
gap: 1.5rem;
padding-block: 0.85rem;
}
.brand {
display: flex; align-items: center; gap: 0.6rem;
font-weight: 700; letter-spacing: -0.01em;
color: inherit; text-decoration: none;
}
.brand img { width: 26px; height: 26px; display: block; }
.top nav { margin-left: auto; display: flex; gap: 1.5rem; font-size: 0.9rem; }
.top nav a { color: var(--dim); text-decoration: none; }
.top nav a:hover { color: var(--fg); }
@media (max-width: 46rem) { .top nav { display: none; } }
/* ---- hero ---------------------------------------------------------- */
.hero { padding-block: clamp(3.5rem, 9vw, 6.5rem) var(--section); }
.hero .wrap {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
gap: clamp(2rem, 5vw, 4rem);
align-items: center;
}
@media (max-width: 56rem) {
.hero .wrap { grid-template-columns: 1fr; }
}
.hero h1 {
font-size: clamp(2.75rem, 1.8rem + 4.2vw, 4.75rem);
font-weight: 700;
letter-spacing: -0.035em;
}
.hero h1 .mark {
display: inline-block;
width: 0.95em;
height: 0.95em;
vertical-align: -0.08em;
margin-right: 0.22em;
}
.hero p { color: var(--dim); font-size: clamp(1.05rem, 1rem + 0.4vw, 1.25rem); margin-top: 1.25rem; }
.hero .cta { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 2rem; }
.btn {
display: inline-block;
padding: 0.65rem 1.25rem;
border-radius: 4px;
font-size: 0.925rem;
font-weight: 500;
text-decoration: none;
border: 1px solid var(--line);
color: var(--fg);
background: var(--surface);
}
.btn:hover { border-color: var(--accent); color: var(--fg); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #0B0714; font-weight: 700; }
.btn.primary:hover { background: #8F73FF; border-color: #8F73FF; color: #0B0714; }
/* ---- console ------------------------------------------------------- */
.console {
background: #090B0F;
border: 1px solid var(--line);
border-radius: 6px;
overflow: hidden;
box-shadow: 0 24px 60px -30px #000;
}
.console-bar {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.55rem 0.85rem;
border-bottom: 1px solid var(--line);
background: var(--surface);
font-family: var(--mono);
font-size: 0.72rem;
color: var(--dim);
letter-spacing: 0.06em;
}
.console-bar .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--line); }
.console-bar .dot.live { background: var(--ok); }
.console-bar span { margin-left: auto; }
.console pre {
margin: 0;
padding: 1rem 1.1rem 1.4rem;
font-family: var(--mono);
font-size: clamp(0.72rem, 0.62rem + 0.35vw, 0.82rem);
line-height: 1.85;
color: var(--fg);
overflow-x: auto;
min-height: 16.5rem;
}
.console .t { color: var(--dim); }
.console .k { color: var(--accent); }
.console .g { color: var(--ok); }
.console .cursor {
display: inline-block;
width: 0.55em;
height: 1.05em;
background: var(--accent);
vertical-align: -0.18em;
animation: blink 1.1s step-end infinite;
}
@keyframes blink { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .console .cursor { animation: none; } }
/* ---- origin timeline ----------------------------------------------- */
.origin-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
gap: var(--gap);
margin-top: 3rem;
}
.step {
border-left: 2px solid var(--accent-dim);
padding-left: 1.1rem;
}
.step h3 { font-size: 1.05rem; font-weight: 700; margin-bottom: 0.4rem; }
.step .when { font-family: var(--mono); font-size: 0.72rem; color: var(--accent); letter-spacing: 0.08em; text-transform: uppercase; display: block; margin-bottom: 0.5rem; }
.step p { color: var(--dim); font-size: 0.925rem; margin: 0; }
/* ---- the stack ----------------------------------------------------- */
.stack { display: flex; flex-direction: column; gap: 3px; margin-top: 3rem; }
.layer {
display: grid;
grid-template-columns: 10.5rem 1fr;
gap: 1.25rem;
align-items: baseline;
padding: 0.95rem 1.15rem;
background: var(--surface);
border: 1px solid transparent;
border-radius: 3px;
}
.layer:hover { border-color: var(--accent-dim); background: var(--surface-2); }
.layer .name { font-family: var(--mono); font-size: 0.8rem; color: var(--accent); letter-spacing: 0.04em; }
.layer .what { color: var(--dim); font-size: 0.9rem; }
.layer.seam { background: var(--accent-dim); }
.layer.seam .name { color: var(--fg); }
.layer.seam .what { color: #C9BEEC; }
@media (max-width: 42rem) {
.layer { grid-template-columns: 1fr; gap: 0.3rem; }
}
.seam-note { margin-top: 1.5rem; color: var(--dim); font-size: 0.9rem; }
/* ---- features ------------------------------------------------------ */
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
gap: var(--gap);
margin-top: 3rem;
}
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: 5px;
padding: 1.4rem;
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.card h3 { font-size: 1.05rem; font-weight: 700; }
.card p { color: var(--dim); font-size: 0.925rem; margin: 0; max-width: none; }
.card .tag {
align-self: flex-start;
font-family: var(--mono);
font-size: 0.68rem;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 0.2rem 0.5rem;
border-radius: 3px;
border: 1px solid var(--line);
color: var(--dim);
}
.card .tag.runs { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 35%, transparent); }
.card .tag.partial{ color: var(--warn); border-color: color-mix(in srgb, var(--warn) 35%, transparent); }
.card .tag.planned{ color: var(--dim); }
/* ---- numbers ------------------------------------------------------- */
.figures {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
gap: var(--gap);
margin-top: 3rem;
}
.figure { border-top: 1px solid var(--line); padding-top: 1rem; }
.figure b {
display: block;
font-family: var(--mono);
font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.25rem);
font-weight: 700;
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
}
.figure span { color: var(--dim); font-size: 0.85rem; }
/* ---- the crash screen: the one loud thing --------------------------- */
.crash { background: var(--psogm); color: #fff; }
.crash .wrap {
display: grid;
grid-template-columns: 8rem minmax(0, 1fr);
gap: clamp(1.5rem, 4vw, 3rem);
align-items: start;
}
@media (max-width: 44rem) { .crash .wrap { grid-template-columns: 1fr; } }
.crash img { width: 100%; max-width: 8rem; height: auto; }
.crash .eyebrow { color: #C3AEEC; }
.crash h2 { color: #fff; }
.crash p { color: #DBCEF5; }
.crash code { background: rgba(0, 0, 0, 0.28); color: #fff; }
/* ---- closing links -------------------------------------------------- */
.links { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: var(--gap); margin-top: 2.5rem; }
.link-card {
display: block;
padding: 1.2rem;
border: 1px solid var(--line);
border-radius: 5px;
text-decoration: none;
color: var(--fg);
background: var(--surface);
}
.link-card:hover { border-color: var(--accent); color: var(--fg); }
.link-card b { display: block; margin-bottom: 0.3rem; }
.link-card span { color: var(--dim); font-size: 0.875rem; }
footer {
border-top: 1px solid var(--line);
padding-block: 2.5rem 3.5rem;
color: var(--dim);
font-size: 0.85rem;
}
footer .wrap { display: flex; flex-wrap: wrap; gap: 1rem 2rem; align-items: center; }
footer .mono { font-size: 0.78rem; }
/* ---- reveal on scroll ---------------------------------------------- */
.reveal { opacity: 0; transform: translateY(12px); transition: opacity .5s ease, transform .5s ease; }
.reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
.reveal { opacity: 1; transform: none; transition: none; }
}
/* The SchFS mark, set beside its own card heading. Baseline-aligned rather
than centred: it sits next to a word, so it follows the word. */
.card h3 .cardmark {
width: 1.15em;
height: 1.15em;
vertical-align: -0.22em;
margin-right: 0.4em;
}