Files
soulseek-rs-modded/website/src/pages/docs.tsx
T
Michel de Graaf 1eef464677 refactor(website): cut the stock patterns out of the design
Audited against the AI-design catalog. The world stays — the terminal
aesthetic, the oxide accent, IBM Plex Mono are all chosen, and the audit
cleared them. What went is the handful of defaults that had crept in
underneath it.

Eight feature cards at identical weight said every feature mattered equally.
Search is what the client is for, so it takes a double-width cell and the
grid drops to three up; eight cards plus that one double still comes out
square.

Each glyph sat in a 34px bordered square — the stock icon-in-a-chip
treatment that ships with every component library. The glyphs are the real
TUI's, so they now sit inline like the prompt markers they are.

Callouts lose the coloured left rule. The catalog puts it plainly: a
coloured left border is about as reliable a sign of generated design as an
em-dash is in prose. The tone already lives in the label, which was saying
the same thing twice.

Thirteen arrow glyphs were typed into labels and link text. The
leaving-the-site mark belongs to the control, not the copy, so Button draws
it once, aria-hidden, and only when the link actually leaves. The "read
more" arrows on internal links were pure flourish and are gone.

The hero also drops "blazingly fast Rust", which TONE-OF-VOICE.md lists
under banned, no exceptions.
2026-07-25 00:54:49 +02:00

