From 9358b24516368b2f0e3a3025bbbb3f138bbb4f57 Mon Sep 17 00:00:00 2001 From: Ben Schellenberger Date: Thu, 16 Apr 2026 01:35:58 -0400 Subject: [PATCH] Install picker: platform buttons plus Linux/Windows method options Replace the platform select with a radiogroup of OS buttons. For Linux and Windows, add a second Install via row (also a radiogroup) with official paths from the upstream READMEs. Linux: order .deb vs .rpm first using a best-effort user-agent distro guess (DEB vs RPM families), then list the remaining options. git-fire adds curl script and Linuxbrew; git-rain adds a manual binary path alongside packages. Windows: winget plus a manual PowerShell + PATH snippet. Session keys store the chosen Linux/Windows method independently from OS; per-picker coercion skips methods that do not exist for that product while preserving the stored preference for other pickers. Made-with: Cursor --- src/components/InstallPicker.astro | 52 ++- src/scripts/install-pickers.ts | 565 +++++++++++++++++++++++++---- src/styles/global.css | 138 ++++++- 3 files changed, 671 insertions(+), 84 deletions(-) diff --git a/src/components/InstallPicker.astro b/src/components/InstallPicker.astro index 6e5304d..36935e8 100644 --- a/src/components/InstallPicker.astro +++ b/src/components/InstallPicker.astro @@ -14,6 +14,8 @@ const releasesUrl = : 'https://github.com/git-fire/git-rain/releases'; const labelId = `platform-label-${product}`; +const platformGroupId = `platform-group-${product}`; +const methodLabelId = `install-method-label-${product}`; ---
{!embedded &&

{title}

} - - +

{embedded ? 'Install' : 'Platform'}

