diff --git a/docs/specs/website-design-system.md b/docs/specs/website-design-system.md index 1cc4b4f..70e982a 100644 --- a/docs/specs/website-design-system.md +++ b/docs/specs/website-design-system.md @@ -57,7 +57,7 @@ Content spans at most 1200 px, with 32 px desktop gutters and 20 px mobile gutte ## [WEB-CONTENT] Minimal information architecture -`/` introduces the toolkit and features the example post. `/docs/` explains the architectural basics and links to `/docs/getting-started/`, `/docs/crates/` and `/docs/profiler/`. `/blog/` and the tag/category pages are generated by techdoc. `/blog/one-engine/` contains one short example post with the header image. The plugin also owns the feed, sitemap and structural assets; do not copy its templates into this project. +`/` introduces the toolkit, links every published crate and npm package to its registry, and features the example post. The header carries Docs, Journal and the GitHub repo; the homepage hero repeats the repo link as a button. The footer credits © Nimblesite, linked to nimblesite.co. `/docs/` explains the architectural basics and links to `/docs/getting-started/`, `/docs/crates/` and `/docs/profiler/`. `/blog/` and the tag/category pages are generated by techdoc. `/blog/one-engine/` contains one short example post with the header image. The plugin also owns the feed, sitemap and structural assets; do not copy its templates into this project. The canonical origin defaults to `http://localhost:8080` for this local sketch and is configured with `SITE_URL` before a production build. Do not invent a live domain or publish through a new workflow. diff --git a/website/.oxlintrc.json b/website/.oxlintrc.json index f71991a..8c54c4e 100644 --- a/website/.oxlintrc.json +++ b/website/.oxlintrc.json @@ -3,7 +3,7 @@ "overrides": [ { // Eleventy requires default exports for config and JavaScript data entrypoints. - "files": ["eleventy.config.js", "src/_data/site.js"], + "files": ["eleventy.config.js", "src/_data/site.js", "src/_data/navigation.js"], "rules": { "import/no-default-export": "off" } }, { diff --git a/website/package.json b/website/package.json index ef1785b..4a52a6e 100644 --- a/website/package.json +++ b/website/package.json @@ -7,7 +7,7 @@ "dev": "eleventy --serve", "build": "eleventy", "test": "python3 tests/prose_test.py", - "lint": "oxlint --deny-warnings eleventy.config.js src/_data/site.js" + "lint": "oxlint --deny-warnings eleventy.config.js src/_data/site.js src/_data/navigation.js" }, "devDependencies": { "@11ty/eleventy": "3.1.7", diff --git a/website/src/_data/navigation.js b/website/src/_data/navigation.js new file mode 100644 index 0000000..2be90c4 --- /dev/null +++ b/website/src/_data/navigation.js @@ -0,0 +1,17 @@ +// [WEB-CONTENT] Header and footer links. The repo is one click from every page. +const repo = "https://github.com/Nimblesite/lspkit"; + +const navigation = { + repo, + main: [ + { text: "Docs", url: "/docs/" }, + { text: "Journal", url: "/blog/" }, + { text: "GitHub ↗", url: repo, external: true }, + ], + footer: [{ title: "The foundation for any language's tooling.", items: [ + { text: "GitHub ↗", url: repo }, + { text: `© ${new Date().getFullYear()} Nimblesite ↗`, url: "https://www.nimblesite.co" }, + ] }], +}; + +export default navigation; diff --git a/website/src/_data/navigation.json b/website/src/_data/navigation.json deleted file mode 100644 index 8d572de..0000000 --- a/website/src/_data/navigation.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "main": [ - { "text": "Docs", "url": "/docs/" }, - { "text": "Journal", "url": "/blog/" } - ], - "footer": [{ "title": "The foundation for any language's tooling.", "items": [ - { "text": "GitHub ↗", "url": "https://github.com/Nimblesite/lspkit" } - ] }] -} diff --git a/website/src/_includes/icons/github.svg b/website/src/_includes/icons/github.svg new file mode 100644 index 0000000..cad4d61 --- /dev/null +++ b/website/src/_includes/icons/github.svg @@ -0,0 +1 @@ + diff --git a/website/src/assets/css/styles.css b/website/src/assets/css/styles.css index 128c1d3..b8ea5cd 100644 --- a/website/src/assets/css/styles.css +++ b/website/src/assets/css/styles.css @@ -53,7 +53,6 @@ h3 { font-size: 25px; line-height: 1.25; letter-spacing: -.025em; } .footer-section { display: flex; justify-content: space-between; gap: 24px; } .footer-section h3 { font-size: 16px; letter-spacing: -.02em; } .footer-section ul { display: flex; gap: 24px; font-size: 13px; } -.footer-bottom { margin-top: 24px; padding: 0; font: 12px var(--font-mono); color: var(--color-muted); text-align: left; } /* [WEB-DESIGN-LAYOUT] Homepage editorial grid. */ .home-shell { max-width: var(--max-width); margin: auto; padding: 0 32px; } .hero { display: grid; grid-template-columns: 1.8fr 1fr; align-items: end; gap: 64px; padding: 64px 0 48px; } @@ -71,6 +70,10 @@ h3 { font-size: 25px; line-height: 1.25; letter-spacing: -.025em; } .registry svg { width: 22px; height: 22px; } .registry a { display: inline-flex; min-height: 44px; align-items: center; gap: 6px; font: 14px var(--font-mono); text-decoration: none; } .registry a span { color: var(--color-primary); } +.hero-links { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 32px; } +.repo-link { display: inline-flex; align-items: center; gap: 10px; min-height: 44px; padding: 0 18px; background: var(--color-text); color: var(--color-bg); font-size: 14px; font-weight: bold; text-decoration: none; } +.repo-link svg { width: 18px; height: 18px; } +.footer-bottom { display: none; } .engineering-plate { border-top: 1px solid var(--color-border); } .engineering-plate img { width: 100%; height: auto; display: block; } .engineering-plate figcaption { display: flex; justify-content: space-between; gap: 20px; padding: 14px 0; border-bottom: 1px solid var(--color-border); font: 10px/1.5 var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--color-muted); } diff --git a/website/src/index.njk b/website/src/index.njk index bb0c9e7..6f1a245 100644 --- a/website/src/index.njk +++ b/website/src/index.njk @@ -11,7 +11,10 @@ title: lspkit — The foundation for any language's tooling.