228 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Link } from 'react-router'
import { Button } from '@/components/ui/button'
import { Code } from '@/components/ui/inline-code'
import {
Cols,
Eyebrow,
PageHead,
Prose,
Section,
SectionHead,
} from '@/components/ui/layout'
import { Callout, Panel } from '@/components/ui/panel'
import { Terminal } from '@/components/ui/terminal'
import { LINKS } from '@/lib/links'
const GLOBAL_KEYS: readonly (readonly [string, string])[] = [
['Space', 'select a result'],
['Enter', 'download / send'],
['b', "browse the owner's files"],
['c', 'chat rooms'],
['m', 'compose a private message'],
['i', 'inbox (unread counter)'],
['/', 'filter the current list'],
['a / A', 'select all / none'],
['1–3', 'focus a pane'],
['q', 'quit'],
]
const ROOM_KEYS: readonly (readonly [string, string])[] = [
['Enter', 'join room / send message'],
['Tab · ⇧Tab', 'switch between open rooms'],
['x', 'leave the active room'],
['l', 'back to the room list'],
['↑ ↓', 'select a member'],
['b', "browse the member's files"],
['m', 'message the member'],
]
const PANES: readonly { num?: number; title: string; body: string }[] = [
{
num: 1,
title: 'Searches',
body: 'Your queries and how many results each returned. Type / to search the network; each search keeps its own result set.',
},
{
num: 2,
title: 'Results',
body: 'Files from the network: size, user, bitrate, speed, free slots. Space selects, Enter queues a download.',
},
{
num: 3,
title: 'Downloads / Uploads',
body: 'Live transfers in both directions with progress and speed: queued, active, complete, failed.',
},
{
title: 'Info',
body: 'Everything about the selected file: user, size, path, bitrate, length, queue position, free slots.',
},
]
/*
* The keycap's heavier bottom edge is a shadow, not a thicker bottom border.
*
* A 2px border on one side of a rounded box thins out as it turns the corner,
* so the edge tapers exactly where a real keycap looks solid. An unblurred
* 1px shadow sits under the full curve instead, which is the look the design
* system's `.key` rule is after.
*/
const KeyRow = ({ combo, description }: { combo: string; description: string }) => (
<div className="grid grid-cols-[120px_1fr] items-baseline gap-3 border-b border-hairline py-[7px]">
<span>
<span className="inline-block min-w-5 rounded-md border border-line px-[7px] text-center text-xs text-primary shadow-[0_1px_0_0_var(--border-default)]">
{combo}
</span>
</span>
<span className="text-[13px] text-secondary">{description}</span>
</div>
)
export const Docs = () => {
return (
<>
<PageHead
eyebrow="docs · quick start"
title="Install it, run it, drive it from the keyboard."
>
This is the short version. The full reference (every flag, every message) lives in
the README and on docs.rs.
</PageHead>
<Section>
<SectionHead eyebrow="1 · first run" title="Two commands to a running client.">
You need a Rust toolchain. After <Code>cargo install</Code>, the binary is on your
PATH.
</SectionHead>
<Cols start>
<Terminal
lines={[
{ t: 'cmd', text: 'cargo install soulseek-rs' },
{ t: 'cmd', text: 'soulseek-rs' },
{ t: 'cm', text: '# the TUI opens; log in or register on first run' },
]}
/>
<Prose>
<p>
On first run soulseek-rs shows a login screen: sign in with an existing
Soulseek account or register a new one, right in the TUI. Your password is
stored in the OS keychain, not in plain text.
</p>
<p>
Downloads land in a conventional folder by default, and your TUI state
(searches, downloads, rooms) is saved and restored across restarts.
</p>
</Prose>
</Cols>
</Section>
<Section>
<SectionHead eyebrow="2 · the layout" title="Four panes.">
The whole client is these four boxes. Press <Code>1</Code>–<Code>3</Code> to focus
one; the focused pane&rsquo;s legend turns green.
</SectionHead>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
{PANES.map((pane) => (
<Panel key={pane.title} num={pane.num} title={pane.title}>
<p className="text-[13px] leading-[22px] text-secondary">{pane.body}</p>
</Panel>
))}
</div>
</Section>
<Section>
<SectionHead eyebrow="3 · keys" title="It's all keyboard.">
The bottom bar always shows the keys for where you are. The essentials:
</SectionHead>
<Cols>
<div>
<Eyebrow className="mb-4 block">anywhere</Eyebrow>
<div className="flex flex-col">
{GLOBAL_KEYS.map(([combo, description]) => (
<KeyRow key={combo} combo={combo} description={description} />
))}
</div>
</div>
<div>
<Eyebrow className="mb-4 block">
in the chat-rooms popup <span className="text-muted">(press c)</span>
</Eyebrow>
<div className="flex flex-col">
{ROOM_KEYS.map(([combo, description]) => (
<KeyRow key={combo} combo={combo} description={description} />
))}
</div>
</div>
</Cols>
</Section>
<Section>
<SectionHead
eyebrow="4 · from the shell"
title="Every command also runs as a one-off."
>
Skip the TUI when you&rsquo;re scripting or mid-task.
</SectionHead>
<Cols start>
<Terminal
label="shell"
lines={[
{ t: 'cmd', text: 'soulseek-rs "public domain field recordings"' },
{ t: 'cm', text: '# open the TUI straight onto a search' },
{ t: 'cmd', text: 'soulseek-rs message <username> "hello there"' },
{ t: 'cmd', text: 'soulseek-rs rooms' },
{ t: 'cm', text: '# list public rooms, busiest first' },
{ t: 'cmd', text: 'soulseek-rs chat <room> "hello room"' },
{ t: 'cmd', text: 'soulseek-rs portmap' },
]}
/>
<Prose>
<p>
The subcommands mirror the TUI: <Code>search</Code>, <Code>message</Code>,{' '}
<Code>browse</Code>, <Code>rooms</Code>, <Code>chat</Code>, and{' '}
<Code>portmap</Code>. Pass a room and a message to <Code>chat</Code> to say
one thing and exit.
</p>
<p>
<Code>portmap</Code> tests whether your router will let peers connect back,
worth running once.{' '}
<Link to="/install" className="text-link hover:text-link-hover">
More on being reachable
</Link>
</p>
</Prose>
</Cols>
<div className="mt-6">
<Callout tone="warn" title="the one limit worth knowing up front">
<p>
If both you and a peer are behind routers with no forwarded port, browsing
that peer can&rsquo;t work, that&rsquo;s a fundamental Soulseek/peer-to-peer
limitation, not a bug.
</p>
</Callout>
</div>
</Section>
<Section band>
<Panel title="the rest">
<Cols center className="gap-6">
<Prose>
<p>
Deeper docs live where they stay current: the full README on GitHub, and the
generated API reference for the library on docs.rs.
</p>
</Prose>
<div className="flex flex-wrap gap-2 sm:gap-3">
<Button href={LINKS.gh} variant="accent">
README
</Button>
<Button href={LINKS.docsrs}>docs.rs</Button>
<Button href={LINKS.changelog}>Changelog</Button>
</div>
</Cols>
</Panel>
</Section>
</>
)
}