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:
@@ -12,6 +12,6 @@ export const Logo = ({ size = 180, className }: LogoProps) => (
|
|||||||
role="img"
|
role="img"
|
||||||
aria-label="soulseek-rs"
|
aria-label="soulseek-rs"
|
||||||
className={cn('logo-mark shrink-0 bg-contain bg-no-repeat', className)}
|
className={cn('logo-mark shrink-0 bg-contain bg-no-repeat', className)}
|
||||||
style={{ height: size, width: size * ASPECT }}
|
style={{ height: size, aspectRatio: ASPECT }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ interface TerminalProps {
|
|||||||
lines: readonly TermLine[]
|
lines: readonly TermLine[]
|
||||||
copy?: string
|
copy?: string
|
||||||
className?: string
|
className?: string
|
||||||
|
wrap?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Terminal = ({ label, lines, copy, className }: TerminalProps) => {
|
export const Terminal = ({ label, lines, copy, className, wrap }: TerminalProps) => {
|
||||||
const copyText =
|
const copyText =
|
||||||
copy ??
|
copy ??
|
||||||
lines
|
lines
|
||||||
@@ -47,7 +48,11 @@ export const Terminal = ({ label, lines, copy, className }: TerminalProps) => {
|
|||||||
{lines.map((line, i) => (
|
{lines.map((line, i) => (
|
||||||
<span
|
<span
|
||||||
key={i}
|
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' && (
|
{line.t === 'cmd' && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import { LINKS } from '@/lib/links'
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const PLATFORMS: readonly { name: OsName; note: string; anchor: string }[] = [
|
const PLATFORMS: readonly { name: OsName; note: string; anchor: string }[] = [
|
||||||
{ name: 'macOS', note: 'Apple Keychain', anchor: 'macos' },
|
{ name: 'macOS', note: 'brew or cargo', anchor: 'macos' },
|
||||||
{ name: 'Linux', note: 'Secret Service', anchor: 'linux' },
|
{ name: 'Linux', note: 'cargo install', anchor: 'linux' },
|
||||||
{ name: 'Windows', note: 'Credential Manager', anchor: 'windows' },
|
{ name: 'Windows', note: 'cargo or .exe', anchor: 'windows' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const FEATURES = [
|
const FEATURES = [
|
||||||
@@ -124,8 +124,8 @@ export const Home = () => {
|
|||||||
<Wrap>
|
<Wrap>
|
||||||
<div className="flex flex-col gap-[22px]">
|
<div className="flex flex-col gap-[22px]">
|
||||||
<Eyebrow className="max-md:hidden">Soulseek client · terminal · Rust</Eyebrow>
|
<Eyebrow className="max-md:hidden">Soulseek client · terminal · Rust</Eyebrow>
|
||||||
<div className="flex items-start gap-3.5 sm:gap-6 md:gap-11">
|
<div className="flex items-center gap-3.5 sm:gap-6 md:items-start md:gap-11">
|
||||||
<Logo size={172} className="mt-2 max-md:!h-28 max-sm:!h-16" />
|
<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)]">
|
<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
|
A soulseek client for the terminal. Built for agents and people who live
|
||||||
there.
|
there.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Button } from '@/components/ui/button'
|
|||||||
import { FeatureCard } from '@/components/ui/feature-card'
|
import { FeatureCard } from '@/components/ui/feature-card'
|
||||||
import { Code } from '@/components/ui/inline-code'
|
import { Code } from '@/components/ui/inline-code'
|
||||||
import { Cols, PageHead, Prose, Section, SectionHead } from '@/components/ui/layout'
|
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 { Callout } from '@/components/ui/panel'
|
||||||
import { Terminal, type TermLine } from '@/components/ui/terminal'
|
import { Terminal, type TermLine } from '@/components/ui/terminal'
|
||||||
import { LINKS } from '@/lib/links'
|
import { LINKS } from '@/lib/links'
|
||||||
@@ -27,6 +28,76 @@ const LIB_LINES: TermLine[] = LIB_SRC.split('\n').map((text) => ({
|
|||||||
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 {
|
interface PlatformNote {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@@ -191,16 +262,7 @@ export const Install = () => {
|
|||||||
<div className="flex flex-col gap-9">
|
<div className="flex flex-col gap-9">
|
||||||
<Step n={1} title="Install the client">
|
<Step n={1} title="Install the client">
|
||||||
<p className="text-secondary">
|
<p className="text-secondary">
|
||||||
Homebrew, on macOS or Linux. A prebuilt binary, so no Rust toolchain:
|
One command per platform. Cargo needs a Rust toolchain from{' '}
|
||||||
</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{' '}
|
|
||||||
<a
|
<a
|
||||||
href={LINKS.rustup}
|
href={LINKS.rustup}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -209,29 +271,39 @@ export const Install = () => {
|
|||||||
>
|
>
|
||||||
rustup.rs
|
rustup.rs
|
||||||
</a>
|
</a>
|
||||||
, cargo works everywhere else:
|
; the other routes don’t.
|
||||||
</p>
|
</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">
|
<p className="text-[13px] text-muted">
|
||||||
Either way the binary lands on your PATH. Run <Code>soulseek-rs</Code> in a
|
Every route puts the same binary on your PATH. Run <Code>soulseek-rs</Code> in
|
||||||
terminal and the TUI opens, in a script or a pipe it wants a subcommand
|
a terminal and the TUI opens, in a script or a pipe it wants a subcommand
|
||||||
instead.
|
instead.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-2 sm:gap-3">
|
<div className="flex flex-wrap gap-2 sm:gap-3">
|
||||||
<Button href={LINKS.releases}>Prebuilt binaries</Button>
|
<Button href={LINKS.releases}>Prebuilt binaries</Button>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<Step n={2} title="Build from source">
|
<Step n={2} title="Build from source">
|
||||||
@@ -350,7 +422,7 @@ export const Install = () => {
|
|||||||
{PLATFORMS.map((platform) => (
|
{PLATFORMS.map((platform) => (
|
||||||
<div
|
<div
|
||||||
key={platform.id}
|
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]"
|
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">
|
<h3 className="mb-4 text-heading leading-[var(--text-heading--line-height)] font-medium">
|
||||||
|
|||||||
Reference in New Issue
Block a user