+
+ + + + +
+ +
diff --git a/src/scripts/install-pickers.ts b/src/scripts/install-pickers.ts index c09d0c3..e9699de 100644 --- a/src/scripts/install-pickers.ts +++ b/src/scripts/install-pickers.ts @@ -1,7 +1,29 @@ export type ProductId = 'git-fire' | 'git-rain'; type OsFamily = 'macos' | 'windows' | 'linux' | 'go'; -const STORAGE_KEY = 'git-fire-site-install-os'; +const STORAGE_KEY_OS = 'git-fire-site-install-os'; +const STORAGE_KEY_LINUX_METHOD = 'git-fire-site-install-linux-method'; +const STORAGE_KEY_WIN_METHOD = 'git-fire-site-install-windows-method'; + +const PLATFORM_ORDER: OsFamily[] = ['macos', 'windows', 'linux', 'go']; + +const LINUX_METHOD_LABELS: Record = { + deb: '.deb (APT)', + rpm: '.rpm (DNF/YUM)', + script: 'curl install script', + brew: 'Homebrew (Linux)', + manual: 'Binary archive (manual)', +}; + +const WIN_METHOD_LABELS: Record = { + winget: 'winget', + manual: 'Binary (manual)', +}; + +function coerceOs(value: string | undefined): OsFamily | null { + if (value === 'macos' || value === 'windows' || value === 'linux' || value === 'go') return value; + return null; +} function detectOs(): OsFamily { if (typeof navigator === 'undefined') return 'go'; @@ -19,9 +41,30 @@ function detectOs(): OsFamily { return 'go'; } +/** Best-effort from browser user agent only — many Linux browsers omit distro. */ +function detectLinuxPackageFamily(): 'deb' | 'rpm' | 'unknown' { + if (typeof navigator === 'undefined') return 'unknown'; + const ua = navigator.userAgent.toLowerCase(); + if ( + /\b(fedora|rhel|red hat|centos|cent stream|rocky|alma|scientific|oracle linux|opensuse|tumbleweed|suse linux|gecko\s*linux|amazon linux|amzn|mageia|pclinuxos|miracle\s*linux|nobara|ultramarine|openmandriva)\b/.test( + ua, + ) + ) { + return 'rpm'; + } + if ( + /\b(ubuntu|debian|linux mint|pop[_!]os|elementary|kali|raspberry|raspbian|neon|zorin|parrot|deepin|mx linux|bodhi|devuan|knoppix|bunsenlabs|linux lite|kubuntu|xubuntu|lubuntu|edubuntu|ubuntu studio|linuxfx)\b/.test( + ua, + ) + ) { + return 'deb'; + } + return 'unknown'; +} + function storedOs(): OsFamily | null { try { - const v = sessionStorage.getItem(STORAGE_KEY); + const v = sessionStorage.getItem(STORAGE_KEY_OS); if (v === 'macos' || v === 'windows' || v === 'linux' || v === 'go') return v; } catch { /* private mode */ @@ -31,107 +74,499 @@ function storedOs(): OsFamily | null { function persistOs(value: OsFamily) { try { - sessionStorage.setItem(STORAGE_KEY, value); + sessionStorage.setItem(STORAGE_KEY_OS, value); + } catch { + /* ignore */ + } +} + +function storedLinuxMethod(): string | null { + try { + return sessionStorage.getItem(STORAGE_KEY_LINUX_METHOD); + } catch { + return null; + } +} + +function persistLinuxMethod(id: string) { + try { + sessionStorage.setItem(STORAGE_KEY_LINUX_METHOD, id); } catch { /* ignore */ } } -function commandBlock(product: ProductId, os: OsFamily): { command: string; note: string } { +function storedWindowsMethod(): string | null { + try { + return sessionStorage.getItem(STORAGE_KEY_WIN_METHOD); + } catch { + return null; + } +} + +function persistWindowsMethod(id: string) { + try { + sessionStorage.setItem(STORAGE_KEY_WIN_METHOD, id); + } catch { + /* ignore */ + } +} + +function orderedLinuxMethodIds(product: ProductId): string[] { + const fam = detectLinuxPackageFamily(); if (product === 'git-fire') { - switch (os) { - case 'macos': - return { - command: 'brew tap git-fire/tap\nbrew install git-fire', - note: 'Requires Homebrew. Same commands work on Linuxbrew.', - }; - case 'windows': - return { - command: 'winget install git-fire', - note: 'If the short ID is not available yet: winget install git-fire.git-fire', - }; - case 'linux': - return { - command: - 'curl -fsSL https://raw.githubusercontent.com/git-fire/git-fire/main/scripts/install.sh | bash', - note: 'Remote code — inspect scripts/install.sh in the repo first when you have time. Prefer release assets + checksums for production rollouts.', - }; - case 'go': - default: - return { - command: 'go install github.com/git-fire/git-fire@latest', - note: 'Requires Go 1.24.2+. Ensure $HOME/go/bin is on your PATH.', - }; - } + return orderPool(fam, ['script', 'deb', 'rpm', 'brew'] as const); } + return orderPool(fam, ['deb', 'rpm', 'manual'] as const); +} - // git-rain - switch (os) { - case 'macos': - return { - command: 'brew tap git-fire/tap\nbrew install git-rain', - note: 'Requires Homebrew.', - }; - case 'windows': - return { - command: 'winget install git-rain.git-rain', - note: 'Windows Package Manager (winget).', - }; - case 'linux': +function orderPool(fam: 'deb' | 'rpm' | 'unknown', neutralOrder: readonly T[]): T[] { + const neutral = [...neutralOrder]; + if (fam === 'deb' && neutral.some((x) => x === 'deb')) { + return ['deb' as T, ...neutral.filter((x) => x !== 'deb')]; + } + if (fam === 'rpm' && neutral.some((x) => x === 'rpm')) { + return ['rpm' as T, ...neutral.filter((x) => x !== 'rpm')]; + } + return neutral; +} + +function buildMethodDefs(product: ProductId, os: OsFamily): { id: string; label: string }[] { + if (os === 'windows') { + return [ + { id: 'winget', label: WIN_METHOD_LABELS.winget }, + { id: 'manual', label: WIN_METHOD_LABELS.manual }, + ]; + } + if (os === 'linux') { + return orderedLinuxMethodIds(product).map((id) => ({ + id, + label: LINUX_METHOD_LABELS[id] ?? id, + })); + } + return []; +} + +function coerceMethodForRoot(root: HTMLElement, choice: string): string { + const product = root.dataset.product as ProductId; + const os = selectedOs(root); + const ids = buildMethodDefs(product, os).map((d) => d.id); + if (ids.includes(choice)) return choice; + return ids[0] ?? choice; +} + +function getEffectiveMethodForRoot(root: HTMLElement): string { + const product = root.dataset.product as ProductId; + const os = selectedOs(root); + const ids = buildMethodDefs(product, os).map((d) => d.id); + if (ids.length === 0) return 'winget'; + const raw = os === 'linux' ? storedLinuxMethod() : os === 'windows' ? storedWindowsMethod() : null; + if (raw && ids.includes(raw)) return raw; + return ids[0]!; +} + +function linuxDetectHint(): string { + const fam = detectLinuxPackageFamily(); + if (fam === 'deb') { + return 'Your browser’s user agent looks DEB-based (best-effort). Installers that match are listed first.'; + } + return 'Your browser’s user agent looks RPM-based (best-effort). Installers that match are listed first.'; +} + +function commandMacosGo(product: ProductId, os: OsFamily): { command: string; note: string } { + if (product === 'git-fire') { + if (os === 'macos') { return { - command: - '# Download .deb or .rpm from GitHub Releases, then:\n# Debian/Ubuntu:\nsudo dpkg -i ./git-rain__amd64.deb\n\n# Fedora/RHEL:\nsudo dnf install ./git-rain__amd64.rpm', - note: 'There is no first-party curl installer for git-rain — use packages from Releases or Go install.', + command: 'brew tap git-fire/tap\nbrew install git-fire', + note: 'Requires Homebrew. Same commands work on Linuxbrew.', }; - case 'go': + } + return { + command: 'go install github.com/git-fire/git-fire@latest', + note: 'Requires Go 1.24.2+. Ensure $HOME/go/bin is on your PATH.', + }; + } + if (os === 'macos') { + return { + command: 'brew tap git-fire/tap\nbrew install git-rain', + note: 'Requires Homebrew.', + }; + } + return { + command: 'go install github.com/git-fire/git-rain@latest', + note: 'Requires Go 1.24.2+. Ensure $HOME/go/bin is on your PATH.', + }; +} + +function fireLinuxDeb(): { command: string; note: string } { + return { + command: 'sudo dpkg -i ./git-fire__amd64.deb', + note: 'Download the matching .deb from GitHub Releases and replace with the release tag (for example v0.2.0). Verify checksums when you can.', + }; +} + +function fireLinuxRpm(): { command: string; note: string } { + return { + command: 'sudo dnf install ./git-fire__amd64.rpm', + note: 'Download the matching .rpm from GitHub Releases and replace with the release tag. On older systems you can use yum instead of dnf. Verify checksums when you can.', + }; +} + +function fireLinuxScript(): { command: string; note: string } { + return { + command: + 'curl -fsSL https://raw.githubusercontent.com/git-fire/git-fire/main/scripts/install.sh | bash', + note: 'Remote code — inspect scripts/install.sh in the repo first when you have time. Prefer release assets + checksums for production rollouts.', + }; +} + +function fireLinuxBrew(): { command: string; note: string } { + return { + command: 'brew tap git-fire/tap\nbrew install git-fire', + note: 'Homebrew on Linux (Linuxbrew). Same tap as macOS.', + }; +} + +function rainLinuxDeb(): { command: string; note: string } { + return { + command: 'sudo dpkg -i ./git-rain__amd64.deb', + note: 'Download the .deb from GitHub Releases and replace with the published tag. Verify checksums when you can.', + }; +} + +function rainLinuxRpm(): { command: string; note: string } { + return { + command: 'sudo dnf install ./git-rain__amd64.rpm', + note: 'Download the .rpm from GitHub Releases and replace with the published tag. Verify checksums when you can.', + }; +} + +function rainLinuxManual(): { command: string; note: string } { + return { + command: + '# Download the archive for your platform from GitHub Releases, extract, then:\nchmod +x git-rain\nsudo mv git-rain /usr/local/bin/', + note: 'Official “binary archive” path from the git-rain README — place the binary on your PATH.', + }; +} + +function fireWindowsWinget(): { command: string; note: string } { + return { + command: 'winget install git-fire', + note: 'If the short ID is not available yet: winget install git-fire.git-fire', + }; +} + +function fireWindowsManual(): { command: string; note: string } { + return { + command: + '# Download git-fire.exe for your arch from GitHub Releases, then in PowerShell:\nNew-Item -ItemType Directory -Force "$env:USERPROFILE\\bin" | Out-Null\nMove-Item .\\git-fire.exe "$env:USERPROFILE\\bin\\git-fire.exe" -Force', + note: 'Add %USERPROFILE%\\bin to your user PATH if it is not already there (see git-fire README).', + }; +} + +function rainWindowsWinget(): { command: string; note: string } { + return { + command: 'winget install git-rain.git-rain', + note: 'Windows Package Manager (winget).', + }; +} + +function rainWindowsManual(): { command: string; note: string } { + return { + command: + '# Download git-rain.exe for your arch from GitHub Releases, then in PowerShell:\nNew-Item -ItemType Directory -Force "$env:USERPROFILE\\bin" | Out-Null\nMove-Item .\\git-rain.exe "$env:USERPROFILE\\bin\\git-rain.exe" -Force', + note: 'Add %USERPROFILE%\\bin to your user PATH if it is not already there (see git-rain README).', + }; +} + +function resolveCommand(product: ProductId, os: OsFamily, methodId: string): { command: string; note: string } { + if (os === 'macos' || os === 'go') return commandMacosGo(product, os); + if (os === 'windows') { + if (product === 'git-fire') { + return methodId === 'manual' ? fireWindowsManual() : fireWindowsWinget(); + } + return methodId === 'manual' ? rainWindowsManual() : rainWindowsWinget(); + } + if (product === 'git-fire') { + switch (methodId) { + case 'deb': + return fireLinuxDeb(); + case 'rpm': + return fireLinuxRpm(); + case 'brew': + return fireLinuxBrew(); + case 'script': + default: + return fireLinuxScript(); + } + } + switch (methodId) { + case 'rpm': + return rainLinuxRpm(); + case 'manual': + return rainLinuxManual(); + case 'deb': default: - return { - command: 'go install github.com/git-fire/git-rain@latest', - note: 'Requires Go 1.24.2+. Ensure $HOME/go/bin is on your PATH.', - }; + return rainLinuxDeb(); } } -function renderPicker(root: HTMLElement) { +function selectedOs(root: HTMLElement): OsFamily { + const active = root.querySelector( + '[data-install-platform][aria-checked="true"]', + ); + return coerceOs(active?.dataset.installPlatform) ?? PLATFORM_ORDER[0]; +} + +function syncPlatformButtons(root: HTMLElement, os: OsFamily) { + root.querySelectorAll('[data-install-platform]').forEach((btn) => { + const v = coerceOs(btn.dataset.installPlatform); + const isSelected = v === os; + btn.setAttribute('aria-checked', isSelected ? 'true' : 'false'); + btn.tabIndex = isSelected ? 0 : -1; + btn.classList.toggle('install-picker__platform-btn--active', isSelected); + }); +} + +function syncMethodButtons(root: HTMLElement, methodId: string) { + root.querySelectorAll('[data-install-method]').forEach((btn) => { + const isSelected = btn.dataset.installMethod === methodId; + btn.setAttribute('aria-checked', isSelected ? 'true' : 'false'); + btn.tabIndex = isSelected ? 0 : -1; + btn.classList.toggle('install-picker__method-btn--active', isSelected); + }); +} + +function ensureMethodControls(root: HTMLElement, product: ProductId, os: OsFamily) { + const area = root.querySelector('[data-install-method-area]'); + const group = root.querySelector('[data-install-method-radiogroup]'); + if (!area || !group) return; + + if (os !== 'linux' && os !== 'windows') { + area.hidden = true; + area.setAttribute('aria-hidden', 'true'); + return; + } + + area.hidden = false; + area.removeAttribute('aria-hidden'); + + const rebuildKey = `${product}:${os}`; + if (root.dataset.installMethodBuilt !== rebuildKey) { + root.dataset.installMethodBuilt = rebuildKey; + group.replaceChildren(); + for (const def of buildMethodDefs(product, os)) { + const btn = document.createElement('button'); + btn.type = 'button'; + btn.role = 'radio'; + btn.setAttribute('aria-checked', 'false'); + btn.className = 'install-picker__method-btn'; + btn.dataset.installMethod = def.id; + btn.textContent = def.label; + btn.tabIndex = -1; + group.append(btn); + } + } + + const effective = getEffectiveMethodForRoot(root); + syncMethodButtons(root, effective); +} + +function updateDetectHint(root: HTMLElement, os: OsFamily) { + const hint = root.querySelector('[data-install-detect-hint]'); + if (!hint) return; + if (os === 'linux' && detectLinuxPackageFamily() !== 'unknown') { + hint.hidden = false; + hint.textContent = linuxDetectHint(); + } else { + hint.hidden = true; + hint.textContent = ''; + } +} + +function updateCommandBlock(root: HTMLElement) { const product = root.dataset.product as ProductId; if (product !== 'git-fire' && product !== 'git-rain') return; - const select = root.querySelector('[data-install-select]'); const pre = root.querySelector('[data-install-command]'); const noteEl = root.querySelector('[data-install-note]'); - if (!select || !pre || !noteEl) return; + if (!pre || !noteEl) return; - const os = select.value as OsFamily; - const { command, note } = commandBlock(product, os); - pre.textContent = command; - noteEl.textContent = note; + const os = selectedOs(root); + const block = + os === 'linux' || os === 'windows' + ? resolveCommand(product, os, getEffectiveMethodForRoot(root)) + : commandMacosGo(product, os); + pre.textContent = block.command; + noteEl.textContent = block.note; +} + +function renderPicker(root: HTMLElement) { + const product = root.dataset.product as ProductId; + if (product !== 'git-fire' && product !== 'git-rain') return; + + const os = selectedOs(root); + ensureMethodControls(root, product, os); + updateDetectHint(root, os); + updateCommandBlock(root); } function setPlatformEverywhere(os: OsFamily) { persistOs(os); document.querySelectorAll('[data-install-picker]').forEach((root) => { - const select = root.querySelector('[data-install-select]'); - if (select) select.value = os; + syncPlatformButtons(root, os); renderPicker(root); }); } +function setInstallMethodEverywhere(os: OsFamily, rawMethodId: string) { + if (os === 'linux') persistLinuxMethod(rawMethodId); + else if (os === 'windows') persistWindowsMethod(rawMethodId); + else return; + + document.querySelectorAll('[data-install-picker]').forEach((root) => { + if (selectedOs(root) !== os) return; + const effective = coerceMethodForRoot(root, rawMethodId); + syncMethodButtons(root, effective); + updateCommandBlock(root); + }); +} + +function bindPlatformRadiogroup(root: HTMLElement) { + const group = root.querySelector('.install-picker__platforms'); + const buttons = root.querySelectorAll('[data-install-platform]'); + if (!group || buttons.length === 0) return; + + group.addEventListener('keydown', (event) => { + if (!(event.target instanceof HTMLButtonElement)) return; + if (!event.target.matches('[data-install-platform]')) return; + + const current = coerceOs(event.target.dataset.installPlatform); + if (!current) return; + + let next: OsFamily | null = null; + + switch (event.key) { + case 'ArrowRight': + case 'ArrowDown': { + event.preventDefault(); + const i = PLATFORM_ORDER.indexOf(current); + next = PLATFORM_ORDER[Math.min(i + 1, PLATFORM_ORDER.length - 1)]; + break; + } + case 'ArrowLeft': + case 'ArrowUp': { + event.preventDefault(); + const i = PLATFORM_ORDER.indexOf(current); + next = PLATFORM_ORDER[Math.max(i - 1, 0)]; + break; + } + case 'Home': { + event.preventDefault(); + next = PLATFORM_ORDER[0]; + break; + } + case 'End': { + event.preventDefault(); + next = PLATFORM_ORDER[PLATFORM_ORDER.length - 1]; + break; + } + default: + return; + } + + if (next) { + setPlatformEverywhere(next); + queueMicrotask(() => { + root.querySelector(`[data-install-platform="${next}"]`)?.focus(); + }); + } + }); + + buttons.forEach((btn) => { + btn.addEventListener('click', () => { + const os = coerceOs(btn.dataset.installPlatform); + if (os) setPlatformEverywhere(os); + }); + }); +} + +function bindMethodInteraction(root: HTMLElement) { + root.addEventListener('click', (event) => { + const t = event.target; + if (!(t instanceof Element)) return; + const btn = t.closest('[data-install-method]'); + if (!btn || !root.contains(btn)) return; + const os = selectedOs(root); + if (os !== 'linux' && os !== 'windows') return; + const id = btn.dataset.installMethod; + if (!id) return; + setInstallMethodEverywhere(os, id); + }); + + root.addEventListener('keydown', (event) => { + if (!(event.target instanceof HTMLButtonElement)) return; + if (!event.target.matches('[data-install-method]')) return; + const os = selectedOs(root); + if (os !== 'linux' && os !== 'windows') return; + + const group = root.querySelector('[data-install-method-radiogroup]'); + if (!group?.contains(event.target)) return; + + const order = [...group.querySelectorAll('[data-install-method]')].map( + (b) => b.dataset.installMethod ?? '', + ); + const current = event.target.dataset.installMethod ?? ''; + const idx = order.indexOf(current); + if (idx < 0) return; + + let nextIdx: number | null = null; + switch (event.key) { + case 'ArrowRight': + case 'ArrowDown': + event.preventDefault(); + nextIdx = Math.min(idx + 1, order.length - 1); + break; + case 'ArrowLeft': + case 'ArrowUp': + event.preventDefault(); + nextIdx = Math.max(idx - 1, 0); + break; + case 'Home': + event.preventDefault(); + nextIdx = 0; + break; + case 'End': + event.preventDefault(); + nextIdx = order.length - 1; + break; + default: + return; + } + if (nextIdx === null) return; + const nextId = order[nextIdx]; + if (nextId) { + setInstallMethodEverywhere(os, nextId); + queueMicrotask(() => { + root.querySelector(`[data-install-method="${nextId}"]`)?.focus(); + }); + } + }); +} + function bindPicker(root: HTMLElement) { const product = root.dataset.product as ProductId; if (product !== 'git-fire' && product !== 'git-rain') return; - const select = root.querySelector('[data-install-select]'); const copyBtn = root.querySelector('[data-install-copy]'); - - if (!select || !copyBtn) return; + if (!copyBtn) return; const initial = storedOs() ?? detectOs(); - select.value = initial; + syncPlatformButtons(root, initial); renderPicker(root); - select.addEventListener('change', () => { - setPlatformEverywhere(select.value as OsFamily); - }); + bindPlatformRadiogroup(root); + bindMethodInteraction(root); copyBtn.addEventListener('click', async () => { const pre = root.querySelector('[data-install-command]'); diff --git a/src/styles/global.css b/src/styles/global.css index e80686f..deb6675 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -445,7 +445,7 @@ a:hover { border-color: color-mix(in srgb, var(--color-border) 80%, var(--color-rain)); } -.install-picker--embedded label { +.install-picker--embedded .install-picker__label { margin-bottom: var(--space-xs); } @@ -470,25 +470,139 @@ a:hover { font-size: 0.8125rem; } -.install-picker label { +.install-picker--embedded .install-picker__methods { + max-width: 100%; +} + +.install-picker--embedded .install-picker__method-btn { + font-size: 0.6875rem; +} + +.install-picker--embedded .install-picker__detect-hint { + font-size: 0.6875rem; + margin-bottom: var(--space-xs); +} + +.install-picker__label { display: block; font-size: 0.8125rem; font-weight: 600; color: var(--color-muted); - margin-bottom: var(--space-sm); + margin: 0 0 var(--space-sm); +} + +.install-picker__label--method { + margin-top: var(--space-xs); + margin-bottom: var(--space-xs); + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.04em; } -.install-picker select { - width: 100%; +.install-picker__platforms { + display: flex; + flex-wrap: wrap; + gap: var(--space-xs); + margin-bottom: var(--space-md); max-width: 22rem; - padding: var(--space-sm) var(--space-md); +} + +.install-picker__platform-btn { font: inherit; - font-size: 0.9375rem; - color: var(--color-text); + font-size: 0.8125rem; + font-weight: 600; + padding: var(--space-xs) var(--space-sm); + border-radius: var(--radius-sm); + border: 1px solid color-mix(in srgb, var(--color-border) 85%, var(--color-text)); background: var(--color-bg); - border: 1px solid color-mix(in srgb, var(--color-border) 80%, var(--color-text)); + color: var(--color-muted); + cursor: pointer; + transition: + border-color 0.15s ease, + background 0.15s ease, + color 0.15s ease; +} + +.install-picker__platform-btn:hover { + color: var(--color-text); + border-color: color-mix(in srgb, var(--color-border) 55%, var(--color-text)); +} + +.install-picker[data-product='git-fire'] .install-picker__platform-btn--active { + color: var(--color-fire); + border-color: color-mix(in srgb, var(--color-fire) 45%, var(--color-border)); + background: color-mix(in srgb, var(--color-fire) 12%, var(--color-bg)); +} + +.install-picker[data-product='git-rain'] .install-picker__platform-btn--active { + color: var(--color-rain); + border-color: color-mix(in srgb, var(--color-rain) 45%, var(--color-border)); + background: color-mix(in srgb, var(--color-rain) 12%, var(--color-bg)); +} + +.install-picker__method-area { + margin-bottom: var(--space-sm); +} + +.install-picker__method-area[hidden] { + display: none; +} + +.install-picker__methods { + display: flex; + flex-wrap: wrap; + gap: var(--space-xs); + margin-bottom: var(--space-sm); + max-width: 28rem; +} + +.install-picker__method-btn { + font: inherit; + font-size: 0.75rem; + font-weight: 600; + padding: 0.2rem 0.45rem; border-radius: var(--radius-sm); - margin-bottom: var(--space-md); + border: 1px dashed color-mix(in srgb, var(--color-border) 70%, var(--color-muted)); + background: color-mix(in srgb, var(--color-bg) 92%, var(--color-surface)); + color: var(--color-muted); + cursor: pointer; + transition: + border-color 0.15s ease, + background 0.15s ease, + color 0.15s ease, + border-style 0.15s ease; +} + +.install-picker__method-btn:hover { + color: var(--color-text); + border-color: color-mix(in srgb, var(--color-border) 50%, var(--color-text)); + border-style: solid; +} + +.install-picker[data-product='git-fire'] .install-picker__method-btn--active { + color: var(--color-fire); + border-style: solid; + border-color: color-mix(in srgb, var(--color-fire) 40%, var(--color-border)); + background: color-mix(in srgb, var(--color-fire) 10%, var(--color-bg)); +} + +.install-picker[data-product='git-rain'] .install-picker__method-btn--active { + color: var(--color-rain); + border-style: solid; + border-color: color-mix(in srgb, var(--color-rain) 40%, var(--color-border)); + background: color-mix(in srgb, var(--color-rain) 10%, var(--color-bg)); +} + +.install-picker__detect-hint { + font-size: 0.75rem; + line-height: 1.4; + color: var(--color-muted); + margin: 0 0 var(--space-sm); + max-width: 32rem; +} + +.install-picker__detect-hint[hidden] { + display: none; } .install-picker pre { @@ -513,7 +627,7 @@ a:hover { margin-bottom: var(--space-md); } -.install-picker button { +.install-picker__actions button { font: inherit; font-size: 0.875rem; font-weight: 600; @@ -526,7 +640,7 @@ a:hover { transition: border-color 0.15s ease, background 0.15s ease; } -.install-picker button:hover { +.install-picker__actions button:hover { border-color: var(--color-rain-dim); background: color-mix(in srgb, var(--color-rain) 12%, var(--color-surface)); }