fix(landing): API example + new /download page (no more dumping users on GitHub) (#487)

* fix(landing): use qwen_custom_voice in API example (instruct is CustomVoice-only)

The curl snippet showed engine: "qwen" alongside an instruct field, but base
Qwen3-TTS has no instruct path — that's a Qwen CustomVoice feature.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fix(landing): use a realistic UUID for profile_id in API example

Profile IDs are str(uuid.uuid4()), not slugs (see backend/services/profiles.py:175).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* feat(landing): add polished /download page — no more dumping users on GitHub

Users were clicking download, landing on the GitHub releases page, and filing
confused comments along the lines of "I ended up on some blog site called
GitHub." We now route every download CTA through a dedicated /download page
that auto-triggers the platform-specific download and gives users a polished
post-click experience with donate + docs + AI help prompts.

- New /download page:
  - Big app logo + "Your download has started" messaging.
  - Auto-detects platform from ?platform=X or navigator.userAgent.
  - Programmatically clicks a hidden anchor to trigger the file download
    without leaving the page.
  - Platform-specific buttons as a visible fallback for "download not
    working" / manual-pick.
  - Personal donate spiel + Buy Me a Coffee button.
  - Resources grid: docs, DeepWiki ("got questions? ask AI"), GitHub.
- Landing page download section cards now link to /download?platform=X
  instead of the asset URL directly.
- /download/[platform] (used by README/docs links) now redirects to the
  /download page rather than straight to the asset or to GitHub on error.
- Drops unused downloadLinks state from the landing page.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fix(landing): use official platform brand icons via simple-icons

The hand-rolled Linux SVG path wasn't actually Tux — it was a symmetric
placeholder shape. Apple/Windows were close but not canonical either.

- Apple + Linux: pulled from @icons-pack/react-simple-icons (SiApple, SiLinux).
- Windows: simple-icons drops the Microsoft mark over trademark policy, so
  the Windows 11 flag is inlined from Microsoft's public brand guidance.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fix(landing): route Download CTAs to /download page, not the section anchor

Hero CTA, navbar link, and footer link were all scrolling to #download
(the section at the bottom of the page) instead of going to the new
/download page that triggers the actual download.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* chore(landing): run dev server on Node instead of Bun runtime

Bun runtime + Next 16 Turbopack dev server intermittently trips a
JavaScriptCore allocator panic ('pas panic: deallocation did fail ...
Alloc bit not set') after a few requests. Dropping --bun keeps Bun as
the package manager but runs next dev on Node, which is stable.

Build + start keep --bun since one-shot invocations don't exhibit the
allocator drift.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fix(landing): route Linux users to /linux-install instead of attempting download

No prebuilt Linux binary exists yet (see /linux-install for build-from-source
instructions). The /download page previously treated Linux like the other
platforms — auto-triggering a non-existent AppImage and offering a dead
manual button.

- /download page: if platform resolves to 'linux' via ?platform or UA detect,
  window.location.replace('/linux-install') — never try to auto-download.
- Manual Linux card: label changed to "Build from source" and links to
  /linux-install (no download attribute, no asset URL).
- /download/linux pretty URL: 307s straight to /linux-install.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* docs: consolidate troubleshooting into the MDX docs site + status updates

- Delete docs/TROUBLESHOOTING.md; the canonical troubleshooting guide now
  lives under docs/content/docs/overview/troubleshooting.mdx so it's served
  from docs.voicebox.sh alongside the rest of the docs.
- CONTRIBUTING.md + README.md: repoint "Troubleshooting" references to the
  new MDX path. README gets a top-level callout so users hit the guide
  before filing an issue.
- PROJECT_STATUS.md: refresh issue/PR counts, document the flash-attn
  warning (cosmetic on all platforms; CUDA-only, fallback is PyTorch SDPA
  which is near-FA2 on Ampere+) with per-platform context + community
  Windows wheels + SageAttention/xformers alternatives, add WebAudio
  audio-session bug note (tracked separately in PR #486), and expand the
  Qwen 0.6B→1.7B MLX fallback explanation for triage.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fix(landing): address PR #487 review feedback

- Preserve canonical camelCase platform aliases (macArm, macIntel) in the
  /download/[platform] redirect so those URLs don't lose their platform param.
- Add accessible title + role="img" to the inline Windows SVG so it passes
  Biome's a11y rule and announces to screen readers.
- On /api/releases fetch failure, show an explicit error state with a single
  intentional link to GitHub releases — no more silent GitHub fallback or
  disabled-button UX lie. Keeps normies off GitHub unless they opt in.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
Jamie Pine
2026-04-18 23:32:32 -07:00
committed by GitHub
co-authored by Claude Opus 4.7
parent d3a44338a2
commit 27a5a62581
14 changed files with 545 additions and 390 deletions
+16 -27
View File
@@ -1,42 +1,31 @@
import { type NextRequest, NextResponse } from 'next/server';
import { getLatestRelease } from '@/lib/releases';
export const dynamic = 'force-dynamic';
const PLATFORM_MAP: Record<
string,
keyof Awaited<ReturnType<typeof getLatestRelease>>['downloadLinks']
> = {
// Pretty URLs from README / docs (e.g. /download/mac-arm) are kept for
// compatibility, but we now always route through the /download page so users
// see context + a donate prompt + resources while the download kicks off.
// The page handles the actual file trigger itself — no more silent redirects
// to GitHub or direct asset URLs.
const PLATFORM_ALIAS: Record<string, string> = {
'mac-arm': 'macArm',
macArm: 'macArm',
'mac-intel': 'macIntel',
macIntel: 'macIntel',
windows: 'windows',
linux: 'linux',
};
export async function GET(
_request: NextRequest,
request: NextRequest,
{ params }: { params: Promise<{ platform: string }> },
) {
const { platform } = await params;
const key = PLATFORM_MAP[platform];
if (!key) {
return NextResponse.json(
{ error: `Unknown platform: ${platform}. Use: ${Object.keys(PLATFORM_MAP).join(', ')}` },
{ status: 404 },
);
}
try {
const release = await getLatestRelease();
const url = release.downloadLinks[key];
if (!url) {
return NextResponse.json({ error: `No download available for ${platform}` }, { status: 404 });
}
return NextResponse.redirect(url);
} catch {
return NextResponse.redirect(`https://github.com/jamiepine/voicebox/releases/latest`);
// No prebuilt Linux binary yet — send straight to the build-from-source page.
if (platform === 'linux') {
return NextResponse.redirect(new URL('/linux-install', request.url), 307);
}
const normalized = PLATFORM_ALIAS[platform];
const target = new URL('/download', request.url);
if (normalized) target.searchParams.set('platform', normalized);
return NextResponse.redirect(target, 307);
}
+313
View File
@@ -0,0 +1,313 @@
'use client';
import {
ArrowLeft,
Bot,
Coffee,
Download as DownloadIcon,
FileText,
Github,
} from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
import { useEffect, useMemo, useState } from 'react';
import { AppleIcon, LinuxIcon, WindowsIcon } from '@/components/PlatformIcons';
import { Button } from '@/components/ui/button';
import { DONATE_URL, GITHUB_RELEASES_PAGE, GITHUB_REPO } from '@/lib/constants';
import type { DownloadLinks } from '@/lib/releases';
type Platform = keyof DownloadLinks;
type PlatformMeta = {
key: Platform;
label: string;
description: string;
icon: React.ComponentType<{ className?: string }>;
};
const PLATFORMS: PlatformMeta[] = [
{ key: 'macArm', label: 'macOS', description: 'Apple Silicon', icon: AppleIcon },
{ key: 'macIntel', label: 'macOS', description: 'Intel (x64)', icon: AppleIcon },
{ key: 'windows', label: 'Windows', description: '64-bit (MSI)', icon: WindowsIcon },
{ key: 'linux', label: 'Linux', description: 'Build from source', icon: LinuxIcon },
];
function detectPlatform(): Platform | null {
if (typeof navigator === 'undefined') return null;
const ua = navigator.userAgent;
if (/Windows/i.test(ua)) return 'windows';
if (/Linux/i.test(ua) && !/Android/i.test(ua)) return 'linux';
if (/Mac/i.test(ua)) {
// Apple Silicon Safari reports "Intel" for compat; default to ARM since
// M-series is the majority. Users can click the Intel button if needed.
return 'macArm';
}
return null;
}
function parseQueryPlatform(search: string): Platform | null {
const params = new URLSearchParams(search);
const raw = params.get('platform');
if (!raw) return null;
// Accept both camelCase and hyphenated forms (/download/mac-arm → ?platform=mac-arm).
const normalized = raw
.toLowerCase()
.replace(/[-_\s]/g, '')
.replace('macarm', 'macArm')
.replace('macintel', 'macIntel');
const valid: Platform[] = ['macArm', 'macIntel', 'windows', 'linux'];
return (valid as string[]).includes(normalized) ? (normalized as Platform) : null;
}
export default function DownloadPage() {
const [links, setLinks] = useState<DownloadLinks | null>(null);
const [linksError, setLinksError] = useState(false);
const [platform, setPlatform] = useState<Platform | null>(null);
const [triggered, setTriggered] = useState(false);
useEffect(() => {
const fromQuery = parseQueryPlatform(window.location.search);
const resolved = fromQuery ?? detectPlatform();
// No prebuilt Linux binary yet — send Linux users to the build-from-source
// instructions instead of sitting on /download trying to trigger a
// download that doesn't exist.
if (resolved === 'linux') {
window.location.replace('/linux-install');
return;
}
setPlatform(resolved);
}, []);
useEffect(() => {
let cancelled = false;
fetch('/api/releases')
.then((r) => {
if (!r.ok) throw new Error(`releases ${r.status}`);
return r.json();
})
.then((data) => {
if (cancelled) return;
if (data.downloadLinks) setLinks(data.downloadLinks as DownloadLinks);
})
.catch(() => {
if (!cancelled) setLinksError(true);
});
return () => {
cancelled = true;
};
}, []);
useEffect(() => {
if (triggered || !links || !platform) return;
const url = links[platform];
if (!url) return;
const a = document.createElement('a');
a.href = url;
a.rel = 'noopener';
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTriggered(true);
}, [triggered, links, platform]);
const activeMeta = useMemo(
() => PLATFORMS.find((p) => p.key === platform) ?? null,
[platform],
);
return (
<div className="min-h-screen bg-background">
{/* Minimal branded header */}
<header className="border-b border-border/50">
<div className="mx-auto flex max-w-5xl items-center justify-between px-6 py-4">
<Link href="/" className="flex items-center gap-2.5">
<Image
src="/voicebox-logo-app.webp"
alt="Voicebox"
width={28}
height={28}
className="h-7 w-7"
/>
<span className="text-[15px] font-semibold text-foreground">Voicebox</span>
</Link>
<Link
href="/"
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
>
<ArrowLeft className="h-3.5 w-3.5" />
Back to voicebox.sh
</Link>
</div>
</header>
<main className="mx-auto max-w-5xl px-6 py-16 md:py-24">
{/* Hero */}
<div className="flex flex-col md:flex-row md:items-center gap-10 md:gap-14">
<Image
src="/voicebox-logo-app.webp"
alt="Voicebox"
width={200}
height={200}
priority
className="h-32 w-32 md:h-44 md:w-44 shrink-0 drop-shadow-2xl"
/>
<div className="flex-1 min-w-0 text-center md:text-left">
{triggered ? (
<>
<h1 className="text-4xl md:text-5xl font-semibold tracking-tight text-foreground mb-4">
Your download has started.
</h1>
<p className="text-lg text-muted-foreground">
{activeMeta
? `Downloading Voicebox for ${activeMeta.label} (${activeMeta.description}). Check your downloads folder.`
: 'Check your downloads folder for Voicebox.'}
</p>
</>
) : (
<>
<h1 className="text-4xl md:text-5xl font-semibold tracking-tight text-foreground mb-4">
{linksError ? "We couldn't load the latest release." : 'Download Voicebox'}
</h1>
<p className="text-lg text-muted-foreground">
{linksError
? 'Our release server is temporarily unreachable. Please try again in a moment.'
: 'Pick your platform to get started.'}
</p>
</>
)}
</div>
</div>
{/* Platform buttons — always visible as a fallback */}
{linksError ? (
<div className="mt-12 rounded-xl border border-border bg-card/60 backdrop-blur-sm p-6 text-center">
<p className="text-sm text-muted-foreground mb-4">
If this keeps happening, you can{' '}
<a
href={`${GITHUB_RELEASES_PAGE}/latest`}
target="_blank"
rel="noopener noreferrer"
className="text-accent underline underline-offset-2 hover:text-accent/80"
>
browse releases on GitHub
</a>
{' '}and grab the build for your platform manually.
</p>
</div>
) : (
<div className="mt-12 rounded-xl border border-border bg-card/60 backdrop-blur-sm p-6">
<h2 className="text-sm font-medium text-foreground mb-4">
{triggered ? 'Download not working?' : 'Choose your platform'}
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{PLATFORMS.map((meta) => {
const isLinux = meta.key === 'linux';
const url = isLinux ? '/linux-install' : links?.[meta.key];
const isActive = meta.key === platform;
const disabled = !isLinux && !url;
return (
<a
key={meta.key}
href={url ?? '#'}
{...(isLinux ? {} : { download: true })}
aria-disabled={disabled}
onClick={(e) => {
if (disabled) e.preventDefault();
}}
className={`flex items-center rounded-xl border px-5 py-4 transition-all group ${
isActive
? 'border-accent/40 bg-accent/5 hover:border-accent/60'
: 'border-border bg-card/40 hover:border-accent/30 hover:bg-card'
} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
>
<meta.icon className="h-6 w-6 shrink-0 text-muted-foreground group-hover:text-foreground transition-colors" />
<div className="ml-4 flex-1">
<div className="text-sm font-medium text-foreground">{meta.label}</div>
<div className="text-xs text-muted-foreground">{meta.description}</div>
</div>
<DownloadIcon className="h-4 w-4 text-muted-foreground/60 group-hover:text-accent transition-colors" />
</a>
);
})}
</div>
</div>
)}
{/* Donate — prominent, heartfelt, post-click context */}
<div className="mt-16 rounded-2xl border border-border bg-gradient-to-br from-card via-card/80 to-background backdrop-blur-sm p-8 md:p-10 overflow-hidden relative">
<div className="absolute top-0 right-0 w-64 h-64 bg-[#FFDD00]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2 pointer-events-none" />
<div className="relative">
<div className="inline-flex items-center gap-2 rounded-full border border-[#FFDD00]/30 bg-[#FFDD00]/10 px-3 py-1 mb-4">
<Coffee className="h-3 w-3 text-[#FFDD00]" />
<span className="text-[11px] font-medium uppercase tracking-wider text-[#FFDD00]">
Hi from the maintainer
</span>
</div>
<h2 className="text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-4">
Jamie here — Voicebox is a side project.
</h2>
<p className="text-muted-foreground leading-relaxed mb-6 max-w-2xl">
I build and maintain Voicebox in my spare time. It's completely
free, open source, runs entirely on your machine — no accounts, no
cloud, no subscriptions, no upsells. If it saves you an ElevenLabs
bill or just made your day, a coffee genuinely helps me keep
shipping updates, adding new models, and fixing bugs. Every little
bit keeps the lights on.
</p>
<Button asChild size="lg" className="bg-[#FFDD00]/10 border-[#FFDD00]/30 text-[#FFDD00] hover:bg-[#FFDD00]/20 hover:border-[#FFDD00]/50">
<a href={DONATE_URL} target="_blank" rel="noopener noreferrer">
<Coffee className="h-4 w-4 mr-2" />
Buy me a coffee
</a>
</Button>
</div>
</div>
{/* Resources */}
<div className="mt-10">
<h2 className="text-sm font-medium text-foreground mb-4">While you wait</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<a
href="https://docs.voicebox.sh"
target="_blank"
rel="noopener noreferrer"
className="rounded-xl border border-border bg-card/60 backdrop-blur-sm p-5 hover:border-accent/30 hover:bg-card transition-all group"
>
<FileText className="h-5 w-5 text-accent mb-3" />
<h3 className="text-sm font-medium text-foreground mb-1">Read the docs</h3>
<p className="text-xs text-muted-foreground leading-relaxed">
Get familiar with Voicebox — setup, voice cloning, the REST API.
</p>
</a>
<a
href="https://deepwiki.com/jamiepine/voicebox"
target="_blank"
rel="noopener noreferrer"
className="rounded-xl border border-border bg-card/60 backdrop-blur-sm p-5 hover:border-accent/30 hover:bg-card transition-all group"
>
<Bot className="h-5 w-5 text-accent mb-3" />
<h3 className="text-sm font-medium text-foreground mb-1">Got questions? Ask AI.</h3>
<p className="text-xs text-muted-foreground leading-relaxed">
DeepWiki is an AI that knows Voicebox inside-out. Ask anything.
</p>
</a>
<a
href={GITHUB_REPO}
target="_blank"
rel="noopener noreferrer"
className="rounded-xl border border-border bg-card/60 backdrop-blur-sm p-5 hover:border-accent/30 hover:bg-card transition-all group"
>
<Github className="h-5 w-5 text-accent mb-3" />
<h3 className="text-sm font-medium text-foreground mb-1">Source on GitHub</h3>
<p className="text-xs text-muted-foreground leading-relaxed">
Star the repo, file issues, or contribute a PR.
</p>
</a>
</div>
</div>
</main>
</div>
);
}
+5 -12
View File
@@ -17,12 +17,9 @@ import {Navbar} from "@/components/Navbar";
import {AppleIcon, LinuxIcon, WindowsIcon} from "@/components/PlatformIcons";
import {TutorialsSection} from "@/components/TutorialsSection";
import {VoiceCreator} from "@/components/VoiceCreator";
import {DOWNLOAD_LINKS, GITHUB_REPO} from "@/lib/constants";
import type {DownloadLinks} from "@/lib/releases";
import {GITHUB_REPO} from "@/lib/constants";
export default function Home() {
const [downloadLinks, setDownloadLinks] =
useState<DownloadLinks>(DOWNLOAD_LINKS);
const [version, setVersion] = useState<string | null>(null);
const [totalDownloads, setTotalDownloads] = useState<number | null>(null);
@@ -33,7 +30,6 @@ export default function Home() {
return res.json();
})
.then((data) => {
if (data.downloadLinks) setDownloadLinks(data.downloadLinks);
if (data.version) setVersion(data.version);
if (data.totalDownloads != null) setTotalDownloads(data.totalDownloads);
})
@@ -92,7 +88,7 @@ export default function Home() {
style={{animationDelay: "300ms"}}
>
<a
href="#download"
href="/download"
className="rounded-full bg-accent px-8 py-3.5 text-sm font-semibold uppercase tracking-wider text-white shadow-[0_4px_20px_hsl(43_60%_50%/0.3),inset_0_2px_0_rgba(255,255,255,0.2),inset_0_-2px_0_rgba(0,0,0,0.1)] transition-all hover:bg-accent-faint active:shadow-[0_2px_10px_hsl(43_60%_50%/0.3),inset_0_4px_8px_rgba(0,0,0,0.3)]"
>
Download
@@ -403,8 +399,7 @@ export default function Home() {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 max-w-2xl mx-auto">
{/* macOS ARM */}
<a
href={downloadLinks.macArm}
download
href="/download?platform=macArm"
className="flex items-center rounded-xl border border-border bg-card/60 backdrop-blur-sm px-5 py-4 transition-all hover:border-accent/30 hover:bg-card group"
>
<AppleIcon className="h-6 w-6 shrink-0 text-muted-foreground group-hover:text-foreground transition-colors" />
@@ -418,8 +413,7 @@ export default function Home() {
{/* macOS Intel */}
<a
href={downloadLinks.macIntel}
download
href="/download?platform=macIntel"
className="flex items-center rounded-xl border border-border bg-card/60 backdrop-blur-sm px-5 py-4 transition-all hover:border-accent/30 hover:bg-card group"
>
<AppleIcon className="h-6 w-6 shrink-0 text-muted-foreground group-hover:text-foreground transition-colors" />
@@ -431,8 +425,7 @@ export default function Home() {
{/* Windows */}
<a
href={downloadLinks.windows}
download
href="/download?platform=windows"
className="flex items-center rounded-xl border border-border bg-card/60 backdrop-blur-sm px-5 py-4 transition-all hover:border-accent/30 hover:bg-card group"
>
<WindowsIcon className="h-6 w-6 shrink-0 text-muted-foreground group-hover:text-foreground transition-colors" />
+2 -2
View File
@@ -29,8 +29,8 @@ const CURL_SNIPPET = `curl -X POST http://127.0.0.1:17493/generate \\
-H "Content-Type: application/json" \\
-d '{
"text": "Welcome to the game, player one.",
"profile_id": "morgan-freeman",
"engine": "qwen",
"profile_id": "b3f1c2d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"engine": "qwen_custom_voice",
"instruct": "warm, slow, cinematic"
}' \\
--output line.wav`;
+1 -1
View File
@@ -45,7 +45,7 @@ export function Footer() {
</a>
</li>
<li>
<a href="#download" className="hover:text-foreground transition-colors">
<a href="/download" className="hover:text-foreground transition-colors">
Download
</a>
</li>
+1 -1
View File
@@ -66,7 +66,7 @@ export function Navbar() {
API
</a>
<a
href="#download"
href="/download"
className="rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:text-foreground"
>
Download
+21 -15
View File
@@ -1,23 +1,29 @@
import { SiApple, SiLinux } from '@icons-pack/react-simple-icons';
// Official brand icons via Simple Icons (apple/linux). Simple Icons drops
// Microsoft's mark due to trademark policy, so the Windows 11 flag is
// inlined from Microsoft's public brand guidance.
export function AppleIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
<path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" />
</svg>
);
return <SiApple className={className} color="currentColor" />;
}
export function LinuxIcon({ className }: { className?: string }) {
return <SiLinux className={className} color="currentColor" />;
}
export function WindowsIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
<path d="M3 12V6.75l6-1.32v6.48L3 12zm17-9v8.75l-10 .15V5.21L20 3zM3 13l6 .09v7.81l-6-1.15V13zm17 .25V22l-10-1.8v-7.15l10 .15z" />
</svg>
);
}
export function LinuxIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
<path d="M12.504 0c-.155 0-.315.008-.48.021-4.226.333-3.105 4.807-3.17 6.298-.076 1.092-.3 1.953-1.05 3.02-.885 1.051-2.127 2.75-2.716 4.521-.278.832-.41 1.684-.287 2.489a.424.424 0 00-.11.135c-.26.26-.195.69-.133 1.001.054.27.112.553.077.784-.12.794-.3 1.593-.3 2.406 0 .599.18 1.193.3 1.791.12.599.3 1.193.3 1.792 0 .812.18 1.611.3 2.405.035.23-.023.514-.077.783-.062.312-.127.742.133 1.002a.424.424 0 00.11.135c-.123.805.01 1.657.287 2.489.589 1.771 1.831 3.47 2.716 4.521.75 1.067 0.974 1.928 1.05 3.02.065 1.491-1.056 5.965 3.17 6.298.165.013.325.021.48.021.155 0 .315-.008.48-.021 4.226-.333 3.105-4.807 3.17-6.298.076-1.092.3-1.953 1.05-3.02.885-1.051 2.127-2.75 2.716-4.521.278-.832.41-1.684.287-2.489a.424.424 0 00.11-.135c.26-.26.195-.69.133-1.001-.054-.27-.112-.553-.077-.784.12-.794.3-1.593.3-2.406 0-.599-.18-1.193-.3-1.791-.12-.599-.3-1.193-.3-1.792 0-.812-.18-1.611-.3-2.405-.035-.23.023-.514.077-.783.062-.312.127-.742-.133-1.002a.424.424 0 00-.11-.135c.123-.805-.01-1.657-.287-2.489-.589-1.771-1.831-3.47-2.716-4.521-.75-1.067-.974-1.928-1.05-3.02-.065-1.491 1.056-5.965-3.17-6.298C12.819.008 12.659 0 12.504 0z" />
<svg
className={className}
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-label="Windows"
>
<title>Windows</title>
<path d="M0 3.449L9.75 2.1v9.451H0m10.949-9.602L24 0v11.4l-13.051.149M0 12.6h9.75v9.451L0 20.699M10.949 12.6H24V24l-12.9-1.801" />
</svg>
);
}