feat(website): add the project site and deploy it to GitHub Pages
A Vite + React + TypeScript + Tailwind v4 site under website/, built from the soulseek-rs design system so the site and the TUI stay one product. The colour tokens in src/index.css are the same values as soulseek-rs/src/ui/styles.rs; change one, change both. The hero is a working recreation of the TUI: it plays one scripted run and then takes the keyboard, or lands on the resting state directly under prefers-reduced-motion. Its sample data is invented or public domain, per the tone-of-voice guardrail against naming copyrighted material. Static output rather than an SPA shell. `bun run build` compiles the client, compiles the same app for Node, then renders every route in src/lib/routes.ts to its own file with that route's title, description and canonical URL. A direct link to /install therefore answers 200 with real markup instead of GitHub's 404 page. routes.ts is keyed into App.tsx by literal path, so adding one without the other fails typecheck rather than shipping a page whose body says "not found" under the right title. Theme is resolved once by an inline script before first paint and adopted by React through useSyncExternalStore, so there is no flash and no hydration mismatch on the toggle. Light mode re-cuts the semantic hues darker: phosphor and tape read on vinyl but wash out on manila. CI and release skip website-only pushes, and Pages skips Rust-only ones. The two Rust workflows share one paths-ignore list so they can only skip together, which keeps the release ci-gate from polling for a run that was never queued. That gate also now treats a cancelled CI as "keep waiting" rather than a failure, since cancel-in-progress cancels the older SHA on every back-to-back push.
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
@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 <html>, 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-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-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);
|
||||
|
||||
--color-primary: var(--text-primary);
|
||||
--color-secondary: var(--text-secondary);
|
||||
--color-muted: var(--text-muted);
|
||||
--color-inverse: var(--text-inverse);
|
||||
|
||||
--color-hairline: var(--border-hairline);
|
||||
--color-line: var(--border-default);
|
||||
--color-line-strong: var(--border-strong);
|
||||
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-hover: var(--accent-hover);
|
||||
--color-accent-active: var(--accent-active);
|
||||
|
||||
--color-success: var(--status-success);
|
||||
--color-warning: var(--status-warning);
|
||||
--color-info: var(--status-info);
|
||||
--color-error: var(--status-error);
|
||||
|
||||
--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;
|
||||
--text-title--line-height: 40px;
|
||||
--text-heading: 20px;
|
||||
--text-heading--line-height: 28px;
|
||||
--text-body: 14px;
|
||||
--text-body--line-height: 22px;
|
||||
--text-caption: 12px;
|
||||
--text-caption--line-height: 18px;
|
||||
--text-label: 11px;
|
||||
--text-label--line-height: 14px;
|
||||
|
||||
--tracking-label: 0.06em;
|
||||
|
||||
/* Radii — favicon 16, panel card 8, cursor 3 */
|
||||
--radius-sm: 3px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 16px;
|
||||
|
||||
/* Restrained. A desk, not a dashboard. */
|
||||
--shadow-panel:
|
||||
0 1px 0 rgb(0 0 0 / 0.35), 0 8px 24px rgb(0 0 0 / 0.35);
|
||||
|
||||
--animate-cursor-blink: ss-cursor-blink 1.06s steps(1) infinite;
|
||||
|
||||
@keyframes ss-cursor-blink {
|
||||
0%,
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
50.01%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 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;
|
||||
--text-inverse: var(--color-sleeve);
|
||||
|
||||
--border-hairline: rgb(138 129 120 / 0.35);
|
||||
--border-default: rgb(138 129 120 / 0.7);
|
||||
--border-strong: var(--color-dust);
|
||||
|
||||
--accent: var(--color-oxide);
|
||||
--accent-hover: #d05f3c;
|
||||
--accent-active: #a8431f;
|
||||
|
||||
--status-warning: var(--color-tape);
|
||||
--status-success: var(--color-phosphor);
|
||||
--status-info: var(--color-signal);
|
||||
--status-error: var(--color-alarm);
|
||||
|
||||
--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;
|
||||
--tm-dim: #bdb4a9;
|
||||
--tm-bd: rgb(138 129 120 / 0.32);
|
||||
--tm-bd2: rgb(138 129 120 / 0.22);
|
||||
--tm-bd3: rgb(138 129 120 / 0.6);
|
||||
--tm-pr: #c1502e;
|
||||
--tm-cm: #aba298;
|
||||
--tm-ok: #7fb685;
|
||||
--tm-fl: #d8a657;
|
||||
--tm-sg: #6e9dc9;
|
||||
--cmd-bg: #0e0c0a;
|
||||
--tui-track: #2a2622;
|
||||
--tui-bezel: #000000;
|
||||
|
||||
/* tmux's own status bar. Emulator chrome, so it does not follow the theme. */
|
||||
--tmux-bg: #0e0c0a;
|
||||
--tmux-hl: #b9a6e8;
|
||||
--tmux-hl-fg: #1a1526;
|
||||
|
||||
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);
|
||||
|
||||
--text-primary: var(--color-sleeve);
|
||||
--text-secondary: #574e45;
|
||||
--text-muted: #6f655b;
|
||||
--text-inverse: var(--color-paper);
|
||||
|
||||
--border-hairline: rgb(20 17 15 / 0.15);
|
||||
--border-default: rgb(20 17 15 / 0.28);
|
||||
--border-strong: #6b6259;
|
||||
|
||||
--accent: #ae4728;
|
||||
--accent-hover: #c1502e;
|
||||
--accent-active: #953b1f;
|
||||
|
||||
--status-warning: #c08e3d;
|
||||
--status-success: #5e9a66;
|
||||
--status-info: #4c7fae;
|
||||
--status-error: #9a2f2f;
|
||||
|
||||
--link: #4c7fae;
|
||||
--link-hover: #3b6a95;
|
||||
|
||||
--tm-bg: #e9e2d7;
|
||||
--tm-hd: #dcd3c4;
|
||||
--tm-fg: #14110f;
|
||||
--tm-dim: #574e45;
|
||||
--tm-bd: rgb(20 17 15 / 0.2);
|
||||
--tm-bd2: rgb(20 17 15 / 0.13);
|
||||
--tm-bd3: rgb(20 17 15 / 0.42);
|
||||
--tm-pr: #9e3f22;
|
||||
--tm-cm: #6b6259;
|
||||
--tm-ok: #3f7147;
|
||||
--tm-fl: #8f6420;
|
||||
--tm-sg: #35648f;
|
||||
--cmd-bg: #dcd3c4;
|
||||
--tui-track: #cfc5b5;
|
||||
--tui-bezel: rgb(20 17 15 / 0.28);
|
||||
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only the cut that is actually shown gets fetched — a hidden <img> 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');
|
||||
}
|
||||
|
||||
[data-theme='light'] .logo-mark {
|
||||
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 on
|
||||
* manila they would leave paper-on-paper. Light mode re-cuts the same names
|
||||
* darker, scoped to the terminal's content.
|
||||
*
|
||||
* Deliberately not applied to the tmux bar above it: that is the emulator's
|
||||
* own chrome, it keeps its near-black background in both themes, and needs
|
||||
* the bright phosphor to stay legible against it.
|
||||
*/
|
||||
[data-theme='light'] .tui-content {
|
||||
--color-paper: #14110f;
|
||||
--color-paper-dim: #3a332c;
|
||||
--color-dust: #645b52;
|
||||
--color-oxide: #9e3f22;
|
||||
--color-tape: #8f6420;
|
||||
--color-phosphor: #3f7147;
|
||||
--color-signal: #35648f;
|
||||
--color-alarm: #8f2626;
|
||||
}
|
||||
|
||||
@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%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg-base);
|
||||
color: var(--text-primary);
|
||||
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;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--bg-selection);
|
||||
}
|
||||
|
||||
/* The design system's own focus treatment, applied once globally. */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user