diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c8355ec..31b97f4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,12 +2,6 @@ name: CI
# Branch pushes are covered by the pull_request trigger. Listening to both for
# every branch ran the whole matrix twice per PR.
-#
-# website/ holds the GitHub Pages site and no Rust. Ignoring it here must stay
-# in lockstep with the same list in release.yml: the two skip together, so the
-# release ci-gate never waits for a CI run that was never queued. Deliberately
-# not applied to pull_request — CI is a required check there, and a skipped
-# required check leaves the PR permanently unmergeable.
on:
push:
branches: [master]
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index d4ff9bb..d18518b 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -1,7 +1,5 @@
name: Pages
-# Only the website. The Rust workflows ignore this same path, so a site-only
-# push runs this alone and a code-only push never rebuilds the site.
on:
push:
branches: [master]
@@ -29,8 +27,6 @@ jobs:
with:
bun-version: latest
- # --frozen-lockfile so a drifted bun.lock fails here rather than
- # silently shipping different dependency versions than were tested.
- name: Install
run: bun install --frozen-lockfile
@@ -40,8 +36,6 @@ jobs:
- name: Lint
run: bun run lint
- # Emits one static HTML file per route plus a 404, so direct links
- # answer 200 with real markup instead of the SPA shell.
- name: Build
run: bun run build
@@ -51,14 +45,10 @@ jobs:
deploy:
name: Deploy
- # Pull requests get the build as a check; only master publishes.
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
- # Scoped to the deploy, not the workflow: a workflow-level group would
- # queue PR builds behind an in-flight master deploy for no reason. Never
- # cancel in flight — a half-uploaded artifact leaves the live site broken.
concurrency:
group: pages
cancel-in-progress: false
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 707ec0b..e0ce3b9 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -2,8 +2,6 @@ name: Release
on:
push:
- # Must match ci.yml's paths-ignore exactly. A website-only push skips both
- # workflows, so ci-gate is never left polling for an absent CI run.
branches: [master]
paths-ignore: ['website/**']
workflow_dispatch:
@@ -51,10 +49,6 @@ jobs:
2>/dev/null || echo "unreachable")
case "$verdict" in
success) echo "CI passed for $GITHUB_SHA"; exit 0 ;;
- # `cancelled` is what CI's own cancel-in-progress does to the
- # older SHA when two pushes land back to back. Treating it as a
- # failure turned every rapid push into a red release; keep
- # waiting instead, and let the newer SHA's run decide.
absent|running|unreachable|cancelled) ;;
*) echo "::error::CI concluded '$verdict' for $GITHUB_SHA - refusing to release"; exit 1 ;;
esac
diff --git a/website/eslint.config.ts b/website/eslint.config.ts
index 6b11a48..755a0fc 100644
--- a/website/eslint.config.ts
+++ b/website/eslint.config.ts
@@ -11,14 +11,9 @@ export default defineConfig(
js.configs.recommended,
- // strictTypeChecked over recommended: it needs type information (below) but
- // catches the whole class of bugs that only types can see — floating
- // promises, unsafe `any` flowing through, misused nullish operands.
tseslint.configs.strictTypeChecked,
tseslint.configs.stylisticTypeChecked,
- // `configs.flat.*`, not `configs['recommended-latest']` — the latter is
- // still the legacy eslintrc shape (plugins as a string array).
reactHooks.configs.flat['recommended-latest'],
jsxA11y.flatConfigs.recommended,
@@ -41,8 +36,6 @@ export default defineConfig(
{ allowConstantExport: true },
],
- // Unused args are usually a mistake, but an `_`-prefixed one is an
- // explicit "I know, I need the positional slot".
'@typescript-eslint/no-unused-vars': [
'error',
{
@@ -52,14 +45,11 @@ export default defineConfig(
},
],
- // Type-only imports must be erasable, so the bundler can drop them
- // without consulting the type graph. Pairs with verbatimModuleSyntax.
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports', fixStyle: 'inline-type-imports' },
],
- // A dropped promise in an event handler fails silently in production.
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
@@ -71,11 +61,6 @@ export default defineConfig(
},
},
- // The TUI demo is a composite widget: it declares role="application" and
- // owns its keys (j/k, arrows, 1-3) the way the real terminal client does.
- // These two rules police exactly the pattern ARIA prescribes for that, so
- // they are off for this one file rather than worked around in the markup.
- // Every control inside it is still a native button or input.
{
files: ['src/components/tui/hero-terminal.tsx'],
rules: {
@@ -84,10 +69,6 @@ export default defineConfig(
},
},
- // Config and build scripts are Node, not browser. Build output is meant to
- // be read on the terminal, so console is the interface, not a stray debug
- // statement. entry-server is a build entry point that never enters the HMR
- // graph, so the component-only export rule does not apply to it.
{
files: ['*.config.ts', 'scripts/**/*.ts'],
languageOptions: {
diff --git a/website/index.html b/website/index.html
index fca9f29..c713b41 100644
--- a/website/index.html
+++ b/website/index.html
@@ -3,8 +3,6 @@
-
soulseek-rs, a Soulseek client for your terminal
@@ -12,7 +10,6 @@
name="description"
content="soulseek-rs is a keyboard-driven Soulseek client and a Rust protocol library. Search, share, browse, and chat from your terminal."
/>
-
@@ -22,18 +19,10 @@
property="og:description"
content="A Soulseek client and protocol library for Rust. Search, share, browse, and chat from your terminal."
/>
-
-
diff --git a/website/scripts/prerender.ts b/website/scripts/prerender.ts
index f570267..a45ae65 100644
--- a/website/scripts/prerender.ts
+++ b/website/scripts/prerender.ts
@@ -1,23 +1,8 @@
-/*
- * Turns the SPA build into real static files.
- *
- * Without this, GitHub Pages has no file at /install and answers a direct
- * link with its 404 page. Writing one HTML file per route means every URL
- * returns 200 with its own title, description, and fully rendered body; the
- * client then hydrates it into the same interactive app.
- *
- * Run after both `vite build` and `vite build --ssr`.
- */
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { NOT_FOUND_META, ROUTES, type RouteMeta } from '../src/lib/routes.ts'
-/*
- * Held in a variable so TypeScript does not try to resolve it: the module is
- * a build artefact, and `bun run typecheck` runs before `vite build --ssr`
- * has produced it. The cast is the contract with src/entry-server.tsx.
- */
const SSR_ENTRY = '../dist-ssr/entry-server.js'
const { render } = (await import(SSR_ENTRY)) as {
render: (location: string) => string
@@ -30,20 +15,12 @@ const SITE_URL = 'https://re-invention.nl/soulseek-rs/'
const escapeAttr = (value: string): string =>
value.replace(/&/g, '&').replace(/"/g, '"').replace(/` matched by attribute and value. */
const setMeta = (html: string, attr: string, name: string, value: string): string =>
html.replace(
new RegExp(`( {
const canonical = meta.path === '/' ? SITE_URL : `${SITE_URL}${meta.path.slice(1)}/`
let out = html.replace(
@@ -84,8 +61,6 @@ for (const route of ROUTES) {
await writePage(template, route, outPath, route.path)
}
-// GitHub Pages serves this whenever no file matches, which after the loop
-// above is only ever a genuinely unknown path.
await writePage(template, NOT_FOUND_META, '404.html', '/__not_found__')
console.log(`done — ${String(ROUTES.length + 1)} pages`)
diff --git a/website/src/App.tsx b/website/src/App.tsx
index 45627ba..d8688a7 100644
--- a/website/src/App.tsx
+++ b/website/src/App.tsx
@@ -9,14 +9,6 @@ import { Home } from '@/pages/home'
import { Install } from '@/pages/install'
import { NotFound } from '@/pages/not-found'
-/*
- * Keyed by RoutePath, so adding an entry to ROUTES without a page here — or a
- * page here that ROUTES does not know about — fails `bun run typecheck`.
- *
- * That drift used to be silent and shipped broken both ways: a prerendered
- * file containing NotFound markup under the new page's title, or a real page
- * with no prerendered file and "not found" in its tab.
- */
const PAGES: Record = {
'/': ,
'/install': ,
@@ -24,11 +16,6 @@ const PAGES: Record = {
'/community': ,
}
-/*
- * Everything is eagerly imported: the whole site is a couple of hundred kB,
- * so route-splitting would trade a network round-trip on every navigation for
- * a bundle saving nobody would notice.
- */
export const App = () => (
}>
diff --git a/website/src/components/brand/logo.tsx b/website/src/components/brand/logo.tsx
index ebfce85..1f414b5 100644
--- a/website/src/components/brand/logo.tsx
+++ b/website/src/components/brand/logo.tsx
@@ -1,23 +1,12 @@
import { cn } from '@/lib/utils'
interface LogoProps {
- /** Height in px. Width follows the artwork's aspect ratio. */
size?: number
className?: string
}
-/** Native aspect ratio of the logo artwork (186.407 × 211). */
const ASPECT = 186.407 / 211
-/*
- * Two assets rather than one recoloured file — the ink and light cuts differ
- * in more than colour — swapped by CSS.
- *
- * Painted as CSS background images rather than image elements: a hidden one
- * is still downloaded, so the two-element version fetched both cuts on every
- * visit (~12-23 kB gzip wasted above the fold). A background on a rule that
- * never paints is never requested.
- */
export const Logo = ({ size = 180, className }: LogoProps) => (
(
export const Footer = () => (
- {/* Gives the three link groups a heading to sit under, so the document
- outline stays nested instead of putting "Packages" on the same
- level as the page's own sections. */}
Site footer
- {/* 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. */}
diff --git a/website/src/components/layout/nav.tsx b/website/src/components/layout/nav.tsx
index 1355d58..3ccb95d 100644
--- a/website/src/components/layout/nav.tsx
+++ b/website/src/components/layout/nav.tsx
@@ -12,16 +12,9 @@ const ITEMS = [
{ label: 'community', to: '/community' },
] as const
-/*
- * No hamburger. Below 820px the links become their own horizontally
- * scrollable row, which keeps every destination one tap away instead of
- * two — the site has four pages, a drawer would be ceremony.
- */
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. */}
, which only works
- * under a data router. Restoring exact scroll offsets on back/forward would
- * mean adopting createBrowserRouter for four static pages.
- */
const useNavigationEffects = () => {
const { pathname, hash } = useLocation()
diff --git a/website/src/components/layout/theme-toggle.tsx b/website/src/components/layout/theme-toggle.tsx
index 53d6d16..60ae677 100644
--- a/website/src/components/layout/theme-toggle.tsx
+++ b/website/src/components/layout/theme-toggle.tsx
@@ -1,11 +1,6 @@
import { useTheme } from '@/theme/use-theme'
import type { Theme } from '@/theme/theme-context'
-/*
- * Cycles system → light → dark → system rather than a plain two-way flip, so
- * "follow the OS" stays reachable after the first click. The glyph shows what
- * is painted; the label says which mode is selected.
- */
const NEXT: Record = {
system: 'light',
light: 'dark',
diff --git a/website/src/components/tui/chrome.tsx b/website/src/components/tui/chrome.tsx
index cb52eec..1cdb090 100644
--- a/website/src/components/tui/chrome.tsx
+++ b/website/src/components/tui/chrome.tsx
@@ -12,10 +12,6 @@ interface TuiPaneProps {
bodyClassName?: string
}
-/**
- * The TUI's bordered pane: a legend that breaks the top border, green when
- * the pane has focus. Mirrors `pane_block()` in the Rust client.
- */
export const TuiPane = ({
title,
num,
@@ -44,7 +40,6 @@ export const TuiPane = ({
)
-/** The macOS window bezel the whole demo sits in. */
export const MacWindow = ({ children }: { children: React.ReactNode }) => (
(
)
-
export interface StatusCounts {
active: number
done: number
diff --git a/website/src/components/tui/data.ts b/website/src/components/tui/data.ts
index b29e85e..737bf00 100644
--- a/website/src/components/tui/data.ts
+++ b/website/src/components/tui/data.ts
@@ -1,11 +1,3 @@
-/*
- * Sample data for the TUI recreation in the hero.
- *
- * Everything here is invented, public-domain, or Creative Commons. No real
- * artist, album, or track names — see TONE-OF-VOICE.md §5 "Banned for legal
- * reasons". Anything added here has to keep that true.
- */
-
export interface SearchRow {
id: string
query: string
@@ -20,9 +12,7 @@ export interface ResultRow {
bitrate: string
speed: string
slots: number
- /** Marked for download with Space, not yet queued. */
marked?: boolean
- /** Handed to the transfer list. */
queued?: boolean
}
@@ -35,7 +25,6 @@ export interface TransferRow {
user: string
progress: string
speed: string
- /** Present only on transfers the demo drives; the static rows leave it out. */
pct?: number
sizeMb?: number
rateMbps?: number
diff --git a/website/src/components/tui/hero-terminal.tsx b/website/src/components/tui/hero-terminal.tsx
index 6db53d5..50ce0a1 100644
--- a/website/src/components/tui/hero-terminal.tsx
+++ b/website/src/components/tui/hero-terminal.tsx
@@ -14,23 +14,13 @@ import { InfoPane, ResultsPane, SearchesPane, TransfersPane } from './panes'
import { useFitScale } from './use-fit-scale'
const DESIGN_WIDTH = 1360
-/*
- * Natural height of the terminal at DESIGN_WIDTH, measured in the browser.
- *
- * The layout is fixed — no content-driven heights — so the wrapper can hold
- * this ratio in CSS from the very first paint. Without it the prerendered
- * hero occupied its full unscaled height, then collapsed to the scaled one
- * once JS measured, shifting everything below it by ~145px.
- */
const DESIGN_HEIGHT = 863
const TICK_MS = 480
const MAX_CONCURRENT = 2
-/** Where the scripted run leaves the cursor. */
const RESTING_ROW = 5
type Phase = 'searching' | 'results' | 'interactive'
-/** Pages are prerendered in Node, where there is no window. */
const isBrowser = typeof window !== 'undefined'
const matches = (query: string): boolean =>
@@ -43,12 +33,6 @@ const sizeMb = (size: string): number => {
return Number.isNaN(parsed) || parsed === 0 ? 12 : parsed
}
-/*
- * Deterministic on purpose: the id is derived from the filename and the rate
- * from its length, so queueing the same row twice is a no-op and nothing
- * random runs inside a state updater. Both matter under StrictMode, which
- * double-invokes updaters in development.
- */
const toTransfer = (row: ResultRow): TransferRow => ({
id: `t:${row.name}`,
status: 'queued',
@@ -61,16 +45,6 @@ const toTransfer = (row: ResultRow): TransferRow => ({
rateMbps: 1.4 + (row.name.length % 7) * 0.4,
})
-/**
- * The hero: a working recreation of the TUI.
- *
- * It plays one scripted run (search → results → queue two files) and then
- * hands over to the keyboard. Only transfers carrying a `pct` are simulated;
- * the seeded rows stay put so the pane looks lived-in from the first frame.
- *
- * Under `prefers-reduced-motion` it starts at the end state instead, so the
- * page never animates at someone who asked it not to.
- */
export const HeroTerminal = () => {
const [searches, setSearches] = useState(SEARCHES)
const [results, setResults] =
@@ -79,23 +53,11 @@ export const HeroTerminal = () => {
const [transfers, setTransfers] = useState(TRANSFERS)
const [focus, setFocus] = useState(2)
- /*
- * 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)
- // Mirrors `results` so queueFrom can read the latest rows without taking a
- // dependency on them. Synced after commit, which is soon enough: every
- // reader is a timer or an event handler, never a render.
const resultsRef = useRef(results)
useEffect(() => {
resultsRef.current = results
@@ -110,7 +72,6 @@ export const HeroTerminal = () => {
return tally
}, [transfers])
- // Derived, not stored: the status bar is a pure function of what is moving.
const pct = useMemo(() => {
const live = transfers.filter(
(t) => t.pct !== undefined && (t.status === 'active' || t.status === 'done'),
@@ -119,15 +80,11 @@ export const HeroTerminal = () => {
return Math.round(live.reduce((sum, t) => sum + (t.pct ?? 0), 0) / live.length)
}, [transfers])
- /** True while any simulated transfer still has somewhere to go. */
const hasWork = useMemo(
() => transfers.some((t) => t.pct !== undefined && t.status !== 'done'),
[transfers],
)
- // Stops once everything has finished. Without the `hasWork` gate the timer
- // would tick twice a second for as long as the tab is open, re-rendering
- // the whole TUI to produce identical output.
useEffect(() => {
if (!hasWork || prefersReducedMotion()) return
@@ -151,8 +108,6 @@ export const HeroTerminal = () => {
}
if (t.status !== 'active') return t
- // Step size varies per file but is a pure function of it, so the
- // updater stays free of side effects under StrictMode's double call.
const step = 4 + ((t.name.length + Math.round(t.pct)) % 9)
const pct = Math.min(100, t.pct + step)
const finished = pct >= 100
@@ -167,9 +122,6 @@ export const HeroTerminal = () => {
}
})
- // map() always allocates, so without this every tick would hand React
- // a fresh array and force a re-render that produces identical output.
- // Returning the original lets React bail out instead.
const moved = next.some((t, i) => t !== list[i])
return moved ? next : list
})
@@ -180,13 +132,6 @@ export const HeroTerminal = () => {
}
}, [hasWork])
- /*
- * Queue rows by index within a named search.
- *
- * Reads the current rows through a ref rather than closing over `results`,
- * so the callback identity never changes — the opening script can then
- * depend on it without restarting itself every time state moves.
- */
const queueFrom = useCallback((searchId: string, indexes: readonly number[]) => {
if (indexes.length === 0) return
@@ -291,17 +236,6 @@ export const HeroTerminal = () => {
}
}
- /*
- * The scripted opening run.
- *
- * No "has this already started" ref: the cleanup cancels every timer, so a
- * StrictMode remount simply replays from the top. A ref guard would let the
- * first cleanup cancel the run and the re-run skip scheduling it, leaving
- * the demo frozen on its first frame in development.
- *
- * It targets 's1' directly rather than whatever is selected, because the
- * script only ever describes the opening state.
- */
useEffect(() => {
const timers: ReturnType[] = []
const at = (fn: () => void, ms: number) => timers.push(setTimeout(fn, ms))
@@ -309,9 +243,6 @@ export const HeroTerminal = () => {
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)
@@ -385,8 +316,6 @@ export const HeroTerminal = () => {
setPhase('interactive')
}
- // No floor: the terminal shrinks to whatever width it is given rather than
- // holding a minimum size and scrolling sideways.
const { wrapRef, innerRef, scale, height, ready } = useFitScale(DESIGN_WIDTH)
const shownSearches = searches.map((search, i) =>
@@ -398,8 +327,6 @@ export const HeroTerminal = () => {
@@ -413,12 +340,6 @@ export const HeroTerminal = () => {
}}
>
- {/*
- role="application" is the ARIA escape hatch for a widget that
- handles its own keys: it stops screen readers intercepting
- j/k/arrows so the demo behaves like the real TUI. Every control
- inside is still a native button or input.
- */}
{
const wrapRef = useRef
(null)
const innerRef = useRef(null)
const [{ scale, height }, setState] = useState({ scale: 0, height: 0 })
useEffect(() => {
- // ResizeObserver is browser-only; prerendering never reaches this effect.
if (typeof ResizeObserver === 'undefined') return
const measure = () => {
@@ -46,8 +37,6 @@ export const useFitScale = (designWidth: number): FitScale => {
measure()
- // Both boxes matter: the wrapper for available width, the inner for the
- // height that width produces once the webfont has swapped in.
const observer = new ResizeObserver(measure)
if (wrapRef.current !== null) observer.observe(wrapRef.current)
if (innerRef.current !== null) observer.observe(innerRef.current)
diff --git a/website/src/components/ui/button.tsx b/website/src/components/ui/button.tsx
index bae2bd6..48e02ef 100644
--- a/website/src/components/ui/button.tsx
+++ b/website/src/components/ui/button.tsx
@@ -25,7 +25,6 @@ const buttonVariants = cva(
type ButtonVariants = VariantProps
interface ButtonProps extends ButtonVariants {
- /** Internal route path, or an absolute URL for an off-site link. */
href: string
children: React.ReactNode
className?: string
@@ -33,17 +32,6 @@ interface ButtonProps extends ButtonVariants {
const isExternal = (href: string): boolean => /^https?:\/\//.test(href)
-/**
- * Every call to action on the site is a link, so this renders an anchor
- * rather than a ``. External links open in a new tab and carry
- * `rel="noopener"`; internal ones route through React Router so navigation
- * stays client-side.
- *
- * The leaving-the-site mark is drawn by the component, never typed into a
- * label. Hand-written "GitHub ↗" put the same glyph in thirteen strings and
- * made it copy — it belongs to the control, so the control renders it, once,
- * and only when the link actually leaves.
- */
export const Button = ({ href, variant, size, children, className }: ButtonProps) => {
const classes = cn(buttonVariants({ variant, size }), className)
diff --git a/website/src/components/ui/command-pill.tsx b/website/src/components/ui/command-pill.tsx
index e6943c3..65a0f05 100644
--- a/website/src/components/ui/command-pill.tsx
+++ b/website/src/components/ui/command-pill.tsx
@@ -6,7 +6,6 @@ interface CommandPillProps {
className?: string
}
-/** The one-line install command, click-to-copy. */
export const CommandPill = ({ text, className }: CommandPillProps) => {
const { copied, copy } = useCopy()
diff --git a/website/src/components/ui/copy-button.tsx b/website/src/components/ui/copy-button.tsx
index 7ccb831..0d6bbaf 100644
--- a/website/src/components/ui/copy-button.tsx
+++ b/website/src/components/ui/copy-button.tsx
@@ -6,8 +6,6 @@ interface CopyButtonProps {
className?: string
}
-/** The copy control in a terminal header. Styled against the terminal
- * surface tokens, not the page ones, so it reads as part of the window. */
export const CopyButton = ({ text, className }: CopyButtonProps) => {
const { copied, copy } = useCopy()
diff --git a/website/src/components/ui/feature-card.tsx b/website/src/components/ui/feature-card.tsx
index da09d90..b497b5b 100644
--- a/website/src/components/ui/feature-card.tsx
+++ b/website/src/components/ui/feature-card.tsx
@@ -37,9 +37,7 @@ const FeatureCommand = ({ text }: FeatureCommandProps) => {
}
interface FeatureCardProps {
- /** A single glyph. Decorative — the title carries the meaning. */
glyph?: string
- /** A CSS colour for the glyph, from the semantic token set. */
color?: string
title: string
children: React.ReactNode
@@ -61,9 +59,6 @@ export const FeatureCard = ({
className,
)}
>
- {/* The glyph sits in the line like a prompt marker, not in a chip. A
- tinted rounded square around an icon is the stock feature-card
- treatment; here the terminal already gives us a better idiom. */}
{glyph !== undefined && (
(
(
{children}
@@ -14,9 +13,7 @@ export const Wrap = ({ children, className }: WrapProps) => (
interface SectionProps {
children: React.ReactNode
- /** Drops the top hairline — for the first section under the nav. */
flush?: boolean
- /** Panel background plus rules top and bottom. */
band?: boolean
id?: string
className?: string
@@ -83,7 +80,6 @@ interface PageHeadProps {
children: React.ReactNode
}
-/** The h1 block every subpage opens with. */
export const PageHead = ({ eyebrow, title, children }: PageHeadProps) => (
@@ -98,13 +94,11 @@ export const PageHead = ({ eyebrow, title, children }: PageHeadProps) => (
interface ColsProps {
children: React.ReactNode
- /** Aligns columns to their tops instead of stretching. */
start?: boolean
center?: boolean
className?: string
}
-/** The site's only multi-column layout: two up on desktop, stacked below. */
export const Cols = ({ children, start, center, className }: ColsProps) => (
(
(
(
(
)}
>
- {/* --color-info, not the raw --color-signal: this panel renders on the
- page, outside .tui-content, where the raw palette never re-cuts. */}
{num !== undefined && [{num}] }
{title}
@@ -37,17 +29,6 @@ interface CalloutProps {
className?: string
}
-/**
- * An aside. `warn` carries the tape amber.
- *
- * 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) => (
= {
interface TerminalProps {
label?: string
lines: readonly TermLine[]
- /** Overrides what the copy button yields. Defaults to the commands only. */
copy?: string
className?: string
}
-/**
- * A terminal window. Paints from the `--tm-*` token set rather than the page
- * tokens, so it stays a dark terminal in light mode instead of dissolving
- * into the page.
- */
export const Terminal = ({ label, lines, copy, className }: TerminalProps) => {
const copyText =
copy ??
@@ -53,7 +46,6 @@ export const Terminal = ({ label, lines, copy, className }: TerminalProps) => {
{lines.map((line, i) => (
diff --git a/website/src/entry-server.tsx b/website/src/entry-server.tsx
index 8dfe96f..176a736 100644
--- a/website/src/entry-server.tsx
+++ b/website/src/entry-server.tsx
@@ -5,12 +5,6 @@ import { StaticRouter } from 'react-router'
import { App } from '@/App'
import { ThemeProvider } from '@/theme/theme-provider'
-/**
- * Renders one route to a static HTML string.
- *
- * `location` is the app-relative path (no base prefix) — the client mounts
- * with `basename`, so the two see the same paths and hydration matches.
- */
export const render = (location: string): string =>
renderToString(
diff --git a/website/src/hooks/use-copy.ts b/website/src/hooks/use-copy.ts
index 3f46b01..00c7f9c 100644
--- a/website/src/hooks/use-copy.ts
+++ b/website/src/hooks/use-copy.ts
@@ -2,14 +2,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'
const RESET_MS = 1400
-/**
- * Copy-to-clipboard with a self-clearing "copied" flag.
- *
- * no execCommand fallback. `navigator.clipboard` is available in
- * every secure context, and the site only ever ships over HTTPS (GitHub
- * Pages) or localhost. If a copy does fail, the flag stays off and the text
- * is still selectable by hand.
- */
export const useCopy = (): { copied: boolean; copy: (text: string) => void } => {
const [copied, setCopied] = useState(false)
const timer = useRef | null>(null)
@@ -31,7 +23,7 @@ export const useCopy = (): { copied: boolean; copy: (text: string) => void } =>
}, RESET_MS)
},
() => {
- // Permission denied or no clipboard: leave the button untouched.
+ setCopied(false)
},
)
}, [])
diff --git a/website/src/hooks/use-page-meta.ts b/website/src/hooks/use-page-meta.ts
index 290deed..5d06c10 100644
--- a/website/src/hooks/use-page-meta.ts
+++ b/website/src/hooks/use-page-meta.ts
@@ -3,13 +3,6 @@ import { useLocation } from 'react-router'
import { metaFor } from '@/lib/routes'
-/**
- * Keeps the title and meta description in step with the current route.
- *
- * Each route is also prerendered with its tags already in the HTML, so
- * crawlers never depend on this running; it only covers client-side
- * navigation after the first paint.
- */
export const usePageMeta = (): void => {
const { pathname } = useLocation()
diff --git a/website/src/index.css b/website/src/index.css
index 8227195..8c0aa98 100644
--- a/website/src/index.css
+++ b/website/src/index.css
@@ -1,39 +1,25 @@
@import 'tailwindcss';
-/*
- * soulseek-rs — design tokens.
- *
- * Ported verbatim from the design system's tokens/{colors,typography,spacing}.css.
- * The same values back the TUI in soulseek-rs/src/ui/styles.rs, so the terminal
- * and the site stay one product. Dark is the native theme; light re-cuts the
- * semantic hues ~10% darker because phosphor and tape read on vinyl but wash
- * out on manila.
- *
- * Theme switching is driven by [data-theme] on , matching the design's
- * own selector, rather than Tailwind's default `.dark` class.
- */
@custom-variant light (&:where([data-theme='light'], [data-theme='light'] *));
@theme {
- /* ---- Raw palette (dark-native names) ---- */
- --color-sleeve: #14110f; /* warm off-black — cardboard shadow, never #000 */
- --color-vinyl: #1e1a17; /* panels / surfaces, one step up from base */
- --color-vinyl-2: #26211d; /* raised surface / hover on panels */
- --color-dust: #8a8178; /* muted warm grey — secondary text, borders */
- --color-paper: #e8e1d6; /* aged insert card — primary text, never #FFF */
+ --color-sleeve: #14110f;
+ --color-vinyl: #1e1a17;
+ --color-vinyl-2: #26211d;
+ --color-dust: #8a8178;
+ --color-paper: #e8e1d6;
--color-paper-dim: #c9c1b4;
- --color-oxide: #c1502e; /* iron-oxide accent, deeper than Rust's own #CE422B */
- --color-tape: #d8a657; /* cassette-shell amber — warnings, queued */
- --color-phosphor: #7fb685; /* desaturated LCD green — success, connected */
- --color-signal: #6e9dc9; /* softened windows-classic blue — links, info */
- --color-alarm: #b33a3a; /* error red, distinguishable from oxide */
+ --color-oxide: #c1502e;
+ --color-tape: #d8a657;
+ --color-phosphor: #7fb685;
+ --color-signal: #6e9dc9;
+ --color-alarm: #b33a3a;
--color-manila: #f2ede4;
--color-manila-2: #e8e1d6;
--color-manila-3: #ded5c7;
- /* ---- Semantic aliases, wired to the runtime vars below ---- */
--color-base: var(--bg-base);
--color-panel: var(--bg-panel);
--color-raised: var(--bg-raised);
@@ -46,13 +32,6 @@
--color-line: var(--border-default);
--color-line-strong: var(--border-strong);
- /*
- * --accent fills; --accent-text writes.
- *
- * The oxide accent is a mid-tone: it clears 4.5:1 as a background behind
- * white, but not as 11px text on vinyl. Splitting the two uses lets the
- * fill keep the brand hue while the text cut earns its contrast.
- */
--color-accent: var(--accent);
--color-accent-text: var(--accent-text);
--color-accent-hover: var(--accent-hover);
@@ -64,13 +43,11 @@
--color-link: var(--link);
--color-link-hover: var(--link-hover);
- /* ---- Typography: monospace, non-negotiable. It's a terminal application. ---- */
--font-mono:
'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
- /* the "earned wink" — the era's forum font. One surface only. */
+
--font-forum: Verdana, Tahoma, sans-serif;
- /* Scale, from the brandbook specimen (48/56, 32/40, 20/28, 14/22, 12/18) */
--text-display: 48px;
--text-display--line-height: 56px;
--text-title: 32px;
@@ -100,14 +77,12 @@
}
}
-/* ---- DARK is the default theme ---- */
:root {
--bg-base: var(--color-sleeve);
--bg-panel: var(--color-vinyl);
--bg-raised: var(--color-vinyl-2);
--bg-selection: rgb(110 157 201 / 0.22);
- /* Brightened from the raw palette for web body contrast — still warm. */
--text-primary: #ffffff;
--text-secondary: #e4dfd8;
--text-muted: #bdb4a9;
@@ -117,7 +92,7 @@
--border-strong: var(--color-dust);
--accent: var(--color-oxide);
- /* Raised off #c1502e, which reads 3.99:1 as small text on vinyl. */
+
--accent-text: #cd7155;
--accent-hover: #d05f3c;
@@ -128,7 +103,6 @@
--link: var(--color-signal);
--link-hover: #8bb4dc;
- /* Terminal surface — a real terminal, darker than the page around it. */
--tm-bg: #1e1a17;
--tm-hd: #181513;
--tm-fg: #ffffff;
@@ -146,19 +120,12 @@
color-scheme: dark;
}
-/* ---- LIGHT: base becomes manila, sleeve becomes ink, accents darken ~10% ---- */
[data-theme='light'] {
--bg-base: var(--color-manila);
--bg-panel: var(--color-manila-2);
--bg-raised: var(--color-manila-3);
--bg-selection: rgb(76 127 174 / 0.2);
- /*
- * Every value below clears 4.5:1 against --bg-raised, the darkest of the
- * three light surfaces, so each one holds on base, panel and raised alike.
- * The design's original cuts were tuned against the base only, which left
- * links at 3.64:1 and the success green at 2.86:1.
- */
--text-primary: var(--color-sleeve);
--text-secondary: #574e45;
--text-muted: #645b52;
@@ -195,11 +162,6 @@
color-scheme: light;
}
-/*
- * Only the cut that is actually shown gets fetched; a hidden image element
- * would still be downloaded. url() is relative to this stylesheet, which
- * Vite rebases against `base`, so the deploy path stays in vite.config.ts.
- */
.logo-mark {
background-image: url('/logo-ink.svg');
}
@@ -208,27 +170,17 @@
background-image: url('/logo-light.svg');
}
-/*
- * The TUI panes paint from the raw palette (paper, dust, oxide…) because that
- * is what the Rust client does. Those names do not flip with the theme, so
- * each theme re-cuts them, scoped to the terminal's content.
- *
- * Dark brightens two of them the way a real emulator brightens its palette
- * against black: #b33a3a reads 3.21:1 on vinyl and #c1502e 3.99:1 — fine for
- * a glyph, not for the "Failed" and progress text beside it.
- */
.tui-content {
--color-alarm: #c26060;
--color-oxide: #c65f40;
}
-/* Light would otherwise leave paper-on-paper: the whole set re-cuts dark. */
[data-theme='light'] .tui-content {
--color-paper: #14110f;
--color-paper-dim: #3a332c;
--color-dust: #645b52;
--color-oxide: #9e3f22;
- --color-tape: #8d621f; /* 4.62:1 on manila; #8f6420 sat at 4.49 */
+ --color-tape: #8d621f;
--color-phosphor: #3f7147;
--color-signal: #35648f;
--color-alarm: #8f2626;
@@ -237,7 +189,7 @@
@layer base {
html {
scroll-behavior: smooth;
- /* Anchored headings clear the sticky nav instead of hiding under it. */
+
scroll-padding-top: 5rem;
-webkit-text-size-adjust: 100%;
}
@@ -248,7 +200,7 @@
font-family: var(--font-mono);
font-size: var(--text-body);
line-height: var(--text-body--line-height);
- /* Tabular figures EVERYWHERE — speeds, sizes, queue positions, ETAs. */
+
font-variant-numeric: tabular-nums;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
@@ -258,25 +210,11 @@
background: var(--bg-selection);
}
- /* The design system's own focus treatment, applied once globally. */
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
- /*
- * Reduced motion, scoped rather than nuked.
- *
- * A blanket `*{animation-duration:0.01ms}` also kills the transitions that
- * carry meaning — a button acknowledging a copy, a card answering a hover.
- * Those are state changes a reduced-motion reader still wants; what they
- * asked not to see is travel and looping.
- *
- * So: stop the looping cursor and smooth scrolling, cap the rest at a
- * duration short enough to read as instant feedback rather than movement.
- * The TUI goes further and skips its opening run entirely — see
- * prefersReducedMotion() in components/tui/hero-terminal.tsx.
- */
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
@@ -289,7 +227,6 @@
transition-duration: 80ms !important;
}
- /* Looping or travelling motion has no reduced-motion equivalent. */
[class*='animate-'] {
animation: none !important;
}
diff --git a/website/src/lib/links.ts b/website/src/lib/links.ts
index 9bc3cb5..fb9d282 100644
--- a/website/src/lib/links.ts
+++ b/website/src/lib/links.ts
@@ -1,4 +1,3 @@
-/** Every off-site destination, in one place. */
export const LINKS = {
gh: 'https://github.com/michel/soulseek-rs',
issues: 'https://github.com/michel/soulseek-rs/issues',
@@ -15,8 +14,6 @@ export const LINKS = {
slsknet: 'https://www.slsknet.org',
} as const
-/** Injected from the workspace Cargo.toml at build time — see vite.config.ts. */
export const VERSION = __APP_VERSION__
-/** Where the site is served from. Used for canonical and og:url. */
export const SITE_URL = 'https://re-invention.nl/soulseek-rs/'
diff --git a/website/src/lib/routes.ts b/website/src/lib/routes.ts
index 33af80e..e42a629 100644
--- a/website/src/lib/routes.ts
+++ b/website/src/lib/routes.ts
@@ -1,23 +1,9 @@
export interface RouteMeta {
- /** Path without the base prefix, always leading-slashed. */
path: string
title: string
description: string
}
-/**
- * The single source of truth for what pages exist.
- *
- * Read three times: React Router matches against it, the layout applies the
- * meta on navigation, and scripts/prerender.ts walks it to decide which HTML
- * files to write. Adding a page means adding one entry here plus its route.
- */
-/*
- * `as const satisfies` rather than a plain annotation: the annotation widened
- * `path` to `string`, which is what let App.tsx's hand-written route list
- * drift from this one without failing the build. Literal paths make the
- * mapping in App.tsx exhaustive-checkable.
- */
export const ROUTES = [
{
path: '/',
@@ -45,7 +31,6 @@ export const ROUTES = [
},
] as const satisfies readonly RouteMeta[]
-/** Every path the site serves, as literals. */
export type RoutePath = (typeof ROUTES)[number]['path']
export const NOT_FOUND_META: RouteMeta = {
diff --git a/website/src/main.tsx b/website/src/main.tsx
index d969ab0..e4f1c77 100644
--- a/website/src/main.tsx
+++ b/website/src/main.tsx
@@ -13,8 +13,6 @@ if (container === null) throw new Error('Root element #root is missing')
const tree = (
- {/* Must match `base` in vite.config.ts — the site is served from a
- subpath, so the router has to strip it before matching. */}
@@ -22,8 +20,6 @@ const tree = (
)
-// Every route ships prerendered markup, so the normal path is to hydrate it.
-// createRoot is the fallback for a container that somehow arrived empty.
if (container.hasChildNodes()) {
hydrateRoot(container, tree)
} else {
diff --git a/website/src/pages/community.tsx b/website/src/pages/community.tsx
index c9b6aa6..831b9c3 100644
--- a/website/src/pages/community.tsx
+++ b/website/src/pages/community.tsx
@@ -251,9 +251,6 @@ 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.
diff --git a/website/src/pages/docs.tsx b/website/src/pages/docs.tsx
index 9174980..65cd3b8 100644
--- a/website/src/pages/docs.tsx
+++ b/website/src/pages/docs.tsx
@@ -59,14 +59,6 @@ const PANES: readonly { num?: number; title: string; body: string }[] = [
},
]
-/*
- * 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 }) => (
@@ -204,13 +196,6 @@ 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/home.tsx b/website/src/pages/home.tsx
index 51be5ce..5a05030 100644
--- a/website/src/pages/home.tsx
+++ b/website/src/pages/home.tsx
@@ -104,9 +104,6 @@ export const Home = () => {
- {/* 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. */}
Soulseek client · terminal · Rust
@@ -139,11 +136,6 @@ export const Home = () => {
- {/*
- 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.
- */}