feat(website): sync the latest design changes
Headline now leads with agents alongside people, and the second callout swaps sharing advice for what the shell subcommands make possible — cron, a script, or an agent driving the client with no daemon to stand up. Mobile gets the design's newer treatment: the eyebrow and the sub-heading step aside so the headline and the install button are all that precede the terminal, nav links and the button row centre, the footer keeps two columns with the brand spanning both, and under 520px the platform strip drops its labels to a centred row of icons. That last one makes the icon the only thing naming the platform, so it carries an aria-label now rather than being hidden from assistive tech.
This commit is contained in:
@@ -45,7 +45,7 @@ const COLUMNS: readonly FooterColumn[] = [
|
||||
},
|
||||
]
|
||||
|
||||
const LINK_CLASS = 'text-[13px] text-secondary hover:text-primary'
|
||||
const LINK_CLASS = 'text-[12.5px] text-secondary hover:text-primary sm:text-[13px]'
|
||||
|
||||
const FooterAnchor = ({ label, href }: FooterLink) =>
|
||||
href.startsWith('/') ? (
|
||||
@@ -59,12 +59,15 @@ const FooterAnchor = ({ label, href }: FooterLink) =>
|
||||
)
|
||||
|
||||
export const Footer = () => (
|
||||
<footer className="border-t border-hairline bg-base pt-11 pb-10">
|
||||
<footer className="border-t border-hairline bg-base pt-7 pb-6 sm:pt-11 sm:pb-10">
|
||||
<Wrap>
|
||||
<div className="grid grid-cols-1 gap-8 min-[520px]:grid-cols-2 md:grid-cols-[1.6fr_1fr_1fr_1fr]">
|
||||
<div className="flex max-w-[320px] flex-col gap-3.5">
|
||||
{/* Two columns even on the narrowest phone, with the brand spanning
|
||||
both — one column per link group makes the footer taller than the
|
||||
page it closes. */}
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-5 sm:gap-8 md:grid-cols-[1.6fr_1fr_1fr_1fr]">
|
||||
<div className="col-span-2 flex max-w-[320px] flex-col gap-2 sm:gap-3.5 md:col-span-1">
|
||||
<Wordmark size={22} />
|
||||
<p className="text-[13px] leading-[22px] text-secondary">
|
||||
<p className="text-xs leading-[19px] text-secondary sm:text-[13px] sm:leading-[22px]">
|
||||
A Soulseek client and protocol library for Rust.
|
||||
</p>
|
||||
<p className="font-forum text-xs text-muted">
|
||||
@@ -74,12 +77,12 @@ export const Footer = () => (
|
||||
|
||||
{COLUMNS.map((column) => (
|
||||
<div key={column.heading}>
|
||||
<h2 className="mb-3.5 text-label uppercase tracking-[var(--tracking-label)] text-secondary">
|
||||
<h2 className="mb-2 text-label uppercase tracking-[var(--tracking-label)] text-secondary sm:mb-3.5">
|
||||
{column.heading}
|
||||
</h2>
|
||||
<ul>
|
||||
{column.links.map((link) => (
|
||||
<li key={link.label} className="mb-2.5">
|
||||
<li key={link.label} className="mb-1.5 sm:mb-2.5">
|
||||
<FooterAnchor {...link} />
|
||||
</li>
|
||||
))}
|
||||
@@ -88,10 +91,10 @@ export const Footer = () => (
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-11 flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-hairline pt-5.5 text-[12.5px] text-muted">
|
||||
<div className="mt-5.5 flex flex-wrap items-center gap-x-4 gap-y-1 border-t border-hairline pt-4 text-[11.5px] text-muted sm:mt-11 sm:gap-x-6 sm:gap-y-2 sm:pt-5.5 sm:text-[12.5px]">
|
||||
<span>MIT · © 2026 Michel de Graaf</span>
|
||||
<span className="text-success">No telemetry. Ever.</span>
|
||||
<span className="ml-auto">{VERSION}</span>
|
||||
<span className="sm:ml-auto">{VERSION}</span>
|
||||
</div>
|
||||
</Wrap>
|
||||
</footer>
|
||||
|
||||
@@ -26,7 +26,7 @@ export const Nav = () => (
|
||||
|
||||
<nav
|
||||
aria-label="Main"
|
||||
className="order-3 flex w-full items-center gap-0.5 overflow-x-auto border-t border-hairline py-1 [-ms-overflow-style:none] [scrollbar-width:none] sm:order-none sm:w-auto sm:border-t-0 sm:py-0 [&::-webkit-scrollbar]:hidden"
|
||||
className="order-3 flex w-full items-center justify-center gap-0.5 overflow-x-auto border-t border-hairline py-1 [-ms-overflow-style:none] [scrollbar-width:none] sm:order-none sm:w-auto sm:justify-start sm:border-t-0 sm:py-0 [&::-webkit-scrollbar]:hidden"
|
||||
>
|
||||
{ITEMS.map((item) => (
|
||||
<NavLink
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export type OsName = 'macOS' | 'Linux' | 'Windows'
|
||||
|
||||
const PATHS: Record<Exclude<OsName, 'Windows'>, string> = {
|
||||
@@ -10,12 +12,24 @@ const PATHS: Record<Exclude<OsName, 'Windows'>, string> = {
|
||||
const WINDOWS_PATH =
|
||||
'M2 3.6l8.5-1.2v9.1H2V3.6zm10 -1.35L22.5 1v10.5H12V2.25zM2 12.5h8.5v9.1L2 20.4v-7.9zm10 0h10.5V23L12 21.75V12.5z'
|
||||
|
||||
/** Decorative — the adjacent label already names the platform. */
|
||||
export const OsIcon = ({ name }: { name: OsName }) => (
|
||||
interface OsIconProps {
|
||||
name: OsName
|
||||
className?: string
|
||||
}
|
||||
|
||||
/*
|
||||
* Labelled rather than decorative: below 520px the adjacent text is hidden,
|
||||
* so the icon becomes the only thing naming the platform.
|
||||
*/
|
||||
export const OsIcon = ({ name, className }: OsIconProps) => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className="block size-[22px] shrink-0 text-primary"
|
||||
role="img"
|
||||
aria-label={name}
|
||||
className={cn(
|
||||
'block size-[22px] shrink-0 text-primary transition-colors group-hover:text-accent',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<path fill="currentColor" d={name === 'Windows' ? WINDOWS_PATH : PATHS[name]} />
|
||||
</svg>
|
||||
|
||||
+35
-19
@@ -11,6 +11,7 @@ import { OsIcon, type OsName } from '@/components/ui/os-icon'
|
||||
import { Callout } from '@/components/ui/panel'
|
||||
import { Terminal } from '@/components/ui/terminal'
|
||||
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' },
|
||||
@@ -103,20 +104,24 @@ export const Home = () => {
|
||||
<section className="pt-5 pb-8 sm:pt-7 sm:pb-10">
|
||||
<Wrap>
|
||||
<div className="flex flex-col gap-[22px]">
|
||||
<Eyebrow>Soulseek client · terminal · Rust</Eyebrow>
|
||||
{/* Eyebrow and sub are desktop-only: on a phone the headline and
|
||||
the CTA should be all that stands between the top of the page
|
||||
and the terminal. */}
|
||||
<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 shrink-0 max-md:!h-28 max-sm:!h-16" />
|
||||
<Logo size={172} className="mt-2 max-md:!h-28 max-sm:!h-16" />
|
||||
<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 people who live in the terminal.
|
||||
Soulseek for the terminal. Built for agents and people who live there.
|
||||
Blazingly fast, in Rust 🦀.
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-lg leading-[29px] text-pretty text-secondary">
|
||||
<p className="text-lg leading-[29px] text-pretty text-secondary max-md:hidden">
|
||||
Search the network, share your files, browse someone’s collection, join
|
||||
a room. It runs over ssh on the machine where your music already lives, with
|
||||
no window and no mouse.
|
||||
no window and no mouse, and it’s blazingly fast Rust 🦀.
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center justify-between gap-x-5 gap-y-3 sm:gap-y-4">
|
||||
<div className="flex flex-wrap items-center gap-2 max-sm:w-full sm:gap-3">
|
||||
<div className="flex flex-wrap items-center gap-2 max-md:w-full max-md:justify-center sm:gap-3">
|
||||
<Button href="/install" variant="accent">
|
||||
Install
|
||||
</Button>
|
||||
@@ -135,24 +140,31 @@ export const Home = () => {
|
||||
|
||||
<section className="border-t border-hairline py-6">
|
||||
<Wrap>
|
||||
{/*
|
||||
Three layouts, narrowest first: under 520px the labels drop and it
|
||||
becomes a centred row of icons; up to 820px each platform is a
|
||||
full-width stacked row; above that they sit inline, rule-separated.
|
||||
*/}
|
||||
<div className="flex flex-wrap items-center gap-3.5 sm:gap-8">
|
||||
<Eyebrow>Runs on</Eyebrow>
|
||||
<ul className="flex w-full flex-wrap sm:w-auto">
|
||||
<Eyebrow className="max-[519px]:hidden">Runs on</Eyebrow>
|
||||
<ul className="flex w-full flex-nowrap justify-center min-[520px]:flex-wrap min-[520px]:justify-start sm:w-auto">
|
||||
{PLATFORMS.map((platform, i) => (
|
||||
<li
|
||||
key={platform.name}
|
||||
className={
|
||||
className={cn(
|
||||
'flex w-auto border-hairline px-[18px] first:border-l-0 first:pl-0 not-first:border-l',
|
||||
'min-[520px]:w-full min-[520px]:border-l-0 min-[520px]:px-0',
|
||||
i === 0
|
||||
? 'flex w-full sm:w-auto sm:pr-6.5'
|
||||
: 'flex w-full border-hairline sm:w-auto sm:border-l sm:px-6.5'
|
||||
}
|
||||
? 'sm:w-auto sm:pr-6.5'
|
||||
: 'sm:w-auto sm:border-l sm:px-6.5',
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
to={`/install#${platform.anchor}`}
|
||||
className="group flex w-full items-center gap-2.5 border-t border-hairline py-2.5 first:border-t-0 sm:w-auto sm:border-t-0 sm:py-0"
|
||||
className="group flex min-h-11 w-auto items-center justify-center gap-2.5 border-hairline py-1.5 min-[520px]:w-full min-[520px]:justify-start min-[520px]:border-t min-[520px]:py-2.5 min-[520px]:first:border-t-0 sm:w-auto sm:border-t-0 sm:py-0"
|
||||
>
|
||||
<OsIcon name={platform.name} />
|
||||
<span className="flex flex-col leading-[1.35]">
|
||||
<OsIcon name={platform.name} className="max-[519px]:size-[26px]" />
|
||||
<span className="flex flex-col leading-[1.35] max-[519px]:hidden">
|
||||
<b className="text-[13.5px] font-medium text-primary transition-colors group-hover:text-accent">
|
||||
{platform.name}
|
||||
</b>
|
||||
@@ -198,11 +210,15 @@ export const Home = () => {
|
||||
</Link>
|
||||
</p>
|
||||
</Callout>
|
||||
<Callout title="share back">
|
||||
<Callout title="agents and automation">
|
||||
<p>
|
||||
Point <Code>--shared-dir</Code> at your library and leave it running in a
|
||||
tmux session on your share or headless box. The network only works because
|
||||
people share, and users who share back tend to get queue slots faster.
|
||||
Every command runs as a one-off from the shell: <Code>search</Code>,{' '}
|
||||
<Code>browse</Code>, <Code>rooms</Code>, <Code>chat</Code>,{' '}
|
||||
<Code>message</Code>, <Code>portmap</Code>. Point cron, a script, or an
|
||||
agent at them and read the output back. No daemon, no API to stand up.{' '}
|
||||
<Link to="/docs" className="text-link hover:text-link-hover">
|
||||
See the commands →
|
||||
</Link>
|
||||
</p>
|
||||
</Callout>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user