fix(website): clear WCAG AA across both themes, and stop the hero shifting
Contrast was measured, not eyeballed: 41 foreground/background pairs across both themes and the terminal surface, each against the darkest (light) or lightest (dark) surface it actually sits on. Five failed. Links on manila read 3.64:1 and the footer's "No telemetry. Ever." — a claim the site goes out of its way to make — sat at 2.86:1. The accent needed splitting rather than moving. #c1502e clears 4.5:1 as a button fill behind white but only 3.99:1 as an 11px eyebrow on vinyl, so --accent keeps the brand hue for fills and a new --accent-text carries the readable cut. The terminal gets the same treatment dark emulators give their palettes: alarm and oxide brighten inside .tui-content so "Failed" is legible next to its glyph. Reduced motion no longer nukes everything. A blanket 0.01ms also kills the transitions that answer a click, which is feedback rather than movement; looping and travelling animation stops, the rest caps at 80ms. The TUI already skipped its opening run and still does. The hero reserves its box with aspect-ratio from the first paint. It was prerendered at full unscaled height and collapsed once JS measured, moving everything below it by ~145px. Measured shift is now 0. Also: the webfont loads non-blocking (display=swap meant the first frame never waited for it anyway), footer link groups nest under a heading instead of sitting level with page sections, and eight tokens nothing referenced stop shipping.
This commit is contained in:
+14
-1
@@ -28,13 +28,26 @@
|
|||||||
<meta name="twitter:card" content="summary" />
|
<meta name="twitter:card" content="summary" />
|
||||||
|
|
||||||
<!-- IBM Plex Mono is the brand typeface. CDN rather than self-hosted
|
<!-- IBM Plex Mono is the brand typeface. CDN rather than self-hosted
|
||||||
binaries, matching the design system's tokens/fonts.css. OFL 1.1. -->
|
binaries, matching the design system's tokens/fonts.css. OFL 1.1.
|
||||||
|
|
||||||
|
Loaded non-blocking: as a plain stylesheet it sat on the critical
|
||||||
|
path, so first paint waited on a third-party DNS + TLS + fetch. It
|
||||||
|
buys nothing there — `display=swap` plus the ui-monospace fallback
|
||||||
|
means the first frame renders in the fallback either way. -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
rel="preload"
|
||||||
|
as="style"
|
||||||
|
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;700&display=swap"
|
||||||
|
onload="this.onload=null;this.rel='stylesheet'"
|
||||||
|
/>
|
||||||
|
<noscript>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;700&display=swap"
|
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;700&display=swap"
|
||||||
/>
|
/>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Runs before first paint so the right theme is already on <html>.
|
// Runs before first paint so the right theme is already on <html>.
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ const ASPECT = 186.407 / 211
|
|||||||
* Two assets rather than one recoloured file — the ink and light cuts differ
|
* Two assets rather than one recoloured file — the ink and light cuts differ
|
||||||
* in more than colour — swapped by CSS.
|
* in more than colour — swapped by CSS.
|
||||||
*
|
*
|
||||||
* Background images, not <img>: a hidden <img> is still downloaded, so the
|
* Painted as CSS background images rather than image elements: a hidden one
|
||||||
* two-element version fetched both cuts on every visit (~12-23 kB gzip wasted
|
* is still downloaded, so the two-element version fetched both cuts on every
|
||||||
* above the fold). A background on a non-painted rule is never requested.
|
* 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 Logo = ({ size = 180, className }: LogoProps) => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ const FooterAnchor = ({ label, href }: FooterLink) =>
|
|||||||
export const Footer = () => (
|
export const Footer = () => (
|
||||||
<footer className="border-t border-hairline bg-base pt-7 pb-6 sm:pt-11 sm:pb-10">
|
<footer className="border-t border-hairline bg-base pt-7 pb-6 sm:pt-11 sm:pb-10">
|
||||||
<Wrap>
|
<Wrap>
|
||||||
|
{/* 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. */}
|
||||||
|
<h2 className="sr-only">Site footer</h2>
|
||||||
{/* Two columns even on the narrowest phone, with the brand spanning
|
{/* Two columns even on the narrowest phone, with the brand spanning
|
||||||
both — one column per link group makes the footer taller than the
|
both — one column per link group makes the footer taller than the
|
||||||
page it closes. */}
|
page it closes. */}
|
||||||
@@ -77,9 +81,9 @@ export const Footer = () => (
|
|||||||
|
|
||||||
{COLUMNS.map((column) => (
|
{COLUMNS.map((column) => (
|
||||||
<div key={column.heading}>
|
<div key={column.heading}>
|
||||||
<h2 className="mb-2 text-label uppercase tracking-[var(--tracking-label)] text-secondary sm:mb-3.5">
|
<h3 className="mb-2 text-label uppercase tracking-[var(--tracking-label)] text-secondary sm:mb-3.5">
|
||||||
{column.heading}
|
{column.heading}
|
||||||
</h2>
|
</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{column.links.map((link) => (
|
{column.links.map((link) => (
|
||||||
<li key={link.label} className="mb-1.5 sm:mb-2.5">
|
<li key={link.label} className="mb-1.5 sm:mb-2.5">
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const Nav = () => (
|
|||||||
cn(
|
cn(
|
||||||
'relative inline-flex shrink-0 items-center rounded-md px-3.5 py-1.5 text-label uppercase tracking-[var(--tracking-label)] max-sm:min-h-11 sm:px-3',
|
'relative inline-flex shrink-0 items-center rounded-md px-3.5 py-1.5 text-label uppercase tracking-[var(--tracking-label)] max-sm:min-h-11 sm:px-3',
|
||||||
isActive
|
isActive
|
||||||
? "bg-raised text-primary before:text-accent before:content-['›_'] after:absolute after:inset-x-3 after:bottom-px after:h-0.5 after:bg-accent"
|
? "bg-raised text-primary before:text-accent-text before:content-['›_'] after:absolute after:inset-x-3 after:bottom-px after:h-0.5 after:bg-accent"
|
||||||
: 'text-secondary hover:bg-[var(--bg-selection)] hover:text-primary',
|
: 'text-secondary hover:bg-[var(--bg-selection)] hover:text-primary',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,15 @@ import { InfoPane, ResultsPane, SearchesPane, TransfersPane } from './panes'
|
|||||||
import { useFitScale } from './use-fit-scale'
|
import { useFitScale } from './use-fit-scale'
|
||||||
|
|
||||||
const DESIGN_WIDTH = 1360
|
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 TICK_MS = 480
|
||||||
const MAX_CONCURRENT = 2
|
const MAX_CONCURRENT = 2
|
||||||
/** Where the scripted run leaves the cursor. */
|
/** Where the scripted run leaves the cursor. */
|
||||||
@@ -374,8 +383,10 @@ export const HeroTerminal = () => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={wrapRef}
|
ref={wrapRef}
|
||||||
className="w-full overflow-x-auto overflow-y-hidden"
|
className="w-full overflow-hidden"
|
||||||
style={ready ? { height } : undefined}
|
// aspect-ratio reserves the right box before JS runs; once measured the
|
||||||
|
// explicit height takes over and the two agree, so nothing moves.
|
||||||
|
style={ready ? { height } : { aspectRatio: `${String(DESIGN_WIDTH)} / ${String(DESIGN_HEIGHT)}` }}
|
||||||
>
|
>
|
||||||
<div style={ready ? { width: DESIGN_WIDTH * scale, height } : undefined}>
|
<div style={ready ? { width: DESIGN_WIDTH * scale, height } : undefined}>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const CommandPill = ({ text, className }: CommandPillProps) => {
|
|||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span aria-hidden="true" className="text-accent">
|
<span aria-hidden="true" className="text-accent-text">
|
||||||
$
|
$
|
||||||
</span>
|
</span>
|
||||||
<code className="font-mono">{text}</code>
|
<code className="font-mono">{text}</code>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ interface EyebrowProps {
|
|||||||
export const Eyebrow = ({ children, className }: EyebrowProps) => (
|
export const Eyebrow = ({ children, className }: EyebrowProps) => (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-label uppercase tracking-[var(--tracking-label)] text-accent',
|
'text-label uppercase tracking-[var(--tracking-label)] text-accent-text',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const OsIcon = ({ name, className }: OsIconProps) => (
|
|||||||
role="img"
|
role="img"
|
||||||
aria-label={name}
|
aria-label={name}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block size-[22px] shrink-0 text-primary transition-colors group-hover:text-accent',
|
'block size-[22px] shrink-0 text-primary transition-colors group-hover:text-accent-text',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const Callout = ({ title, tone = 'accent', children, className }: Callout
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'mb-2 text-label uppercase tracking-[var(--tracking-label)]',
|
'mb-2 text-label uppercase tracking-[var(--tracking-label)]',
|
||||||
tone === 'warn' ? 'text-warning' : 'text-accent',
|
tone === 'warn' ? 'text-warning' : 'text-accent-text',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
|
|||||||
+58
-37
@@ -41,20 +41,25 @@
|
|||||||
--color-primary: var(--text-primary);
|
--color-primary: var(--text-primary);
|
||||||
--color-secondary: var(--text-secondary);
|
--color-secondary: var(--text-secondary);
|
||||||
--color-muted: var(--text-muted);
|
--color-muted: var(--text-muted);
|
||||||
--color-inverse: var(--text-inverse);
|
|
||||||
|
|
||||||
--color-hairline: var(--border-hairline);
|
--color-hairline: var(--border-hairline);
|
||||||
--color-line: var(--border-default);
|
--color-line: var(--border-default);
|
||||||
--color-line-strong: var(--border-strong);
|
--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: var(--accent);
|
||||||
|
--color-accent-text: var(--accent-text);
|
||||||
--color-accent-hover: var(--accent-hover);
|
--color-accent-hover: var(--accent-hover);
|
||||||
--color-accent-active: var(--accent-active);
|
|
||||||
|
|
||||||
--color-success: var(--status-success);
|
--color-success: var(--status-success);
|
||||||
--color-warning: var(--status-warning);
|
--color-warning: var(--status-warning);
|
||||||
--color-info: var(--status-info);
|
--color-info: var(--status-info);
|
||||||
--color-error: var(--status-error);
|
|
||||||
|
|
||||||
--color-link: var(--link);
|
--color-link: var(--link);
|
||||||
--color-link-hover: var(--link-hover);
|
--color-link-hover: var(--link-hover);
|
||||||
@@ -74,21 +79,12 @@
|
|||||||
--text-heading--line-height: 28px;
|
--text-heading--line-height: 28px;
|
||||||
--text-body: 14px;
|
--text-body: 14px;
|
||||||
--text-body--line-height: 22px;
|
--text-body--line-height: 22px;
|
||||||
--text-caption: 12px;
|
|
||||||
--text-caption--line-height: 18px;
|
|
||||||
--text-label: 11px;
|
--text-label: 11px;
|
||||||
--text-label--line-height: 14px;
|
--text-label--line-height: 14px;
|
||||||
|
|
||||||
--tracking-label: 0.06em;
|
--tracking-label: 0.06em;
|
||||||
|
|
||||||
/* Radii — favicon 16, panel card 8, cursor 3 */
|
|
||||||
--radius-sm: 3px;
|
|
||||||
--radius-md: 8px;
|
--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;
|
--animate-cursor-blink: ss-cursor-blink 1.06s steps(1) infinite;
|
||||||
|
|
||||||
@@ -115,20 +111,19 @@
|
|||||||
--text-primary: #ffffff;
|
--text-primary: #ffffff;
|
||||||
--text-secondary: #e4dfd8;
|
--text-secondary: #e4dfd8;
|
||||||
--text-muted: #bdb4a9;
|
--text-muted: #bdb4a9;
|
||||||
--text-inverse: var(--color-sleeve);
|
|
||||||
|
|
||||||
--border-hairline: rgb(138 129 120 / 0.35);
|
--border-hairline: rgb(138 129 120 / 0.35);
|
||||||
--border-default: rgb(138 129 120 / 0.7);
|
--border-default: rgb(138 129 120 / 0.7);
|
||||||
--border-strong: var(--color-dust);
|
--border-strong: var(--color-dust);
|
||||||
|
|
||||||
--accent: var(--color-oxide);
|
--accent: var(--color-oxide);
|
||||||
|
/* Raised off #c1502e, which reads 3.99:1 as small text on vinyl. */
|
||||||
|
--accent-text: #cd7155;
|
||||||
--accent-hover: #d05f3c;
|
--accent-hover: #d05f3c;
|
||||||
--accent-active: #a8431f;
|
|
||||||
|
|
||||||
--status-warning: var(--color-tape);
|
--status-warning: var(--color-tape);
|
||||||
--status-success: var(--color-phosphor);
|
--status-success: var(--color-phosphor);
|
||||||
--status-info: var(--color-signal);
|
--status-info: var(--color-signal);
|
||||||
--status-error: var(--color-alarm);
|
|
||||||
|
|
||||||
--link: var(--color-signal);
|
--link: var(--color-signal);
|
||||||
--link-hover: #8bb4dc;
|
--link-hover: #8bb4dc;
|
||||||
@@ -144,8 +139,6 @@
|
|||||||
--tm-pr: #c1502e;
|
--tm-pr: #c1502e;
|
||||||
--tm-cm: #aba298;
|
--tm-cm: #aba298;
|
||||||
--tm-ok: #7fb685;
|
--tm-ok: #7fb685;
|
||||||
--tm-fl: #d8a657;
|
|
||||||
--tm-sg: #6e9dc9;
|
|
||||||
--cmd-bg: #0e0c0a;
|
--cmd-bg: #0e0c0a;
|
||||||
--tui-track: #2a2622;
|
--tui-track: #2a2622;
|
||||||
--tui-bezel: #000000;
|
--tui-bezel: #000000;
|
||||||
@@ -160,26 +153,30 @@
|
|||||||
--bg-raised: var(--color-manila-3);
|
--bg-raised: var(--color-manila-3);
|
||||||
--bg-selection: rgb(76 127 174 / 0.2);
|
--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-primary: var(--color-sleeve);
|
||||||
--text-secondary: #574e45;
|
--text-secondary: #574e45;
|
||||||
--text-muted: #6f655b;
|
--text-muted: #645b52;
|
||||||
--text-inverse: var(--color-paper);
|
|
||||||
|
|
||||||
--border-hairline: rgb(20 17 15 / 0.15);
|
--border-hairline: rgb(20 17 15 / 0.15);
|
||||||
--border-default: rgb(20 17 15 / 0.28);
|
--border-default: rgb(20 17 15 / 0.28);
|
||||||
--border-strong: #6b6259;
|
--border-strong: #6b6259;
|
||||||
|
|
||||||
--accent: #ae4728;
|
--accent: #ae4728;
|
||||||
|
--accent-text: #9c3f23;
|
||||||
--accent-hover: #c1502e;
|
--accent-hover: #c1502e;
|
||||||
--accent-active: #953b1f;
|
|
||||||
|
|
||||||
--status-warning: #c08e3d;
|
--status-warning: #755725;
|
||||||
--status-success: #5e9a66;
|
--status-success: #3e6543;
|
||||||
--status-info: #4c7fae;
|
--status-info: #395f82;
|
||||||
--status-error: #9a2f2f;
|
|
||||||
|
|
||||||
--link: #4c7fae;
|
--link: #395f82;
|
||||||
--link-hover: #3b6a95;
|
--link-hover: #24486a;
|
||||||
|
|
||||||
--tm-bg: #e9e2d7;
|
--tm-bg: #e9e2d7;
|
||||||
--tm-hd: #dcd3c4;
|
--tm-hd: #dcd3c4;
|
||||||
@@ -191,8 +188,6 @@
|
|||||||
--tm-pr: #9e3f22;
|
--tm-pr: #9e3f22;
|
||||||
--tm-cm: #6b6259;
|
--tm-cm: #6b6259;
|
||||||
--tm-ok: #3f7147;
|
--tm-ok: #3f7147;
|
||||||
--tm-fl: #8f6420;
|
|
||||||
--tm-sg: #35648f;
|
|
||||||
--cmd-bg: #dcd3c4;
|
--cmd-bg: #dcd3c4;
|
||||||
--tui-track: #cfc5b5;
|
--tui-track: #cfc5b5;
|
||||||
--tui-bezel: rgb(20 17 15 / 0.28);
|
--tui-bezel: rgb(20 17 15 / 0.28);
|
||||||
@@ -201,9 +196,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Only the cut that is actually shown gets fetched — a hidden <img> would
|
* Only the cut that is actually shown gets fetched; a hidden image element
|
||||||
* still be downloaded. url() is relative to this stylesheet, which Vite
|
* would still be downloaded. url() is relative to this stylesheet, which
|
||||||
* rebases against `base`, so the deploy path stays in vite.config.ts.
|
* Vite rebases against `base`, so the deploy path stays in vite.config.ts.
|
||||||
*/
|
*/
|
||||||
.logo-mark {
|
.logo-mark {
|
||||||
background-image: url('/logo-ink.svg');
|
background-image: url('/logo-ink.svg');
|
||||||
@@ -215,16 +210,25 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* The TUI panes paint from the raw palette (paper, dust, oxide…) because that
|
* 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
|
* is what the Rust client does. Those names do not flip with the theme, so
|
||||||
* manila they would leave paper-on-paper. Light mode re-cuts the same names
|
* each theme re-cuts them, scoped to the terminal's content.
|
||||||
* darker, 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 {
|
[data-theme='light'] .tui-content {
|
||||||
--color-paper: #14110f;
|
--color-paper: #14110f;
|
||||||
--color-paper-dim: #3a332c;
|
--color-paper-dim: #3a332c;
|
||||||
--color-dust: #645b52;
|
--color-dust: #645b52;
|
||||||
--color-oxide: #9e3f22;
|
--color-oxide: #9e3f22;
|
||||||
--color-tape: #8f6420;
|
--color-tape: #8d621f; /* 4.62:1 on manila; #8f6420 sat at 4.49 */
|
||||||
--color-phosphor: #3f7147;
|
--color-phosphor: #3f7147;
|
||||||
--color-signal: #35648f;
|
--color-signal: #35648f;
|
||||||
--color-alarm: #8f2626;
|
--color-alarm: #8f2626;
|
||||||
@@ -260,6 +264,19 @@
|
|||||||
outline-offset: 2px;
|
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) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
html {
|
html {
|
||||||
scroll-behavior: auto;
|
scroll-behavior: auto;
|
||||||
@@ -268,9 +285,13 @@
|
|||||||
*,
|
*,
|
||||||
*::before,
|
*::before,
|
||||||
*::after {
|
*::after {
|
||||||
animation-duration: 0.01ms !important;
|
|
||||||
animation-iteration-count: 1 !important;
|
animation-iteration-count: 1 !important;
|
||||||
transition-duration: 0.01ms !important;
|
transition-duration: 80ms !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Looping or travelling motion has no reduced-motion equivalent. */
|
||||||
|
[class*='animate-'] {
|
||||||
|
animation: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const CITIZENSHIP = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
glyph: '∞',
|
glyph: '∞',
|
||||||
color: 'var(--accent)',
|
color: 'var(--accent-text)',
|
||||||
title: 'Stay online',
|
title: 'Stay online',
|
||||||
body: (
|
body: (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const PLATFORMS: readonly { name: OsName; note: string; anchor: string }[] = [
|
|||||||
const FEATURES = [
|
const FEATURES = [
|
||||||
{
|
{
|
||||||
glyph: '→',
|
glyph: '→',
|
||||||
color: 'var(--accent)',
|
color: 'var(--accent-text)',
|
||||||
title: 'Search & download',
|
title: 'Search & download',
|
||||||
cmd: 'soulseek-rs "field recordings"',
|
cmd: 'soulseek-rs "field recordings"',
|
||||||
body: <>Search the network, pick results in the TUI, and queue downloads with pause, resume, and retry.</>,
|
body: <>Search the network, pick results in the TUI, and queue downloads with pause, resume, and retry.</>,
|
||||||
@@ -86,7 +86,7 @@ const FEATURES = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
glyph: '▮',
|
glyph: '▮',
|
||||||
color: 'var(--accent)',
|
color: 'var(--accent-text)',
|
||||||
title: 'TUI and CLI',
|
title: 'TUI and CLI',
|
||||||
cmd: 'soulseek-rs',
|
cmd: 'soulseek-rs',
|
||||||
body: (
|
body: (
|
||||||
@@ -165,7 +165,7 @@ export const Home = () => {
|
|||||||
>
|
>
|
||||||
<OsIcon name={platform.name} className="max-[519px]:size-[26px]" />
|
<OsIcon name={platform.name} className="max-[519px]:size-[26px]" />
|
||||||
<span className="flex flex-col leading-[1.35] max-[519px]:hidden">
|
<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">
|
<b className="text-[13.5px] font-medium text-primary transition-colors group-hover:text-accent-text">
|
||||||
{platform.name}
|
{platform.name}
|
||||||
</b>
|
</b>
|
||||||
<span className="text-[11.5px] text-secondary">{platform.note}</span>
|
<span className="text-[11.5px] text-secondary">{platform.note}</span>
|
||||||
@@ -251,7 +251,7 @@ export const Home = () => {
|
|||||||
<div className="flex flex-col gap-3.5">
|
<div className="flex flex-col gap-3.5">
|
||||||
<Eyebrow>get started</Eyebrow>
|
<Eyebrow>get started</Eyebrow>
|
||||||
<h2 className="text-[26px] leading-[34px] font-medium text-balance sm:text-title sm:leading-[var(--text-title--line-height)]">
|
<h2 className="text-[26px] leading-[34px] font-medium text-balance sm:text-title sm:leading-[var(--text-title--line-height)]">
|
||||||
Install it, then press <span className="text-accent">/</span> to search.
|
Install it, then press <span className="text-accent-text">/</span> to search.
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-secondary">
|
<p className="text-secondary">
|
||||||
Needs a Rust toolchain. From there it’s one command, then the TUI opens.
|
Needs a Rust toolchain. From there it’s one command, then the TUI opens.
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ interface StepProps {
|
|||||||
|
|
||||||
const Step = ({ n, title, children }: StepProps) => (
|
const Step = ({ n, title, children }: StepProps) => (
|
||||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-[auto_1fr] sm:gap-5">
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-[auto_1fr] sm:gap-5">
|
||||||
<div className="flex size-[34px] items-center justify-center rounded-md border border-line text-sm text-accent">
|
<div className="flex size-[34px] items-center justify-center rounded-md border border-line text-sm text-accent-text">
|
||||||
{n}
|
{n}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex min-w-0 flex-col gap-3">
|
<div className="flex min-w-0 flex-col gap-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user