From 2f3833a295fd97f6e65e12b8d2893c737285488b Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Sat, 5 Sep 2026 01:47:36 +0300 Subject: [PATCH] refactor(docs): rebuild the site around reading, not launching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generated site had the shape of a product launch page: a near-black navy ground with mint/coral radial glows, a gradient-clipped headline, a glowing pill badge, nine emoji feature cards and scroll-reveal animations. None of it told a reader anything about the language. It is now typographic and light-first — a warm paper ground, a serif display face, one ink-blue accent used only where it means something, and rules instead of floating cards. Colour is reserved for code. Dark mode is the same design with the ground inverted, defined once as tokens under a single prefers-color-scheme block. Structurally: - base.css holds the tokens and shared chrome; site.css and docs.css hold what is specific to the landing page and the reference pages. They ship as linked files rather than being inlined into all 900+ pages, which takes the site from 16 MB to 5 MB and means a design change no longer needs a regenerate to be seen. - api.css was dead — the generator never referenced it, rendering the API index with item.css — and is gone. docs.css replaces item.css and covers all four reference page kinds. - Grids draw their separators as cell borders instead of bleeding a ruled background through gaps, so a final row with fewer cards than columns stops cleanly instead of leaving a grey hole. The feature card count is not a multiple of the column count at any breakpoint. - Inline code loses its tinted chip; in a language reference, a box behind every keyword turns a paragraph into confetti. - Fonts are the platform's own, so the site makes no webfont request. Co-Authored-By: Claude Opus 5 --- .claude/launch.json | 6 + changes/site-redesign.md | 22 + docs/site/site.json | 245 ++++++++--- docs/site/snippets/hero.ludic | 2 +- tools/docgen/assets/api.css | 83 ---- tools/docgen/assets/base.css | 268 ++++++++++++ tools/docgen/assets/docs.css | 249 +++++++++++ tools/docgen/assets/index.tmpl.html | 87 ++-- tools/docgen/assets/item.css | 176 -------- tools/docgen/assets/ludic-highlight.tmpl.js | 6 +- tools/docgen/assets/site.css | 448 +++++++++++--------- tools/x/docgen_gen.ludic | 25 +- 12 files changed, 1046 insertions(+), 571 deletions(-) create mode 100644 changes/site-redesign.md delete mode 100644 tools/docgen/assets/api.css create mode 100644 tools/docgen/assets/base.css create mode 100644 tools/docgen/assets/docs.css delete mode 100644 tools/docgen/assets/item.css diff --git a/.claude/launch.json b/.claude/launch.json index fd0e3829..2f8d274d 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -6,6 +6,12 @@ "runtimeExecutable": "python3", "runtimeArgs": ["-m", "http.server", "8123", "-d", "build/web"], "port": 8123 + }, + { + "name": "ludic-docs", + "runtimeExecutable": "python3", + "runtimeArgs": ["-m", "http.server", "8124", "-d", "build/pages"], + "port": 8124 } ] } diff --git a/changes/site-redesign.md b/changes/site-redesign.md new file mode 100644 index 00000000..774babdb --- /dev/null +++ b/changes/site-redesign.md @@ -0,0 +1,22 @@ +bump: minor +type: docs +The generated site is redesigned around reading rather than launching: a warm +paper ground with a serif display face, one ink-blue accent, and rules instead +of floating cards. Colour is reserved for code. Dark mode is the same design +with the ground inverted, driven entirely by tokens under one +`prefers-color-scheme` block, and the landing page's scroll-reveal animations, +gradient headline, glowing badge and emoji feature icons are gone. + +- Stylesheets are linked files (`base.css` + `site.css`/`docs.css`) instead of + being inlined into all 900+ pages, which cuts the published site from 16 MB to + 5 MB and means a design change no longer requires regenerating to be seen. +- Fonts are the platform's own; the site makes no webfont request. +- `api.css` was dead — the generator never referenced it — and is removed along + with `item.css`, which `docs.css` replaces. +- A page no longer flashes its own title on every plain visit; only a deep link + highlights its target, and under `prefers-reduced-motion` the highlight no + longer stays on the element permanently. +- The copy leads with what is verifiable — ahead-of-time compiled, an ECS in the + syntax, deterministic fixed-point, no C in a build — and the "get started" + steps now begin with the clang-plus-seed bootstrap, without which `bin/x` does + not exist on a clean checkout. diff --git a/docs/site/site.json b/docs/site/site.json index 8848ceaf..290012a2 100644 --- a/docs/site/site.json +++ b/docs/site/site.json @@ -1,108 +1,233 @@ { "brand": "Ludic", - "tagline": "The opinionated, compiled language for 2D games.", + "tagline": "A compiled language for 2D games, with the entity system in the syntax.", "repo_url": "https://git.workshopsoft.io/workshopsoft/ludic", "meta": { - "title": "Ludic — the opinionated game language", - "description": "Ludic is an opinionated, compiled language for 2D games. The entity system, rendering, named colors, input, deterministic math and save/load are part of the language — so you build a game, not a framework.", - "og_title": "Ludic — the opinionated game language", - "og_description": "An opinionated, compiled language for 2D games. The ECS, rendering, colors, input and save/load are built in — build a game, not a framework." + "title": "Ludic — a compiled game language", + "description": "Ludic compiles ahead of time to a native binary. The entity-component system is part of the syntax, the runtime is deterministic fixed-point, and the compiler is written in Ludic — no C is generated, compiled or linked in a build.", + "og_title": "Ludic — a compiled game language", + "og_description": "Ahead-of-time compiled, with an ECS in the syntax and a deterministic fixed-point runtime. The compiler is written in Ludic; no C is generated, compiled or linked in a build." }, "nav_links": [ - { "label": "Features", "href": "#features" }, - { "label": "Examples", "href": "#showcase" }, - { "label": "Get started", "href": "#start" }, - { "label": "API Reference", "href": "api.html" } + { + "label": "Features", + "href": "#features" + }, + { + "label": "Examples", + "href": "#showcase" + }, + { + "label": "Get started", + "href": "#start" + }, + { + "label": "API Reference", + "href": "api.html" + } ], "ref_nav_links": [ - { "label": "Home", "href": "index.html" }, - { "label": "API Reference", "href": "api.html" } + { + "label": "Home", + "href": "index.html" + }, + { + "label": "API Reference", + "href": "api.html" + } ], "hero": { - "pill": "Opinionated · Batteries-included · No framework", - "title_pre": "Build a game,", - "title_accent": "not a framework.", - "lead": "Ludic is an opinionated, compiled language for 2D games. The entity system, drawing, named colors, input, deterministic math and save/load aren't libraries you wire up — they're part of the language. You write the game; there's nothing to assemble first.", + "title_pre": "A compiled language", + "title_accent": "for 2D games.", + "lead": "Ludic compiles ahead of time to a native binary — no engine to install, no interpreter, nothing shipped beside the executable. The entity-component system is part of the syntax, the maths is deterministic fixed-point, and ludicc is itself written in Ludic: no C is generated, compiled or linked in a build.", "snippet": "docs/site/snippets/hero.ludic", "snippet_name": "hello.ludic", - "primary_cta": { "label": "Get started →", "href": "#start" }, - "secondary_cta": { "label": "API Reference", "href": "api.html" }, - "pipeline": [".ludic", "ludicc", "optimized IR", "native binary"], - "pipeline_note": "One step from your code to a native binary. No engine to install, no interpreter, no runtime to ship alongside it — the game is the executable." + "primary_cta": { + "label": "Get started →", + "href": "#start" + }, + "secondary_cta": { + "label": "API Reference", + "href": "api.html" + }, + "pipeline": [ + ".ludic", + "ludicc", + "LLVM IR", + "native binary" + ], + "pipeline_note": "ludicc lowers straight to LLVM IR and links a native binary. There is no C step in between and no runtime to ship alongside — the game is the executable." }, "features": { "kicker": "Why Ludic", "title": "A language shaped around the game.", - "intro": "The things you normally bolt on — an entity-component system, a renderer, a deterministic clock, save/load — are primitives of the language itself. One opinionated way to do each, so there's little to decide and nothing to wire up.", + "intro": "The things you normally bolt on — an entity-component system, a renderer, a deterministic clock, save/load — are primitives of the language itself. One way to do each, so there is little to decide and nothing to wire up.", "cards": [ - { "icon": "🧩", "title": "ECS in the syntax", "html": "property, model, and handler are keywords. Query entities with query [A, B, {Tag}] and iterate matches directly — no framework to wire up." }, - { "icon": "⏱️", "title": "Deterministic runtime", "html": "Q16.16 fixed-point math and a seeded RNG mean the same inputs produce the same frame — byte-for-byte — every run. Ideal for replays and lockstep netcode." }, - { "icon": "🎨", "title": "Drawing & named colors", "html": "The Screen API draws rectangles, text and pixels with named arguments; Color.Crimson and 220 more names read like English and cost nothing at runtime." }, - { "icon": "📦", "title": "One self-contained binary", "html": "A game compiles to a single native executable — no engine to install, no interpreter, no runtime shipped beside it. Build, and run the file." }, - { "icon": "💾", "title": "Snapshot save/load", "html": "save() and load() serialize the entire ECS world — every entity, property and program var — in one call." }, - { "icon": "🎬", "title": "Scenes & state machines", "html": "scene/layer/become model mutually-exclusive game states with enter/exit hooks; match/machine/state handle dispatch and per-entity FSMs." }, - { "icon": "🖼️", "title": "Retained UI & assets", "html": "Declare a widget tree as data with ui — panels, labels, buttons, 9-slice skins, keyboard focus. Sprites decode from PNG at runtime; text is real TrueType." }, - { "icon": "🔌", "title": "Modules & native calls", "html": "module + @export fn builds a shared library of plain native symbols; extern fn … = \"symbol\" reaches out to any native library when you need the platform." } + { + "title": "ECS in the syntax", + "html": "property, model, and handler are keywords. Query entities with query [A, B, {Tag}] and iterate matches directly — no framework to wire up." + }, + { + "title": "Deterministic runtime", + "html": "Q16.16 fixed-point math and a seeded RNG mean the same inputs produce the same frame — byte-for-byte — every run. Ideal for replays and lockstep netcode." + }, + { + "title": "Drawing & named colors", + "html": "The Screen API draws rectangles, text and pixels with named arguments; Color.Crimson and 220 more names read like English and cost nothing at runtime." + }, + { + "title": "One self-contained binary", + "html": "A game compiles to a single native executable — no engine to install, no interpreter, no runtime shipped beside it. Build, and run the file." + }, + { + "title": "Snapshot save/load", + "html": "save() and load() serialize the entire ECS world — every entity, property and program var — in one call." + }, + { + "title": "Scenes & state machines", + "html": "scene/layer/become model mutually-exclusive game states with enter/exit hooks; match/machine/state handle dispatch and per-entity FSMs." + }, + { + "title": "Retained UI & assets", + "html": "Declare a widget tree as data with ui — panels, labels, buttons, 9-slice skins, keyboard focus. Sprites decode from PNG at runtime; text is real TrueType." + }, + { + "title": "Modules & native calls", + "html": "module + @export fn builds a shared library of plain native symbols; extern fn … = \"symbol\" reaches out to any native library when you need the platform." + } ] }, "showcase": { "kicker": "Show, don't tell", "title": "Real programs, one toolchain.", - "intro": "The same ludicc that builds a JRPG builds a from-scratch Snake and a scene demo. Nothing is hardcoded to a genre — and every token below links into the reference.", + "intro": "The same ludicc that builds a JRPG builds a from-scratch Snake and a scene demo. Nothing is hardcoded to a genre — and every token below links into the reference.", "samples": [ - { "label": "snake", "name": "snake.ludic", "file": "docs/site/snippets/snake.ludic", - "note": "A complete Snake — grid, growth, food, game-over — from primitives. State is named vars, colors are named, and every draw call says what each argument is." }, - { "label": "scenes", "name": "scenes.ludic", "file": "docs/site/snippets/scenes.ludic", - "note": "One active scene at a time. `become` runs the old scene's on-exit and the new one's on-enter; layers draw in declaration order. State is a plain named var." }, - { "label": "lifecycle", "name": "toggle.ludic", "file": "docs/site/snippets/lifecycle.ludic", - "note": "Enable/disable at three scopes — entity, model, handler. Disabling never destroys data: a property's values persist, so a later enable restores them." }, - { "label": "hello", "name": "hello.ludic", "file": "docs/site/snippets/hero.ludic", - "note": "The smallest program that exercises the whole pipeline: properties, a spawn, a queried handler, and a render/quit." } + { + "label": "snake", + "name": "snake.ludic", + "file": "docs/site/snippets/snake.ludic", + "note": "A complete Snake — grid, growth, food, game-over — from primitives. State is named vars, colors are named, and every draw call says what each argument is." + }, + { + "label": "scenes", + "name": "scenes.ludic", + "file": "docs/site/snippets/scenes.ludic", + "note": "One active scene at a time. `become` runs the old scene's on-exit and the new one's on-enter; layers draw in declaration order. State is a plain named var." + }, + { + "label": "lifecycle", + "name": "toggle.ludic", + "file": "docs/site/snippets/lifecycle.ludic", + "note": "Enable/disable at three scopes — entity, model, handler. Disabling never destroys data: a property's values persist, so a later enable restores them." + }, + { + "label": "hello", + "name": "hello.ludic", + "file": "docs/site/snippets/hero.ludic", + "note": "The smallest program that exercises the whole pipeline: properties, a spawn, a queried handler, and a render/quit." + } ] }, "philosophy": { "kicker": "The philosophy", - "title": "No framework. No glue. Just the game.", + "title": "Batteries in the language, not in a framework.", "paras": [ - "Most game code is plumbing — registering systems, wiring a renderer, threading state through a framework. Ludic makes those decisions for you and bakes them into the language, so the code you write is the game's actual logic.", - "Opinionated on purpose: one clear way to spawn an entity, draw a frame, name a color, run a scene. Less to choose, less to learn, less to maintain." + "Most game code is plumbing — registering systems, wiring a renderer, threading state through a framework. Ludic makes those decisions once and bakes them into the language, so the code you write is the game's actual logic.", + "That extends to the toolchain. ludicc is written in Ludic and recompiles its own source to a byte-identical binary, rebuilding from a checked-in IR seed with clang and nothing else." ], "stats": [ - { "big": "0", "lbl": "engines to install" }, - { "big": "1", "lbl": "clear way to do each thing" }, - { "big": "=", "lbl": "deterministic frames" } + { + "big": "0", + "lbl": "lines of C in a build" + }, + { + "big": "1", + "lbl": "backend: LLVM IR" + }, + { + "big": "=", + "lbl": "byte-exact self-rebuild" + } ] }, "start": { "kicker": "Get started", "title": "From clone to a native window.", - "intro": "Build the toolchain once, then your game. Everything runs from the repo root.", + "intro": "Bootstrap the task runner once from the checked-in IR seed, then build the toolchain and your game. Everything runs from the repo root.", "steps": [ - { "title": "Build the toolchain", "html": "bin/x build produces ludicc, the compiler you'll use for everything below." }, - { "title": "Compile & run an example", "html": "bin/x app examples/games/snake.ludic turns a .ludic file into a native binary. Run it to open a real window." }, - { "title": "Go headless for tests", "html": "--headless renders frames to a .ppm from piped input — deterministic output you can diff." }, - { "title": "Edit with full tooling", "html": "bin/x tools builds the formatter and language server; every editor gets completion, diagnostics and go-to-definition." } + { + "title": "Bootstrap from the seed", + "html": "bin/ is not checked in, so it is created first; then clang assembles the compiler's own checked-in LLVM IR seed, and that compiler builds bin/x, the task runner. This is the only step Ludic cannot do for itself." + }, + { + "title": "Build the toolchain", + "html": "bin/x build produces ludicc, ludic, ludic-fmt and ludic-lsp — all compiled by Ludic, from Ludic." + }, + { + "title": "Compile & run an example", + "html": "bin/x app examples/games/snake.ludic turns a .ludic file into a native binary. Run it to open a real window." + }, + { + "title": "Go headless for tests", + "html": "--headless renders frames to a .ppm from piped input — deterministic output you can diff in CI." + } ], "terminal_name": "zsh — ludic", "terminal": [ - { "comment": "build the toolchain (once)" }, - { "cmd": "bin/x build" }, - { "blank": true }, - { "comment": "build and run an example (opens a window)" }, - { "cmd": "bin/x app examples/games/snake.ludic" }, - { "cmd": "./build/snake" }, - { "blank": true }, - { "comment": "deterministic headless render for tests" }, - { "cmd": "bin/x app examples/games/snake.ludic --headless" }, - { "cmd": "printf 'ddddwww' | ./build/snake_headless" }, - { "out": "→ writes out.ppm" } + { + "comment": "bootstrap the task runner (clang + the IR seed, once)" + }, + { + "cmd": "mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc" + }, + { + "cmd": "bin/ludicc tools/x/main.ludic -o bin/x" + }, + { + "blank": true + }, + { + "comment": "build the toolchain, then an example (opens a window)" + }, + { + "cmd": "bin/x build" + }, + { + "cmd": "bin/x app examples/games/snake.ludic" + }, + { + "cmd": "./build/snake" + }, + { + "blank": true + }, + { + "comment": "deterministic headless render for tests" + }, + { + "cmd": "bin/x app examples/games/snake.ludic --headless" + }, + { + "cmd": "printf 'ddddwww' | ./build/snake_headless" + }, + { + "out": "→ writes build/out.ppm" + } ] }, "editors": { "kicker": "Editor experience", "title": "One language server, every editor.", "intro": "ludic-lsp speaks LSP 3.17 over stdio: context-aware completion, diagnostics from the compiler itself, go-to-definition and rename across imported files, and comment-preserving formatting. It even understands ```ludic fences in Markdown.", - "list": ["VS Code", "JetBrains IDEs", "Neovim", "Helix", "Emacs", "Sublime Text", "Zed"], + "list": [ + "VS Code", + "JetBrains IDEs", + "Neovim", + "Helix", + "Emacs", + "Sublime Text", + "Zed" + ], "note": "bin/x tools builds ludic-fmt and ludic-lsp — the same formatter runs as a CLI for pre-commit hooks and CI." } } diff --git a/docs/site/snippets/hero.ludic b/docs/site/snippets/hero.ludic index ce486ffe..beeb4266 100644 --- a/docs/site/snippets/hero.ludic +++ b/docs/site/snippets/hero.ludic @@ -9,7 +9,7 @@ program Hello { spawn Enemy { Position { column: 10, row: 2 }, Velocity { delta_x: 0, delta_y: 1 } } } - # a handler declares the models it touches; the body + # a handler declares the entities it touches; the body # runs once per match, each property bound by name. @Queries(these: [Position, Velocity]) handler AdvancePositions phase FixedUpdate { diff --git a/tools/docgen/assets/api.css b/tools/docgen/assets/api.css deleted file mode 100644 index 72f21c2d..00000000 --- a/tools/docgen/assets/api.css +++ /dev/null @@ -1,83 +0,0 @@ - :root{ - --bg:#0a0d18; --bg2:#0d1122; --panel:#12172b; --panel2:#151b32; - --line:#232b47; --line2:#2c3660; --text:#c9d2ea; --head:#f2f5ff; --muted:#8290b4; - --mint:#7cf5c4; --coral:#ff5d73; --violet:#c792ea; --blue:#82aaff; --amber:#f6c177; - --c-bg:#0b0f20; --c-comment:#5b6788; --c-key:#ff7eb6; --c-type:#7cf5c4; - --c-str:#f6c177; --c-num:#a6b8ff; --c-annot:#c792ea; --c-fn:#82aaff; --c-punc:#9aa6cc; - --radius:14px; --max:1180px; - } - *{box-sizing:border-box} - html{scroll-behavior:smooth} - body{margin:0;background:var(--bg);color:var(--text); - font-family:"Space Grotesk",system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.6; - -webkit-font-smoothing:antialiased} - a{color:inherit;text-decoration:none} - code,pre,.mono{font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace} - .wrap{max-width:var(--max);margin:0 auto;padding:0 24px} - - header.nav{position:sticky;top:0;z-index:50;backdrop-filter:blur(10px); - background:rgba(10,13,24,.78);border-bottom:1px solid var(--line)} - .nav-in{display:flex;align-items:center;gap:22px;height:64px} - .brand{display:flex;align-items:center;gap:10px;font-weight:700;color:var(--head);font-size:18px} - .logo{width:26px;height:26px;border-radius:7px;display:grid;place-items:center; - background:linear-gradient(135deg,var(--mint),#3fd7a6);color:#06231a;font-weight:700;font-size:15px} - .nav-links{display:flex;gap:24px;margin-left:auto;align-items:center} - .nav-links a{color:var(--muted);font-size:14.5px;font-weight:500;transition:color .15s} - .nav-links a:hover{color:var(--head)} - .nav-cta{border:1px solid var(--line2);padding:8px 15px;border-radius:9px;color:var(--head)!important;background:var(--panel)} - .nav-cta:hover{border-color:var(--mint)} - @media(max-width:760px){.nav-links a:not(.nav-cta){display:none}} - - /* two-column reference layout */ - .ref-layout{display:grid;grid-template-columns:230px 1fr;gap:40px;align-items:start;padding-top:34px;padding-bottom:80px} - @media(max-width:900px){.ref-layout{grid-template-columns:1fr}.side{display:none}} - .side{position:sticky;top:88px;display:flex;flex-direction:column;gap:2px;max-height:calc(100vh - 110px);overflow:auto} - .side a{color:var(--muted);font-size:13.5px;padding:6px 10px;border-radius:8px;border-left:2px solid transparent;transition:all .12s} - .side a:hover{color:var(--head);background:var(--panel)} - .side a.active{color:var(--mint);border-left-color:var(--mint);background:rgba(124,245,196,.06)} - - .ref-intro{margin-bottom:44px} - .ref-intro .kicker{font-size:13px;font-weight:600;letter-spacing:2px;text-transform:uppercase;color:var(--mint);margin-bottom:12px} - .ref-intro h1{font-size:clamp(30px,5vw,46px);margin:0 0 12px;color:var(--head);letter-spacing:-1px;font-weight:700} - .ref-intro p{font-size:17px;color:var(--muted);max-width:640px;margin:0} - - .ref-sec{padding:26px 0 10px;border-top:1px solid var(--line);margin-top:26px} - .ref-sec:first-of-type{border-top:none;margin-top:0} - .ref-sec h2{font-size:24px;color:var(--head);margin:0 0 6px;letter-spacing:-.4px} - .sec-blurb{color:var(--muted);font-size:15px;margin:0 0 22px;max-width:720px} - .sec-blurb code{color:var(--mint);background:rgba(124,245,196,.08);padding:1px 5px;border-radius:5px;font-size:12.5px} - - .entry{padding:16px 0;border-top:1px dashed var(--line)} - .entry:first-of-type{border-top:none} - .entry-head{display:flex;align-items:center;gap:10px} - .entry h3{margin:0;font-size:16.5px;color:var(--head);font-weight:600;font-family:"JetBrains Mono",monospace} - .entry .anchor{color:var(--line2);font-size:15px;opacity:0;transition:opacity .12s} - .entry:hover .anchor{opacity:1} - .entry .anchor:hover{color:var(--mint)} - .entry .sig{display:inline-block;margin:8px 0 6px;color:var(--c-fn);background:var(--c-bg); - border:1px solid var(--line);border-radius:8px;padding:5px 10px;font-size:13px} - .entry p{margin:6px 0 0;color:var(--text);font-size:14.5px;max-width:720px} - .entry p code{color:var(--amber);background:var(--panel);padding:1px 5px;border-radius:5px;font-size:12.5px} - .entry p b{color:var(--head)} - - pre{margin:10px 0 0;padding:14px 16px;overflow:auto;font-size:13px;line-height:1.7;tab-size:2; - background:var(--c-bg);border:1px solid var(--line);border-radius:10px} - pre.ex{max-width:720px} - pre::-webkit-scrollbar{height:8px} - pre::-webkit-scrollbar-thumb{background:var(--line2);border-radius:6px} - .t-com{color:var(--c-comment);font-style:italic} - .t-key{color:var(--c-key)}.t-type{color:var(--c-type)}.t-str{color:var(--c-str)} - .t-num{color:var(--c-num)}.t-annot{color:var(--c-annot)}.t-fn{color:var(--c-fn)}.t-punc{color:var(--c-punc)} - /* hover-jump links inside code */ - a.tok{border-radius:3px;transition:background .12s} - a.tok:hover{background:rgba(124,245,196,.14);outline:1px solid rgba(124,245,196,.35)} - - /* color swatches */ - .swatch-group{margin-bottom:22px} - .swatch-group h4{margin:0 0 12px;font-size:13px;letter-spacing:1.5px;text-transform:uppercase;color:var(--muted);font-weight:600} - .swatch-row{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:10px} - .swatch{display:flex;align-items:center;gap:10px;background:var(--panel);border:1px solid var(--line); - border-radius:9px;padding:8px 10px} - .chip{width:24px;height:24px;border-radius:6px;flex:none;box-shadow:inset 0 0 0 1px rgba(255,255,255,.12)} - .cname{font-family:"JetBrains Mono",monospace;font-size:12px;color:var(--head);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} - .chex{margin-left:auto;font-family:"JetBrains Mono",monospace;font-size:11px;color:var(--muted)} diff --git a/tools/docgen/assets/base.css b/tools/docgen/assets/base.css new file mode 100644 index 00000000..186e6811 --- /dev/null +++ b/tools/docgen/assets/base.css @@ -0,0 +1,268 @@ +/* base.css — the design tokens and shared chrome for every generated page. + * + * The site is deliberately typographic and light-first: a warm paper ground, a + * serif display face for headings, one ink-blue accent used only where it means + * something (links, the current page, a focused control), and colour otherwise + * reserved for code. Dark mode is the same design with the ground inverted, not + * a second theme — every colour below is a token, redefined once under + * prefers-color-scheme and nowhere else. + * + * Fonts are the platform's own. A language reference should render instantly + * and identically offline; a webfont round-trip buys nothing here. */ + +:root { + /* ground and ink */ + --paper: #fcfcfa; + --paper-2: #f5f4f0; + --panel: #ffffff; + --ink: #17171a; + --ink-2: #3d3d44; + --muted: #67676f; + --rule: #e3e2dc; + --rule-2: #cfcec6; + + /* the single accent */ + --accent: #0f4c81; + --accent-ink: #0b3a63; + --accent-bg: #eaf1f8; + + /* code surface and tokens */ + --code-bg: #f7f6f2; + --code-rule: #e3e2dc; + --c-comment: #7a7a70; + --c-key: #8f2d56; + --c-type: #0f6b5c; + --c-str: #9a5518; + --c-num: #1d4f8c; + --c-annot: #6b3fa0; + --c-fn: #0f4c81; + --c-punc: #6a6a72; + --c-arg: #9a5518; + + --radius: 6px; + --max: 1080px; + --hdr: 58px; + + --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --serif: ui-serif, "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif; + --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; +} + +@media (prefers-color-scheme: dark) { + :root { + --paper: #16161a; + --paper-2: #1c1c21; + --panel: #1a1a1f; + --ink: #e6e5e0; + --ink-2: #c2c1bb; + --muted: #8f8f98; + --rule: #2b2b32; + --rule-2: #3d3d46; + + --accent: #7fb0dd; + --accent-ink: #a8ccec; + --accent-bg: #1b2733; + + --code-bg: #1b1b21; + --code-rule: #2b2b32; + --c-comment: #7b7b84; + --c-key: #e08aa8; + --c-type: #64c9b4; + --c-str: #d9a066; + --c-num: #8fb8e8; + --c-annot: #b596e0; + --c-fn: #7fb0dd; + --c-punc: #8b8b94; + --c-arg: #d9a066; + } +} + +*, *::before, *::after { box-sizing: border-box } + +html { + -webkit-text-size-adjust: 100%; + scroll-behavior: smooth; +} +@media (prefers-reduced-motion: reduce) { + html { scroll-behavior: auto } +} + +body { + margin: 0; + background: var(--paper); + color: var(--ink-2); + font-family: var(--sans); + font-size: 16px; + line-height: 1.65; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +h1, h2, h3, h4 { + font-family: var(--serif); + color: var(--ink); + font-weight: 600; + line-height: 1.2; + letter-spacing: -0.005em; + margin: 0; +} + +a { color: var(--accent); text-decoration: none } +a:hover { text-decoration: underline; text-underline-offset: 2px } + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + border-radius: 3px; +} + +.wrap { max-width: var(--max); margin: 0 auto; padding: 0 24px } +.muted { color: var(--muted) } +.mono, code, pre, kbd { font-family: var(--mono) } + +/* Inline code carries no chip: the mono face plus the darker ink is already a + clear enough break from running text, and a tinted box behind every keyword + in a language reference turns a paragraph into confetti. */ +code { + font-size: 0.905em; + color: var(--ink); +} +pre code { font-size: inherit } + +pre { + margin: 14px 0 0; + padding: 15px 17px; + overflow-x: auto; + font-size: 13px; + line-height: 1.7; + tab-size: 2; + background: var(--code-bg); + border: 1px solid var(--code-rule); + border-radius: var(--radius); + color: var(--ink); +} +pre::-webkit-scrollbar { height: 9px } +pre::-webkit-scrollbar-thumb { background: var(--rule-2); border-radius: 5px } + +/* syntax tokens, shared by the highlighter and the hand-written terminal block */ +.t-com { color: var(--c-comment); font-style: italic } +.t-key { color: var(--c-key) } +.t-type { color: var(--c-type) } +.t-str { color: var(--c-str) } +.t-num { color: var(--c-num) } +.t-annot { color: var(--c-annot) } +.t-fn { color: var(--c-fn) } +.t-punc { color: var(--c-punc) } +.t-arg { color: var(--c-arg) } + +/* ---------- header ---------- */ + +header.nav { + position: sticky; + top: 0; + z-index: 50; + background: color-mix(in srgb, var(--paper) 92%, transparent); + backdrop-filter: saturate(1.4) blur(8px); + border-bottom: 1px solid var(--rule); +} +.nav-in { display: flex; align-items: center; gap: 14px; min-height: var(--hdr) } + +.brand { + display: inline-flex; + align-items: center; + gap: 9px; + font-family: var(--serif); + font-weight: 600; + font-size: 18px; + color: var(--ink); + letter-spacing: -0.01em; +} +.brand:hover { text-decoration: none } +.logo { + width: 23px; height: 23px; + flex: none; + display: grid; + place-items: center; + border: 1px solid var(--rule-2); + border-radius: 4px; + background: var(--panel); + font-family: var(--mono); + font-size: 12px; + font-weight: 500; + color: var(--accent); +} + +.nav-toggle { + margin-left: auto; + display: inline-flex; + align-items: center; + justify-content: center; + width: 38px; height: 38px; + border: 1px solid var(--rule-2); + border-radius: var(--radius); + background: var(--panel); + color: var(--ink); + font-size: 17px; + line-height: 1; + cursor: pointer; +} + +.nav-links { + display: none; + position: absolute; + top: 100%; left: 0; right: 0; + flex-direction: column; + background: var(--paper); + border-bottom: 1px solid var(--rule); + padding: 0 24px 8px; +} +header.nav.open .nav-links { display: flex } +.nav-links a { + color: var(--ink-2); + font-size: 15px; + padding: 13px 2px; + border-top: 1px solid var(--rule); +} +.nav-links a:first-child { border-top: none } + +@media (min-width: 760px) { + .nav-toggle { display: none } + .nav-links { + display: flex; + position: static; + flex-direction: row; + align-items: center; + gap: 26px; + margin-left: auto; + padding: 0; + border: none; + background: none; + } + .nav-links a { + color: var(--muted); + font-size: 14.5px; + padding: 0; + border: none; + } + .nav-links a:hover { color: var(--ink); text-decoration: none } + .nav-links .nav-cta { color: var(--ink) } +} + +/* ---------- footer ---------- */ + +footer { + margin-top: 84px; + border-top: 1px solid var(--rule); + padding: 32px 0 44px; +} +.foot-in { + display: flex; + flex-wrap: wrap; + gap: 22px; + align-items: flex-start; + justify-content: space-between; +} +.foot-in .muted { font-size: 14px; max-width: 42ch } +.foot-links { display: flex; flex-wrap: wrap; gap: 20px } +.foot-links a { color: var(--muted); font-size: 14px } +.foot-links a:hover { color: var(--ink) } diff --git a/tools/docgen/assets/docs.css b/tools/docgen/assets/docs.css new file mode 100644 index 00000000..d8368ed0 --- /dev/null +++ b/tools/docgen/assets/docs.css @@ -0,0 +1,249 @@ +/* docs.css — every reference page: a symbol page, a namespace overview, the + * color palette, and the API index. Loaded after base.css, which owns the + * tokens. (This replaces the old item.css/api.css pair; api.css was never + * referenced by the generator at all.) + * + * A reference page is a document, so it is set as one: a measured column, a + * serif heading, a signature that reads as code, and rules instead of cards. */ + +main { padding: 34px 0 0 } + +/* ---------- breadcrumbs ---------- */ + +.crumbs { + font-size: 13px; + color: var(--muted); + display: flex; + flex-wrap: wrap; + gap: 7px; + margin-bottom: 20px; +} +.crumbs a { color: var(--muted) } +.crumbs a:hover { color: var(--ink) } +.crumbs .here { color: var(--ink) } + +/* ---------- symbol head ---------- */ + +.item { max-width: 78ch } +.item-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap } +.item-head h1 { + font-size: clamp(27px, 4vw, 36px); + letter-spacing: -0.02em; + word-break: break-word; +} + +.kind-badge { + font-family: var(--mono); + font-size: 11px; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--muted); + border: 1px solid var(--rule-2); + border-radius: 3px; + padding: 2px 7px; + white-space: nowrap; +} +/* the accent is the only colour that distinguishes a kind, and only for the + two that are structural rather than callable */ +.kind-namespace, .kind-keyword { color: var(--accent); border-color: var(--accent) } + +.sig { + display: block; + margin: 18px 0 0; + padding: 11px 14px; + font-size: 14px; + background: var(--code-bg); + border: 1px solid var(--code-rule); + border-left: 2px solid var(--rule-2); + border-radius: var(--radius); + color: var(--ink); + overflow-x: auto; + white-space: pre; +} + +.desc { margin-top: 20px } +.desc p { margin: 0 0 14px } +.desc p:last-child { margin-bottom: 0 } + +.ns-blurb { margin: 20px 0 0; color: var(--ink-2) } + +/* ---------- sections within a page ---------- */ + +.params h2, .examples h2, .related h2, .swatch-group h3 { + font-family: var(--sans); + font-size: 12px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--muted); + margin: 34px 0 12px; + padding-bottom: 7px; + border-bottom: 1px solid var(--rule); +} + +.param { + display: grid; + gap: 3px 14px; + padding: 9px 0; + border-bottom: 1px solid var(--rule); +} +@media (min-width: 620px) { + .param { grid-template-columns: minmax(90px, 22%) 1fr; align-items: baseline } +} +.param:last-child { border-bottom: none } +.pname { color: var(--c-arg); font-size: 13.5px } +.pdesc { color: var(--ink-2); font-size: 15px } + +.examples pre { margin-top: 0 } +.examples pre + pre { margin-top: 12px } + +.rel-row { display: flex; flex-wrap: wrap; gap: 8px } +.rel { + font-family: var(--mono); + font-size: 13px; + color: var(--ink-2); + border: 1px solid var(--rule-2); + border-radius: var(--radius); + padding: 4px 10px; +} +.rel:hover { color: var(--accent); border-color: var(--accent); text-decoration: none } + +.back { + display: inline-block; + margin: 40px 0 0; + font-size: 14px; + color: var(--muted); +} +.back:hover { color: var(--ink) } + +/* ---------- namespace overview ---------- */ + +.ns-methods { margin-top: 26px; border-top: 1px solid var(--rule) } +.ns-method { + display: grid; + gap: 2px; + padding: 12px 2px; + border-bottom: 1px solid var(--rule); + color: inherit; +} +@media (min-width: 720px) { + .ns-method { grid-template-columns: minmax(0, 46%) 1fr; gap: 18px; align-items: baseline } +} +.ns-method:hover { background: var(--paper-2); text-decoration: none } +.nm-sig { font-size: 13.5px; color: var(--c-fn) } +.nm-tip { color: var(--muted); font-size: 14.5px } + +/* ---------- color palette ---------- */ + +.swatch-row { + display: grid; + border-top: 1px solid var(--rule); + border-left: 1px solid var(--rule); + border-radius: var(--radius); + overflow: hidden; + grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); +} +.swatch { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 11px; + font-size: 13px; + border-right: 1px solid var(--rule); + border-bottom: 1px solid var(--rule); +} +.swatch.flash { background: var(--accent-bg) } +.chip { + width: 17px; height: 17px; + flex: none; + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, .18); +} +.cname { font-family: var(--mono); color: var(--ink); overflow: hidden; text-overflow: ellipsis } +.chex { font-family: var(--mono); color: var(--muted); margin-left: auto; font-size: 12px } + +/* ---------- API index ---------- */ + +.ref { max-width: var(--max) } +.ref-intro { max-width: 68ch; margin-bottom: 40px } +.ref-intro h1 { font-size: clamp(28px, 4vw, 38px); letter-spacing: -0.02em; margin: 0 0 12px } +.ref-intro p { color: var(--muted); margin: 0 0 22px } + +.search { + width: 100%; + font-family: var(--sans); + font-size: 15px; + color: var(--ink); + background: var(--panel); + border: 1px solid var(--rule-2); + border-radius: var(--radius); + padding: 11px 14px; +} +.search::placeholder { color: var(--muted) } +.search:focus { outline: none; border-color: var(--accent) } + +.noresults { margin-top: 14px; color: var(--muted); font-size: 14.5px } + +.idx-sec { margin-bottom: 44px } +.idx-sec h2 { + font-size: 20px; + margin: 0 0 6px; + padding-bottom: 9px; + border-bottom: 1px solid var(--rule); +} +.sec-blurb { color: var(--muted); font-size: 14.5px; margin: 0 0 16px; max-width: 74ch } + +.idx-grid { + display: grid; + border-top: 1px solid var(--rule); + border-left: 1px solid var(--rule); + border-radius: var(--radius); + overflow: hidden; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); +} +.idx-item { + display: grid; + gap: 1px; + padding: 11px 14px; + color: inherit; + border-right: 1px solid var(--rule); + border-bottom: 1px solid var(--rule); +} +.idx-item:hover { background: var(--paper-2); text-decoration: none } +.idx-item code { font-size: 13.5px; color: var(--c-fn) } +.idx-item span { color: var(--muted); font-size: 13.5px } + +/* ---------- hover cards + deep-link flash (from ludic-highlight.js) ------- */ + +.tok { cursor: help; border-bottom: 1px dotted var(--rule-2) } +a.tok { cursor: pointer } + +.hovercard { + position: absolute; + z-index: 90; + max-width: 360px; + background: var(--panel); + border: 1px solid var(--rule-2); + border-radius: var(--radius); + box-shadow: 0 8px 24px -12px rgba(0, 0, 0, .35); + padding: 11px 13px; + font-size: 13.5px; +} +.hc-top { display: flex; align-items: center; gap: 9px; margin-bottom: 6px } +.hc-name { font-family: var(--mono); font-size: 13.5px; color: var(--ink) } +.hc-sig { font-family: var(--mono); font-size: 12.5px; color: var(--muted); display: block; margin-bottom: 6px } +.hc-tip { color: var(--ink-2) } +.hc-foot { display: block; margin-top: 8px; font-size: 12px; color: var(--muted) } + +:target { scroll-margin-top: calc(var(--hdr) + 16px) } +.flash { animation: flash 1.1s ease-out } +@keyframes flash { + from { background: var(--accent-bg) } + to { background: transparent } +} +/* No standing tint here: `animation: none` plus a background would leave the + highlight on the element forever, which is exactly what a reader who asked + for less motion did not ask for. */ +@media (prefers-reduced-motion: reduce) { + .flash { animation: none } +} diff --git a/tools/docgen/assets/index.tmpl.html b/tools/docgen/assets/index.tmpl.html index c28f7a60..acb8c5fa 100644 --- a/tools/docgen/assets/index.tmpl.html +++ b/tools/docgen/assets/index.tmpl.html @@ -8,12 +8,8 @@ - - - - + +