diff --git a/website/src/components/layout/nav.tsx b/website/src/components/layout/nav.tsx index 3d48502..1355d58 100644 --- a/website/src/components/layout/nav.tsx +++ b/website/src/components/layout/nav.tsx @@ -20,7 +20,13 @@ const ITEMS = [ export const Nav = () => (
- + {/* min-h-11: the wordmark's own box is 19px tall, which left the most + reached-for control in the header under the 24px target minimum. */} + diff --git a/website/src/components/tui/hero-terminal.tsx b/website/src/components/tui/hero-terminal.tsx index 042ae0b..6db53d5 100644 --- a/website/src/components/tui/hero-terminal.tsx +++ b/website/src/components/tui/hero-terminal.tsx @@ -79,14 +79,17 @@ export const HeroTerminal = () => { const [transfers, setTransfers] = useState(TRANSFERS) const [focus, setFocus] = useState(2) - // Reduced motion skips the performance, so the resting state is the initial - // state. Resolved once, in the initialiser, to keep it out of an effect. - const [phase, setPhase] = useState(() => - prefersReducedMotion() ? 'interactive' : 'searching', - ) - const [selected, setSelected] = useState(() => - prefersReducedMotion() ? RESTING_ROW : 0, - ) + /* + * Both start where the prerendered HTML starts, whatever the OS says. + * + * Reading prefers-reduced-motion in the initialiser made the first client + * render disagree with the server for anyone who has it on: the server + * always serialises the searching state, the client painted the resting + * one, and React bailed out with a hydration mismatch (#418). The + * preference is applied a tick later instead, by the opening-run effect. + */ + const [phase, setPhase] = useState('searching') + const [selected, setSelected] = useState(0) const shellRef = useRef(null) @@ -300,10 +303,23 @@ export const HeroTerminal = () => { * script only ever describes the opening state. */ useEffect(() => { - if (prefersReducedMotion()) return - const timers: ReturnType[] = [] const at = (fn: () => void, ms: number) => timers.push(setTimeout(fn, ms)) + const cancel = () => { + for (const timer of timers) clearTimeout(timer) + } + + // Asked for no motion: cut to where the run would have ended. Deferred by + // a tick rather than decided in the state initialiser, so the first render + // still matches the prerendered HTML. + if (prefersReducedMotion()) { + at(() => { + setSelected(RESTING_ROW) + setPhase('interactive') + }, 0) + return cancel + } + const mark = (i: number) => { setResults((prev) => ({ ...prev, @@ -343,9 +359,7 @@ export const HeroTerminal = () => { setPhase('interactive') }, 5050) - return () => { - for (const timer of timers) clearTimeout(timer) - } + return cancel }, [queueFrom]) const submitSearch = (query: string) => { diff --git a/website/src/components/tui/panes.tsx b/website/src/components/tui/panes.tsx index 54a7b43..4e5854b 100644 --- a/website/src/components/tui/panes.tsx +++ b/website/src/components/tui/panes.tsx @@ -47,6 +47,10 @@ export const SearchesPane = ({ > / { setQuery(event.target.value) diff --git a/website/src/components/ui/panel.tsx b/website/src/components/ui/panel.tsx index da0dac4..80a029d 100644 --- a/website/src/components/ui/panel.tsx +++ b/website/src/components/ui/panel.tsx @@ -42,6 +42,11 @@ interface CalloutProps { * * The tone lives in the label, not in a coloured left rule — one ribbon per * card is decoration, and the label was already saying the same thing twice. + * + * But hue alone cannot carry it: in the light theme --status-warning and + * --accent-text are exactly isoluminant (5.14:1 each on --bg-panel, 1.00:1 + * against each other), so a red-green deficient reader sees one colour. The + * "!" is the second channel the left rule used to provide by sheer area. */ export const Callout = ({ title, tone = 'accent', children, className }: CalloutProps) => (
+ {tone === 'warn' && } {title}
)} diff --git a/website/src/pages/community.tsx b/website/src/pages/community.tsx index 50e4c99..c9b6aa6 100644 --- a/website/src/pages/community.tsx +++ b/website/src/pages/community.tsx @@ -112,7 +112,7 @@ export const Community = () => { <> It’s mostly one person who’s been on Soulseek since the early 2000s. Issues and pull requests are welcome, and if another client fits you better, use it. @@ -207,7 +207,7 @@ export const Community = () => { title="Three good clients to point you elsewhere." > soulseek-rs has no GUI and no daemon mode yet. If one of these fits you better, - use it. Recommending them is the strongest trust signal there is. + use it.
{ALTERNATIVES.map((alt) => ( @@ -251,7 +251,10 @@ export const Community = () => {
- + {/* Raised, not panel: this section is a band, which is itself + bg-panel — a panel-on-panel callout has only a 1.36:1 hairline + holding it together. */} +

No telemetry. Ever.

Not affiliated with or endorsed by the Soulseek project. diff --git a/website/src/pages/docs.tsx b/website/src/pages/docs.tsx index 42dab38..9174980 100644 --- a/website/src/pages/docs.tsx +++ b/website/src/pages/docs.tsx @@ -204,7 +204,14 @@ export const Docs = () => {

- + {/* + The only Panel on a band. Two consequences, both handled here: + its default bg-panel fill would match the band exactly, so it takes + bg-raised like the band callouts do; and the legend paints over the + top border to punch the gap, so it has to match the surface *behind* + the panel — the band — not the panel's own fill. See panel.tsx. + */} +

diff --git a/website/src/pages/install.tsx b/website/src/pages/install.tsx index c5dc603..e1aaa88 100644 --- a/website/src/pages/install.tsx +++ b/website/src/pages/install.tsx @@ -255,7 +255,13 @@ export const Install = () => {

{platform.name}

-
+ {/* + Label beside value while the card is full width, label above it + once the cards go three up: at a third of the row the label + column ate enough of the line that every path broke mid-token + ("config.to / ml"). Stacking gives the path the whole card. + */} +
{( [ ['Password', platform.credentialStore, false], @@ -265,7 +271,7 @@ export const Install = () => { ] as const ).map(([label, value, mono]) => (
-
+
{label}
@@ -333,7 +339,10 @@ export const Install = () => { />
- + {/* Raised, not panel: this section is a band, which is itself + bg-panel — a panel-on-panel callout has only a 1.36:1 hairline + holding it together. */} +

Your downloads and your shared directories are yours: they stay exactly where they are. On macOS, delete the keychain entry for soulseek-rs in