feat(website): route the OS links to per-platform install steps

Clicking macOS, Linux or Windows on the home page landed on the file-path
cards, which say where config lives, not how to install. Step 1 now carries
one card per OS at those anchors: brew on macOS, cargo on Linux, cargo or the
prebuilt .exe on Windows. The path cards keep their own -paths anchors.

The hero logo drew into a fixed inline width, so the mobile height overrides
shrank the mark inside a box sized for the desktop one. An aspect-ratio lets
the width follow, and the mark is now larger and centred against the title.
This commit is contained in:
Michel de Graaf
2026-07-25 05:35:01 +02:00
parent 5090af97a2
commit 96e6bcdb8a
4 changed files with 112 additions and 35 deletions
+1 -1
View File
@@ -12,6 +12,6 @@ export const Logo = ({ size = 180, className }: LogoProps) => (
role="img"
aria-label="soulseek-rs"
className={cn('logo-mark shrink-0 bg-contain bg-no-repeat', className)}
style={{ height: size, width: size * ASPECT }}
style={{ height: size, aspectRatio: ASPECT }}
/>
)
+7 -2
View File
@@ -20,9 +20,10 @@ interface TerminalProps {
lines: readonly TermLine[]
copy?: string
className?: string
wrap?: boolean
}
export const Terminal = ({ label, lines, copy, className }: TerminalProps) => {
export const Terminal = ({ label, lines, copy, className, wrap }: TerminalProps) => {
const copyText =
copy ??
lines
@@ -47,7 +48,11 @@ export const Terminal = ({ label, lines, copy, className }: TerminalProps) => {
{lines.map((line, i) => (
<span
key={i}
className={cn('block whitespace-pre', LINE_TONE[line.t])}
className={cn(
'block',
wrap ? 'break-all whitespace-pre-wrap' : 'whitespace-pre',
LINE_TONE[line.t],
)}
>
{line.t === 'cmd' && (
<>
+5 -5
View File
@@ -14,9 +14,9 @@ import { LINKS } from '@/lib/links'
import { cn } from '@/lib/utils'
const PLATFORMS: readonly { name: OsName; note: string; anchor: string }[] = [
{ name: 'macOS', note: 'Apple Keychain', anchor: 'macos' },
{ name: 'Linux', note: 'Secret Service', anchor: 'linux' },
{ name: 'Windows', note: 'Credential Manager', anchor: 'windows' },
{ name: 'macOS', note: 'brew or cargo', anchor: 'macos' },
{ name: 'Linux', note: 'cargo install', anchor: 'linux' },
{ name: 'Windows', note: 'cargo or .exe', anchor: 'windows' },
]
const FEATURES = [
@@ -124,8 +124,8 @@ export const Home = () => {
<Wrap>
<div className="flex flex-col gap-[22px]">
<Eyebrow className="max-md:hidden">Soulseek client · terminal · Rust</Eyebrow>
<div className="flex items-start gap-3.5 sm:gap-6 md:gap-11">
<Logo size={172} className="mt-2 max-md:!h-28 max-sm:!h-16" />
<div className="flex items-center gap-3.5 sm:gap-6 md:items-start md:gap-11">
<Logo size={172} className="max-md:!h-32 md:mt-2" />
<h1 className="text-[29px] leading-[37px] font-bold tracking-[-0.01em] text-balance sm:text-[34px] sm:leading-[42px] md:text-display md:leading-[var(--text-display--line-height)]">
A soulseek client for the terminal. Built for agents and people who live
there.
+99 -27
View File
@@ -2,6 +2,7 @@ import { Button } from '@/components/ui/button'
import { FeatureCard } from '@/components/ui/feature-card'
import { Code } from '@/components/ui/inline-code'
import { Cols, PageHead, Prose, Section, SectionHead } from '@/components/ui/layout'
import { OsIcon, type OsName } from '@/components/ui/os-icon'
import { Callout } from '@/components/ui/panel'
import { Terminal, type TermLine } from '@/components/ui/terminal'
import { LINKS } from '@/lib/links'
@@ -27,6 +28,76 @@ const LIB_LINES: TermLine[] = LIB_SRC.split('\n').map((text) => ({
text,
}))
const ReleasesLink = ({ children }: { children: React.ReactNode }) => (
<a
href={LINKS.releases}
target="_blank"
rel="noopener noreferrer"
className="text-link hover:text-link-hover"
>
{children}
</a>
)
interface InstallRoute {
id: string
os: OsName
best: string
lines: readonly TermLine[]
alt: React.ReactNode
}
const INSTALLS: readonly InstallRoute[] = [
{
id: 'macos',
os: 'macOS',
best: 'Homebrew',
lines: [
{ t: 'cmd', text: 'brew install michel/tap/soulseek-rs' },
{ t: 'cmd', text: 'soulseek-rs' },
],
alt: (
<>
A prebuilt binary for both Apple silicon and Intel, so no Rust toolchain. With one
installed, <Code>cargo install soulseek-rs</Code> builds it from crates.io instead.
</>
),
},
{
id: 'linux',
os: 'Linux',
best: 'cargo',
lines: [
{ t: 'cmd', text: 'cargo install soulseek-rs' },
{ t: 'cmd', text: 'soulseek-rs' },
],
alt: (
<>
Homebrew on Linux works the same as on macOS. Without either, the{' '}
<ReleasesLink>releases page</ReleasesLink> has static musl archives for x86-64 and
arm64 that run on any distribution.
</>
),
},
{
id: 'windows',
os: 'Windows',
best: 'cargo or a prebuilt .exe',
lines: [
{ t: 'cmd', text: 'cargo install soulseek-rs' },
{ t: 'cmd', text: 'soulseek-rs.exe' },
],
alt: (
<>
Cargo needs the MSVC build tools. To skip both, take the{' '}
<code>pc-windows-msvc</code> zip from the{' '}
<ReleasesLink>releases page</ReleasesLink>, unpack it, and put{' '}
<code>soulseek-rs.exe</code> on your PATH.
</>
),
},
]
interface PlatformNote {
id: string
name: string
@@ -191,16 +262,7 @@ export const Install = () => {
<div className="flex flex-col gap-9">
<Step n={1} title="Install the client">
<p className="text-secondary">
Homebrew, on macOS or Linux. A prebuilt binary, so no Rust toolchain:
</p>
<Terminal
lines={[
{ t: 'cmd', text: 'brew install michel/tap/soulseek-rs' },
{ t: 'cmd', text: 'soulseek-rs' },
]}
/>
<p className="text-secondary">
With a Rust toolchain from{' '}
One command per platform. Cargo needs a Rust toolchain from{' '}
<a
href={LINKS.rustup}
target="_blank"
@@ -209,29 +271,39 @@ export const Install = () => {
>
rustup.rs
</a>
, cargo works everywhere else:
; the other routes don&rsquo;t.
</p>
<Terminal lines={[{ t: 'cmd', text: 'cargo install soulseek-rs' }]} />
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
{INSTALLS.map((route) => (
<div
key={route.id}
id={route.id}
className="flex scroll-mt-20 flex-col gap-3.5 rounded-md border border-hairline bg-panel p-[18px] sm:p-[22px]"
>
<div className="flex items-center gap-2.5">
<OsIcon name={route.os} />
<h3 className="text-heading leading-[var(--text-heading--line-height)] font-medium">
{route.os}
</h3>
</div>
<span className="text-[11px] uppercase tracking-[var(--tracking-label)] text-secondary">
{route.best}
</span>
<Terminal lines={route.lines} wrap />
<p className="text-[12.5px] leading-5 text-secondary [&_code]:font-mono [&_code]:text-primary">
{route.alt}
</p>
</div>
))}
</div>
<p className="text-[13px] text-muted">
Either way the binary lands on your PATH. Run <Code>soulseek-rs</Code> in a
terminal and the TUI opens, in a script or a pipe it wants a subcommand
Every route puts the same binary on your PATH. Run <Code>soulseek-rs</Code> in
a terminal and the TUI opens, in a script or a pipe it wants a subcommand
instead.
</p>
<div className="flex flex-wrap gap-2 sm:gap-3">
<Button href={LINKS.releases}>Prebuilt binaries</Button>
</div>
<p className="text-[13px] text-muted">
No Rust toolchain? Grab a prebuilt archive for your platform from the{' '}
<a
href={LINKS.releases}
target="_blank"
rel="noopener noreferrer"
className="text-link hover:text-link-hover"
>
releases page
</a>
, unpack it, and put the binary somewhere on your PATH.
</p>
</Step>
<Step n={2} title="Build from source">
@@ -350,7 +422,7 @@ export const Install = () => {
{PLATFORMS.map((platform) => (
<div
key={platform.id}
id={platform.id}
id={`${platform.id}-paths`}
className="scroll-mt-20 rounded-md border border-hairline bg-panel p-[18px] sm:p-[22px]"
>
<h3 className="mb-4 text-heading leading-[var(--text-heading--line-height)] font-medium">