diff --git a/site/astro.config.mjs b/site/astro.config.mjs
index 336d7934..be12b238 100644
--- a/site/astro.config.mjs
+++ b/site/astro.config.mjs
@@ -82,6 +82,7 @@ export default defineConfig({
// scripts/css-regions.mjs and css-shadowing.mjs hold this shape.
customCss: [
'./src/styles/tokens.css',
+ './src/styles/nav-spacing.css',
'./src/styles/docs/theme.css',
'./src/styles/docs/base.css',
'./src/styles/docs/shell.css',
diff --git a/site/src/components/Compare.astro b/site/src/components/Compare.astro
new file mode 100644
index 00000000..d20b30b9
--- /dev/null
+++ b/site/src/components/Compare.astro
@@ -0,0 +1,155 @@
+---
+import {
+ compareColumns,
+ compareCopy,
+ compareNoteHtml,
+ compareRows,
+} from '../data/site-content/compare';
+
+const highlightIndex = compareColumns.findIndex((c) => c.highlight);
+---
+
+{compareCopy.heading}
+ {/* Optional: netscli's table stands on its heading, and a lead that only
+ listed what was compared and which versions was filler. */}
+ {compareCopy.leadHtml && }
+
+
+
+
+
+
+
+
+
+ {compareRows.map((row) => (
+ Feature
+ {compareColumns.map((c, i) => (
+ {c.name}
+ ))}
+
+
+ ))}
+
+ {row.feature}
+ {row.cells.map((cell, i) => (
+
+ {cell === '—' ? (
+ <>
+ —
+ Not offered
+ >
+ ) : (
+ cell
+ )}
+
+ ))}
+
{recommended.hint}
} @@ -217,6 +225,16 @@ html[data-theme='light'] #install .reco{ box-shadow:var(--ui-lift-sm); } .reco-meta{font-size:.85rem;font-weight:500;color:var(--ui-text);margin-bottom:8px} +/* A recommended download (Linux's .deb) lays out like the download rows + under it: label left, button on the right edge. Stacked, the button sat + under the label on the left while the AppImage button below it sat on the + right, so the two buttons in one group pointed in different directions. + 16px sides match `.alt`, so labels and buttons line up down the group. */ +.reco-download{ + display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between; + gap:8px 16px;padding-inline:16px; +} +.reco-download .reco-meta{margin-bottom:0} .cmd.big{font-size:1.05rem;padding-right:74px} /* The container is the divider: the rows sit on it with a 1px gap, so its background shows through as the hairline between them. Chip tokens, so the @@ -267,8 +285,8 @@ html[data-theme='light'] #install .reco{ * * This was accent text on an accent tint inside an accent border -- the * loudest thing in the section, on the row that is the *least* recommended - * route: a direct download is unsigned, unverified and unmanaged, while the - * package-manager rows above it check a hash. The green was pointing at the + * route: a direct download is unmanaged and nothing checks it on the way + * in, while the package-manager rows above it check a hash. The green was pointing at the * wrong option. It reads as one route among several now, and keeps the * accent for hover and focus so it is still obviously a control. */ a.install-download{ diff --git a/site/src/components/Nav.astro b/site/src/components/Nav.astro index 3d5ec590..b071876d 100644 --- a/site/src/components/Nav.astro +++ b/site/src/components/Nav.astro @@ -206,7 +206,7 @@ html[data-theme='light'] nav[data-site-nav]::after{ border-color:rgba(var(--ui-accent-rgb),.35); outline:none; } -.nlinks{display:flex;gap:20px;font-size:.8125rem;flex-wrap:wrap;justify-content:flex-end} +.nlinks{display:flex;gap:var(--ui-nav-link-gap);font-size:var(--ui-nav-font-size);flex-wrap:wrap;justify-content:flex-end} .nlinks a{ color:var(--ui-text-muted);text-decoration:none; display:inline-flex;align-items:center; @@ -235,12 +235,12 @@ html[data-theme='light'] nav[data-site-nav]::after{ starlight/Header.astro, where the docs page shows it. */ .nlinks a.nav-group-start{ position:relative; - margin-inline-start:32px; + margin-inline-start:var(--ui-nav-group-gap); } .nlinks a.nav-group-start::before{ content:""; position:absolute; - inset-inline-start:-16px; + inset-inline-start:calc((var(--ui-nav-group-gap) + var(--ui-nav-link-gap) + 1px) / -2); top:50%; width:1px; height:18px; diff --git a/site/src/components/starlight/CopyPage.astro b/site/src/components/starlight/CopyPage.astro new file mode 100644 index 00000000..b8e3854b --- /dev/null +++ b/site/src/components/starlight/CopyPage.astro @@ -0,0 +1,140 @@ +--- +/** + * The "Copy page" button: copies this page's markdown source to the + * clipboard. Rendered twice on a docs page -- above the content on wide + * screens (MarkdownContent.astro), and at the end of the collapsed "On this + * page" bar on narrow ones (PageSidebar.astro), where it stays in reach as the + * page scrolls. CSS shows one or the other; the script wires every copy. + */ +const { entry } = Astro.locals.starlightRoute; +// 'docs/cli' -> 'cli'. The section index arrives as the bare id 'docs', +// which the strip does not match -- see the same mapping in +// src/pages/docs/[...slug].md.ts, which serves these files. +const slug = entry.id === 'docs' ? 'index' : entry.id.replace(/^docs\//, ''); +const markdownHref = `/docs/${slug}.md`; +--- + + + + + + diff --git a/site/src/components/starlight/Footer.astro b/site/src/components/starlight/Footer.astro index 98b2a4dd..662a8350 100644 --- a/site/src/components/starlight/Footer.astro +++ b/site/src/components/starlight/Footer.astro @@ -4,7 +4,7 @@ import LastUpdated from 'virtual:starlight/components/LastUpdated'; import Pagination from 'virtual:starlight/components/Pagination'; import { site } from '../../data/site'; -const { meta, hero, builtWith } = site; +const { meta } = site; const year = new Date().getFullYear(); --- @@ -19,35 +19,19 @@ const year = new Date().getFullYear(); */}Install the netscli package for the CLI, terminal UI, and MCP server:
${INSTALL_SH_COMMAND}cargo install netscliInstall the Windows desktop app separately:
-Install the desktop app separately:
+winget install netscli-guibrew install --cask fstubner/tap/netscli-guiPrebuilt binaries and desktop installers for Windows, Linux, and macOS are attached to GitHub releases when available for that platform.
+Linux has a .deb and an AppImage. Every download is on the GitHub releases page.
netscli serve starts a Model Context Protocol (MCP) server over stdio. It exposes structured local-network tools for host discovery, port scanning, ping, DNS, ARP, host inspection, network sweep, interface listing, and mDNS discovery. Packet-capture builds can also expose capture tools. Packet capture uses a job-style flow for long-running work: start the capture, poll status, then fetch the result.',
+ 'Yes. Running netscli serve starts a Model Context Protocol (MCP) server over stdio. It exposes structured local-network tools for host discovery, port scanning, ping, DNS, ARP, host inspection, network sweep, interface listing, and mDNS discovery. Packet-capture builds can also expose capture tools. Packet capture uses a job-style flow for long-running work. Start the capture, poll status, then fetch the result.',
},
{
group: 'Install and updates',
q: 'Does NetsCLI work on Windows, macOS, and Linux?',
- a: 'Yes. Release artifacts target Windows, macOS, and Linux. CLI/TUI binaries are published for common x86_64 and aarch64 targets, including macOS Intel and Apple Silicon. The desktop app is published as Windows, macOS, and Linux installers where the release workflow supports that platform.',
+ a: 'Yes. The CLI and terminal UI are published for Windows (x86_64), macOS (Intel and Apple Silicon) and Linux (x86_64 and ARM64). The desktop app is published for Windows and Linux (x86_64) and macOS (Intel and Apple Silicon).',
aHtml:
- 'Yes. Release artifacts target Windows, macOS, and Linux. CLI/TUI binaries are published for common x86_64 and aarch64 targets, including macOS Intel and Apple Silicon. The desktop app is published as Windows, macOS, and Linux installers where the release workflow supports that platform.',
+ 'Yes. The CLI and terminal UI are published for Windows (x86_64), macOS (Intel and Apple Silicon) and Linux (x86_64 and ARM64). The desktop app is published for Windows and Linux (x86_64) and macOS (Intel and Apple Silicon).',
},
{
group: 'What it is',
@@ -68,9 +69,9 @@ export const faq: FaqItem[] = [
{
group: 'Limits and dependencies',
q: 'Does NetsCLI require libpcap or other system dependencies?',
- a: 'Not for the normal scan, discovery, DNS, ARP, ping, trace, or interface workflows. Packet capture is the exception: it requires libpcap on Linux/macOS or Npcap on Windows at runtime, and only works in builds compiled with packet-capture support.',
+ a: 'Not for the normal scan, discovery, DNS, ARP, ping, trace, or interface workflows. Packet capture is the exception. It requires libpcap on Linux/macOS or Npcap on Windows at runtime, and only works in the separate packet-capture downloads.',
aHtml:
- 'Not for the normal scan, discovery, DNS, ARP, ping, trace, or interface workflows. Packet capture is the exception: it requires libpcap on Linux/macOS or Npcap on Windows at runtime, and only works in builds compiled with packet-capture support.',
+ 'Not for the normal scan, discovery, DNS, ARP, ping, trace, or interface workflows. Packet capture is the exception. It requires libpcap on Linux/macOS or Npcap on Windows at runtime, and only works in the separate packet-capture downloads.',
},
{
group: 'Network workflows',
@@ -87,21 +88,21 @@ export const faq: FaqItem[] = [
// folders and remote shutdown, and a comparison that omitted the thing a
// reader can see on that page in ten seconds would discredit the rest of
// the answer.
- a: 'NetsCLI overlaps with those tools for common LAN discovery tasks: finding live hosts, scanning TCP ports, resolving hostnames, and showing MAC vendors from the local ARP cache. It is not a drop-in clone of either. Advanced IP Scanner is Windows-only and closed-source freeware. NetsCLI runs on Windows, macOS, and Linux, is MIT-licensed, and offers the same scans from a desktop app, a terminal UI, a CLI, and an MCP server, with JSON and YAML output. Angry IP Scanner is also open source and cross-platform, and ships its own command-line interface. What NetsCLI does not do is remote administration: Advanced IP Scanner can open shared folders, control machines over RDP or Radmin, and switch them off remotely, and NetsCLI has no equivalent.',
+ a: 'NetsCLI overlaps with those tools for common LAN discovery tasks, such as finding live hosts, scanning TCP ports, resolving hostnames, and showing MAC vendors from the local ARP cache. It is not a drop-in clone of either. Advanced IP Scanner is Windows-only and closed-source freeware. NetsCLI runs on Windows, macOS, and Linux, is MIT-licensed, and offers the same scans from a desktop app, a terminal UI, a CLI, and an MCP server, with JSON, YAML, CSV and Markdown output. Angry IP Scanner is also open source and cross-platform, and ships its own command-line interface. NetsCLI does not do remote administration. Advanced IP Scanner can open shared folders, control machines over RDP or Radmin, and switch them off remotely, and NetsCLI has no equivalent.',
aHtml:
- 'NetsCLI overlaps with those tools for common LAN discovery tasks: finding live hosts, scanning TCP ports, resolving hostnames, and showing MAC vendors from the local ARP cache. It is not a drop-in clone of either. Advanced IP Scanner is Windows-only and closed-source freeware. NetsCLI runs on Windows, macOS, and Linux, is MIT-licensed, and offers the same scans from a desktop app, terminal UI, CLI, and MCP server, with --json and --yaml output. Angry IP Scanner is also open source and cross-platform, and ships its own command-line interface. What NetsCLI does not do is remote administration: Advanced IP Scanner can open shared folders, control machines over RDP or Radmin, and switch them off remotely, and NetsCLI has no equivalent.',
+ 'NetsCLI overlaps with those tools for common LAN discovery tasks, such as finding live hosts, scanning TCP ports, resolving hostnames, and showing MAC vendors from the local ARP cache. It is not a drop-in clone of either. Advanced IP Scanner is Windows-only and closed-source freeware. NetsCLI runs on Windows, macOS, and Linux, is MIT-licensed, and offers the same scans from a desktop app, terminal UI, CLI, and MCP server, with --json, --yaml, --csv and --md output. Angry IP Scanner is also open source and cross-platform, and ships its own command-line interface. NetsCLI does not do remote administration. Advanced IP Scanner can open shared folders, control machines over RDP or Radmin, and switch them off remotely, and NetsCLI has no equivalent.',
},
{
group: 'Network workflows',
q: 'How do I find devices on my home network with NetsCLI?',
- a: 'Run `netscli discover` from a machine on the network, or pass a subnet explicitly with `netscli discover Run discovery from any machine on the network:
netscli discovernetscli discover <subnet>NetsCLI probes the range, then adds reverse DNS, ARP cache data, MAC addresses, and OUI vendor names when the operating system has that information available.
+NetsCLI probes the range, then adds reverse DNS, ARP cache data, MAC addresses, and the device maker when the operating system has that information available.
`, }, { @@ -111,8 +112,8 @@ export const faq: FaqItem[] = [ aHtml: `Yes. NetsCLI is MIT-licensed and free for personal, open-source, and commercial use.
winget install netscliwinget install netscli-guiwinget install netscliwinget install netscli-guiscoop bucket add fstubner https://github.com/fstubner/scoop-bucket && scoop install netsclibrew tap fstubner/tap && brew install netscli${INSTALL_SH_COMMAND}netscli with no arguments to get an interactive, keyboard-driven scanner in the terminal, which nmap itself does not provide. For advanced service detection, NSE scripts, and OS fingerprinting, nmap remains the better tool.',
+ 'NetsCLI covers the simpler cases nmap is often reached for, such as host discovery, TCP and UDP port scans, DNS lookups, and ARP-table inspection on a local network, with direct subcommands and structured output. It also ships a terminal UI. Run netscli with no arguments to get an interactive, keyboard-driven scanner in the terminal, which nmap itself does not provide. For advanced service detection, NSE scripts, and OS fingerprinting, nmap remains the better tool.',
},
{
group: 'Network workflows',
q: 'Is there a netscan command for Linux or Windows?',
- a: 'There is no standard netscan command on Linux, macOS, or Windows — it is not part of any of those systems. People searching for one usually want a command-line network scanner, which is what NetsCLI is: netscli discover lists live hosts on your subnet, netscli scan checks TCP ports on a host, and netscli sweep does both across a range. Several unrelated third-party tools also use the name NetScan, so check which one you mean before installing.',
+ a: 'There is no standard netscan command on Linux, macOS, or Windows. People searching for one usually want a command-line network scanner, which is what NetsCLI is. netscli discover lists live hosts on your subnet, netscli scan checks TCP or UDP ports on a host, and netscli sweep does both across a range. Several unrelated third-party tools also use the name NetScan, so check which one you mean before installing.',
aHtml:
- 'There is no standard netscan command on Linux, macOS, or Windows — it is not part of any of those systems. People searching for one usually want a command-line network scanner, which is what NetsCLI is:
netscli discovernetscli scan router.local -p 22,80,443netscli sweepSeveral unrelated third-party tools also use the name NetScan, so check which one you mean before installing.
', + 'There is no standard netscan command on Linux, macOS, or Windows. People searching for one usually want a command-line network scanner, which is what NetsCLI is:
netscli discovernetscli scan router.local -p 22,80,443netscli sweepSeveral unrelated third-party tools also use the name NetScan, so check which one you mean before installing.
', }, { group: 'Network workflows', q: 'What is the difference between scan, inspect, discover, and sweep?', - a: 'Use scan when you already know a host and want TCP port status. Use inspect when you want a host profile that combines reachability, reverse DNS, and optional port checks. Use discover to find reachable devices on a subnet. Use sweep when you want discovery plus open-port checks across the discovered hosts.', + a: 'Use scan when you already know a host and want TCP or UDP port status. Use inspect when you want a host profile with reachability, reverse DNS, MAC address and maker, an OS hint, and optional port checks. Use discover to find reachable devices on a subnet. Use sweep when you want discovery plus open-port checks across the discovered hosts.', aHtml: - 'Usescan when you already know a host and want TCP port status. Use inspect when you want a host profile that combines reachability, reverse DNS, and optional port checks. Use discover to find reachable devices on a subnet. Use sweep when you want discovery plus open-port checks across the discovered hosts.',
+ 'Use scan when you already know a host and want TCP or UDP port status. Use inspect when you want a host profile with reachability, reverse DNS, MAC address and maker, an OS hint, and optional port checks. Use discover to find reachable devices on a subnet. Use sweep when you want discovery plus open-port checks across the discovered hosts.',
},
{
group: 'Limits and dependencies',
diff --git a/site/src/data/site-content/hero.ts b/site/src/data/site-content/hero.ts
index a08ad0e0..6af8cadf 100644
--- a/site/src/data/site-content/hero.ts
+++ b/site/src/data/site-content/hero.ts
@@ -52,7 +52,7 @@ export const hero: Hero = {
// it. "All on one Rust core" carries the same point as "each interface
// calls the same Rust core, so results stay consistent" -- the reader who
// needs the longer version is already reading /docs/.
- 'Discover LAN devices, scan TCP ports, query DNS, and inspect hosts from the desktop app, terminal UI, CLI, or MCP server — all on one Rust core.',
+ 'Discover LAN devices, scan TCP and UDP ports, query DNS, and inspect hosts from the desktop app, terminal UI, CLI, or MCP server, all on one Rust core.',
// `winget install netscli` leads, and the shell script follows.
//
// os-tabs.ts already swapped these per-OS at runtime -- Windows visitors
@@ -70,7 +70,7 @@ export const hero: Hero = {
heroImage: '/assets/tui-discover.png',
heroImageWebp: '/assets/tui-discover.webp',
heroImageAlt:
- 'netscli terminal UI running /discover with sanitized lab hostnames, vendors, and response times',
+ 'netscli terminal UI running /discover with demo hostnames, vendors, and response times',
heroImageWidth: 1640,
heroImageHeight: 930,
sourceUrl: 'https://github.com/fstubner/netscli',
diff --git a/site/src/data/site-content/install.ts b/site/src/data/site-content/install.ts
index ef68108f..3a7b1bb7 100644
--- a/site/src/data/site-content/install.ts
+++ b/site/src/data/site-content/install.ts
@@ -4,33 +4,27 @@ import { INSTALL_PS1_COMMAND, INSTALL_SH_COMMAND } from './install-urls';
export const installCopy: SectionCopy = {
heading: 'Get started',
leadHtml:
- 'Install the desktop app or the command line — both drive the same Rust core. Full install guide →',
+ 'Install the desktop app or the command line. Both drive the same Rust core. Full install guide →',
};
const RELEASE_DOWNLOAD = 'https://github.com/fstubner/netscli/releases/latest/download';
-/** Shown on every direct .dmg / .msi row.
+/** Shown on the direct .dmg rows and on the Homebrew cask.
*
- * The desktop installers are not code-signed yet (tracked separately —
- * notarization needs a paid Apple Developer cert, Authenticode needs a
- * Windows cert). Warning up front is better than someone hitting a
- * Gatekeeper or SmartScreen dialog with no context and assuming the
- * download is malware. */
-const MACOS_UNSIGNED_HINT = 'Unsigned — right-click → Open on first launch';
-/* Describes the row it is attached to, which is the direct .msi download.
- * It used to end "winget verifies the hash" -- true of winget, and winget is
- * not what this row does. Someone clicking Download gets the installer
- * straight from GitHub Releases with nothing checking it, and was being told
- * otherwise at the moment they did it. The checksums are real and published;
- * this now points at them.
+ * The cask too: Homebrew quarantines what a cask downloads, and 5.0
+ * deprecated `--no-quarantine`, the flag that skipped it. A cask install
+ * meets the same Gatekeeper block as a browser download.
*
- * Stated as the exception rather than labelling its opposite. Every package
- * manager row used to carry a "Hash-verified" tag so this row would read as
- * different -- four copies of it in one Windows panel, saying the unremarkable
- * thing four times to make the remarkable thing stand out once. The
- * verification is the norm; not having it is the news, so only the news is
- * written down. */
-const WINDOWS_UNSIGNED_HINT = 'Unsigned — SmartScreen may warn. Checksums are published.';
+ * The macOS app is not notarized (that needs a paid Apple Developer
+ * account). Warning up front is better than someone hitting a Gatekeeper
+ * dialog with no context and assuming the download is malware.
+ *
+ * It said "right-click → Open on first launch" until 2026-09. macOS 15
+ * Sequoia removed that override: Control-click → Open now shows the same
+ * refusal with no Open button. Apple's support page for opening an app from
+ * an unknown developer gives only the System Settings route, which is what
+ * this names now. */
+const MACOS_UNSIGNED_HINT = 'Unsigned. Open it once, then System Settings → Privacy & Security → Open Anyway';
export const installByPlatform: Recordcargo install netscli. Every asset is checksummed and signed with Sigstore cosign — see how to verify a download, plus standalone binaries and packet-capture builds.';
+ 'Rust users can cargo install netscli. Every binary and installer is checksummed and signed with Sigstore cosign. The install guide shows how to verify a download and lists the standalone binaries and packet-capture builds.';
// Two things /llms.txt says that no page does: a build-from-source route,
// listed after the per-platform quickstart, and any caveat a reader acting
@@ -188,6 +182,6 @@ export const installFromSource = 'cargo install netscli';
export const installNotes = [
'Packet capture is a compile-time feature. No published desktop installer',
- 'includes it; capture-enabled CLI assets are published separately and also',
+ 'includes it. Capture-enabled CLI assets are published separately and also',
'need a system capture library (libpcap or Npcap).',
];
diff --git a/site/src/data/site-content/meta.ts b/site/src/data/site-content/meta.ts
index 9748d6e2..966eb4e9 100644
--- a/site/src/data/site-content/meta.ts
+++ b/site/src/data/site-content/meta.ts
@@ -6,7 +6,7 @@ export const meta: Meta = {
// a search result shows on words nobody searches for. The interfaces are the
// differentiator, and MCP is the one term here with real intent behind it
// and almost no competition.
- title: 'NetsCLI — Network Scanner for Desktop, CLI, and MCP',
+ title: 'NetsCLI | Network Scanner for Desktop, CLI, and MCP',
// 153 characters. The previous one ran to 172 and was cut around 158, which
// truncated the MCP mention off the end -- the most valuable word in it. It
// also ran operations and interfaces through a single "for" series
@@ -15,22 +15,22 @@ export const meta: Meta = {
// "inspect hosts", not "trace routes": the sentence attributes what it
// lists to all four interfaces, and the MCP server has no traceroute tool.
// `inspect_host` is a real MCP tool, so the swap keeps the claim true.
- // Measured at 154 characters in the built page, one more than the 153 this
- // replaced -- still inside the ~160 a search result shows, and the MCP
- // mention this description was rewritten to protect is nowhere near the cut.
+ // "TCP and UDP" took it to 162, past the ~160 a search result shows, so
+ // the closer became the hero's "All on one Rust core." Check the length in
+ // the built page after any edit; the MCP mention must stay clear of the cut.
description:
- 'Discover LAN devices, scan TCP ports, query DNS, and inspect hosts from a desktop app, terminal UI, CLI, or MCP server. One Rust core, consistent results.',
+ 'Discover LAN devices, scan TCP and UDP ports, query DNS, and inspect hosts from a desktop app, terminal UI, CLI, or MCP server. All on one Rust core.',
// Same scoping as the hero subhead: capture is not in any published desktop
// installer, so this must not imply it ships with the app. It is what search
// results and link previews show, which is where an inaccurate claim travels
// furthest.
ogDescription:
- 'Discover LAN devices, scan TCP ports, query DNS, and inspect hosts from the desktop app, terminal UI, CLI, or MCP server backed by one Rust core. Packet capture in capture-enabled CLI builds.',
+ 'Discover LAN devices, scan TCP and UDP ports, query DNS, and inspect hosts from the desktop app, terminal UI, CLI, or MCP server backed by one Rust core. Packet capture in capture-enabled CLI builds.',
siteName: 'NetsCLI',
author: { name: 'Felix Stubner', url: 'https://github.com/fstubner' },
ogImage: 'https://netscli.com/assets/tui-discover.png',
ogImageAlt:
- 'netscli terminal UI running /discover with sanitized lab hostnames, vendors, and response times',
+ 'netscli terminal UI running /discover with demo hostnames, vendors, and response times',
faviconPath: '/favicon.svg',
themeColor: '#111',
};
diff --git a/site/src/data/site-content/nav.ts b/site/src/data/site-content/nav.ts
index f6ce4aef..fe2615ab 100644
--- a/site/src/data/site-content/nav.ts
+++ b/site/src/data/site-content/nav.ts
@@ -30,6 +30,7 @@ export interface NavLink {
const allLinks: NavLink[] = [
{ label: 'Features', section: 'surfaces', mobileGroup: 'site' },
+ { label: 'Compare', section: 'compare', mobileGroup: 'site' },
{ label: 'Install', section: 'install', mobileGroup: 'site' },
{ label: 'FAQ', section: 'faq', mobileGroup: 'site' },
{ label: 'Docs', href: '/docs/', mobileGroup: 'site' },
diff --git a/site/src/data/site-content/sections.ts b/site/src/data/site-content/sections.ts
index 9af98ba5..288bbf6f 100644
--- a/site/src/data/site-content/sections.ts
+++ b/site/src/data/site-content/sections.ts
@@ -2,7 +2,7 @@ import type { LandingLayout, LandingSection } from './types';
// Which sections the landing page renders, in order.
//
-// The four are independent: each reads its own content file and none assumes
+// The sections are independent: each reads its own content file and none assumes
// what sits above it. So a product with no install story drops 'install'
// rather than filling it with something weak, and one whose visitors ask
// questions before they read features can put 'faq' second.
@@ -10,7 +10,7 @@ import type { LandingLayout, LandingSection } from './types';
// A name that appears twice renders twice; a name whose content is empty
// renders nothing. Dropping the last section is fine -- the footer is not in
// this list, and neither is the nav.
-export const sections: LandingSection[] = ['hero', 'surfaces', 'install', 'faq'];
+export const sections: LandingSection[] = ['hero', 'surfaces', 'compare', 'install', 'faq'];
// How the landing page is laid out above the fold, and the rhythm that
// follows from it.
diff --git a/site/src/data/site-content/surfaces.ts b/site/src/data/site-content/surfaces.ts
index 1dfdeeb7..92594e32 100644
--- a/site/src/data/site-content/surfaces.ts
+++ b/site/src/data/site-content/surfaces.ts
@@ -3,7 +3,7 @@ import type { SectionCopy, SurfaceCard } from './types';
export const surfacesCopy: SectionCopy = {
heading: 'Choose how to work with your network',
leadHtml:
- 'Four ways in, one engine behind them. Whichever you pick — desktop app, terminal UI, CLI or MCP — a port scan means the same thing and returns the same answer. Full docs →',
+ 'Four ways in, one engine behind them. Whichever you pick (desktop app, terminal UI, CLI or MCP), a port scan means the same thing and returns the same answer. Full docs →',
};
export const surfaces: SurfaceCard[] = [
@@ -15,7 +15,7 @@ export const surfaces: SurfaceCard[] = [
src: '/gui-scan.png',
webp: '/gui-scan.webp',
alt:
- 'NetsCLI Desktop dark theme scan view showing sanitized demo port results, row details, command preview, and status bar',
+ 'NetsCLI Desktop dark theme scan view showing demo port results, row details, command preview, and status bar',
width: 1377,
height: 740,
},
@@ -23,12 +23,12 @@ export const surfaces: SurfaceCard[] = [
{
title: 'Terminal UI',
body:
- 'Run netscli with no subcommand to start the terminal UI. It is the one to reach for when you are already in a shell and want to stay there — history and autocomplete included, with local interface activity beside the results.',
+ 'Run netscli with no subcommand to start the terminal UI. It is the one to reach for when you are already in a shell and want to stay there. History and autocomplete are included, with local interface activity beside the results.',
image: {
src: '/assets/tui-discover.png',
webp: '/assets/tui-discover.webp',
alt:
- 'netscli terminal UI running /discover with sanitized lab hostnames, vendors, and response times',
+ 'netscli terminal UI running /discover with demo hostnames, vendors, and response times',
width: 1640,
height: 930,
},
@@ -37,7 +37,7 @@ export const surfaces: SurfaceCard[] = [
{
title: 'Command line',
body:
- 'Use the CLI for repeatable diagnostics and automation. Network operations expose --json and --yaml output, so scripts and other tools can consume the same data the desktop app displays.',
+ 'Use the CLI for repeatable diagnostics and automation. Network operations take --json and --yaml, and lists also --csv and --md, so scripts and other tools can consume the same data the desktop app displays.',
// `--resolve` on the discover line is load-bearing, not decoration.
// `hostname` is only populated when the flag is passed (core's
// discover.rs guards the reverse-lookup pass on it), so without it this
diff --git a/site/src/data/site-content/types.ts b/site/src/data/site-content/types.ts
index c4069f7b..9c528da5 100644
--- a/site/src/data/site-content/types.ts
+++ b/site/src/data/site-content/types.ts
@@ -212,6 +212,18 @@ export interface Analytics {
cloudflareToken?: string;
}
+/** A comparison column. `highlight` marks this product's, which is tinted. */
+export interface ComparisonColumn {
+ name: string;
+ highlight?: boolean;
+}
+
+/** A capability row. `cells` is one per column, in order: '✓', '—', or a word. */
+export interface ComparisonRow {
+ feature: string;
+ cells: string[];
+}
+
export interface SectionCopy {
heading: string;
/** HTML allowed — typically short tagline with an anchor link. */
@@ -223,7 +235,7 @@ export type LandingLayout = 'centered' | 'split';
/** A section the landing page can render. Adding one here means adding a
* component for it in src/pages/index.astro's map. */
-export type LandingSection = 'hero' | 'surfaces' | 'install' | 'faq';
+export type LandingSection = 'hero' | 'surfaces' | 'compare' | 'install' | 'faq';
/** One group in the docs sidebar, as Starlight expects it. */
export interface DocsSection {
diff --git a/site/src/layouts/Page.astro b/site/src/layouts/Page.astro
index e7e52934..c5eede45 100644
--- a/site/src/layouts/Page.astro
+++ b/site/src/layouts/Page.astro
@@ -2,6 +2,7 @@
// Shared with the docs shell (astro.config.mjs customCss): the palette, the
// code surface and the theme control. Then the landing page's own tier.
import '../styles/tokens.css';
+import '../styles/nav-spacing.css';
import '../styles/code-surface.css';
import '../styles/landing/tokens.css';
import '../styles/landing/base.css';
diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro
index a491615e..9c74129f 100644
--- a/site/src/pages/index.astro
+++ b/site/src/pages/index.astro
@@ -3,6 +3,7 @@ import Page from '../layouts/Page.astro';
import Nav from '../components/Nav.astro';
import Hero from '../components/Hero.astro';
import Surfaces from '../components/Surfaces.astro';
+import Compare from '../components/Compare.astro';
import Install from '../components/Install.astro';
import Faq from '../components/Faq.astro';
import Footer from '../components/Footer.astro';
@@ -14,7 +15,7 @@ const githubRepo = site.social.repo;
// One component per section name. The list in site-content/sections.ts is
// the order; this is only the lookup, and a name with no entry here is a
// type error rather than a silently missing section.
-const COMPONENTS = { hero: Hero, surfaces: Surfaces, install: Install, faq: Faq };
+const COMPONENTS = { hero: Hero, surfaces: Surfaces, compare: Compare, install: Install, faq: Faq };
---