Write your language analysis once, as one Rust trait. lspkit serves it to your VS Code extension over the Language Server Protocol and to Claude Code, Cursor and Codex as an MCP server. Find the hot path and the memory leak with a profiler built into VS Code.

- Get started +
{# [WEB-CONTENT] Every published crate and npm package, linked to its registry. #} diff --git a/website/tests/prose_test.py b/website/tests/prose_test.py index 524fc74..cf1e0d0 100644 --- a/website/tests/prose_test.py +++ b/website/tests/prose_test.py @@ -10,6 +10,7 @@ SITE = Path(__file__).resolve().parents[1] OUTPUT = SITE / "_site" DOCS = ("docs/getting-started", "docs/crates", "docs/profiler", "docs/examples") +REPO = "https://github.com/Nimblesite/lspkit" class Document(HTMLParser): @@ -110,11 +111,18 @@ def test_reading_pages_have_header_images_and_explicit_prose_classes(self): css = OUTPUT / "assets/css/styles.css" self.assertLessEqual(len(css.read_text().splitlines()), 2000) - def test_journal_has_illustrated_posts_and_only_docs_and_journal_navigation(self): + def test_journal_has_illustrated_posts_and_docs_journal_and_repo_navigation(self): for path in OUTPUT.rglob("*.html"): page = Document(path) - navigation = [a["href"] for a in page.attributes("a") if "nav-link" in a.get("class", "").split()] - self.assertEqual(navigation, ["/docs/", "/blog/"], path) + links = page.attributes("a") + navigation = [a["href"] for a in links if "nav-link" in a.get("class", "").split()] + self.assertEqual(navigation, ["/docs/", "/blog/", REPO], path) + self.assertIn("https://www.nimblesite.co", [a.get("href") for a in links], path) + home = [a.get("href") for a in Document(OUTPUT / "index.html").attributes("a")] + self.assertIn(REPO, home) + for package in ("crates.io/crates/lspkit", "crates.io/crates/lspkit-server", "crates.io/crates/lsp-profiler", + "crates.io/crates/lsp-profiler-dotnet", "npmjs.com/package/@nimblesite/lsp-profiler-ui"): + self.assertTrue(any(href and href.endswith(package) for href in home), package) journal = self.check_page(OUTPUT / "blog/index.html") self.assertTrue(journal.attributes("img"), "Journal needs a real header image") self.assertTrue(journal.attributes("h2"), "Post titles need a heading hierarchy")