Update global styles and enhance landing page layout

- Added overflow-x hidden to prevent horizontal scrolling on html and body elements.
- Centered the heading and paragraph text on the landing page for improved aesthetics.
- Introduced a mobile-friendly centered screenshot above download buttons.
- Updated comments for clarity regarding screenshot positioning on different devices.
This commit is contained in:
Jamie Pine
2026-01-25 21:06:41 -08:00
parent af2f37ccb1
commit b4b3762ef0
5 changed files with 164 additions and 17 deletions
+18
View File
@@ -0,0 +1,18 @@
import { NextResponse } from 'next/server';
import { getLatestRelease } from '@/lib/releases';
export const dynamic = 'force-dynamic';
export const revalidate = 600; // Revalidate every 10 minutes
export async function GET() {
try {
const releaseInfo = await getLatestRelease();
return NextResponse.json(releaseInfo);
} catch (error) {
console.error('Error fetching release info:', error);
return NextResponse.json(
{ error: 'Failed to fetch release information' },
{ status: 500 }
);
}
}
+4
View File
@@ -54,8 +54,12 @@
* {
@apply border-border;
}
html {
overflow-x: hidden;
}
body {
@apply bg-background text-foreground antialiased;
overflow-x: hidden;
background-image:
radial-gradient(at 0% 0%, rgba(255, 255, 255, 0.03) 0px, transparent 50%),
radial-gradient(at 100% 100%, rgba(255, 255, 255, 0.02) 0px, transparent 50%);
+44 -7
View File
@@ -2,13 +2,36 @@
import { Cloud, Code, Cpu, Github, Shield, Zap } from 'lucide-react';
import Image from 'next/image';
import { useEffect, useState } from 'react';
import { AppleIcon, LinuxIcon, WindowsIcon } from '@/components/PlatformIcons';
import { Button } from '@/components/ui/button';
import { Section, SectionTitle } from '@/components/ui/section';
import { DOWNLOAD_LINKS, GITHUB_REPO } from '@/lib/constants';
import type { DownloadLinks } from '@/lib/releases';
import { FeatureCard } from '../components/ui/feature-card';
export default function Home() {
const [downloadLinks, setDownloadLinks] = useState<DownloadLinks>(DOWNLOAD_LINKS);
useEffect(() => {
// Fetch latest release info
fetch('/api/releases')
.then((res) => {
if (!res.ok) {
throw new Error('Failed to fetch releases');
}
return res.json();
})
.then((data) => {
if (data.downloadLinks) {
setDownloadLinks(data.downloadLinks);
}
})
.catch((error) => {
console.error('Failed to fetch release info:', error);
// Keep fallback links (releases page) on error
});
}, []);
const features = [
{
title: 'Near-Perfect Voice Cloning',
@@ -64,20 +87,34 @@ export default function Home() {
priority
/>
</div>
<h1 className="text-5xl sm:text-6xl md:text-7xl lg:text-8xl font-bold leading-tight text-left">
<h1 className="text-5xl sm:text-6xl md:text-7xl lg:text-8xl font-bold leading-tight text-center lg:text-left">
Voicebox
</h1>
<p className="text-lg sm:text-xl md:text-2xl text-foreground/70 max-w-xl text-left">
<p className="text-lg sm:text-xl md:text-2xl text-foreground/70 max-w-xl text-center lg:text-left mx-auto lg:mx-0">
Open source voice cloning powered by Qwen3-TTS. Create natural-sounding speech from
text with near-perfect voice replication.
</p>
{/* Mobile: centered screenshot above download buttons */}
<div className="flex justify-center lg:hidden my-8">
<div className="w-full max-w-2xl">
<Image
src="/VoiceBoxAppScreenshot.webp"
alt="Voicebox Application Screenshot"
width={1920}
height={1080}
className="w-full h-auto rounded-lg shadow-lg"
priority
/>
</div>
</div>
{/* Download buttons under left content */}
<div className="space-y-4 pt-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 w-full max-w-2xl">
<Button asChild size="lg" className="w-full px-0">
<a
href={DOWNLOAD_LINKS.macArm}
href={downloadLinks.macArm}
download
className="flex items-center w-full relative"
>
@@ -90,7 +127,7 @@ export default function Home() {
</Button>
<Button asChild size="lg" className="w-full px-0">
<a
href={DOWNLOAD_LINKS.macIntel}
href={downloadLinks.macIntel}
download
className="flex items-center w-full relative"
>
@@ -103,7 +140,7 @@ export default function Home() {
</Button>
<Button asChild size="lg" className="w-full px-0">
<a
href={DOWNLOAD_LINKS.windows}
href={downloadLinks.windows}
download
className="flex items-center w-full relative"
>
@@ -116,7 +153,7 @@ export default function Home() {
</Button>
<Button asChild size="lg" className="w-full px-0">
<a
href={DOWNLOAD_LINKS.linux}
href={downloadLinks.linux}
download
className="flex items-center w-full relative"
>
@@ -137,7 +174,7 @@ export default function Home() {
</div>
</div>
{/* Right side - Large screenshot positioned off-screen */}
{/* Desktop: Large screenshot positioned off-screen */}
<div className="hidden lg:block relative">
<div className="absolute right-0 top-0 -mt-10 w-[200%] -mr-[100%]">
<Image
+10 -10
View File
@@ -1,16 +1,16 @@
// Download links for voicebox releases
// Update these when new releases are published
// These are fallback values - link to releases page if API fails
export const LATEST_VERSION = 'v0.1.0';
export const GITHUB_REPO = 'https://github.com/jamiepine/voicebox';
export const GITHUB_RELEASES_PAGE = `${GITHUB_REPO}/releases`;
export const DOWNLOAD_LINKS = {
macArm:
'https://github.com/jamiepine/voicebox/releases/download/v0.1.0/voicebox_0.1.0_aarch64.dmg',
macIntel:
'https://github.com/jamiepine/voicebox/releases/download/v0.1.0/voicebox_0.1.0_x64.dmg',
windows:
'https://github.com/jamiepine/voicebox/releases/download/v0.1.0/voicebox_0.1.0_x64_en-US.msi',
linux:
'https://github.com/jamiepine/voicebox/releases/download/v0.1.0/voicebox_x86_64-unknown-linux-gnu.AppImage',
macArm: GITHUB_RELEASES_PAGE,
macIntel: GITHUB_RELEASES_PAGE,
windows: GITHUB_RELEASES_PAGE,
linux: GITHUB_RELEASES_PAGE,
} as const;
export const GITHUB_REPO = 'https://github.com/jamiepine/voicebox';
// Export function to get dynamic download links
export { getLatestRelease } from './releases';
+88
View File
@@ -0,0 +1,88 @@
// Fetch latest release information from GitHub
export interface DownloadLinks {
macArm: string;
macIntel: string;
windows: string;
linux: string;
}
export interface ReleaseInfo {
version: string;
downloadLinks: DownloadLinks;
}
const GITHUB_REPO = 'jamiepine/voicebox';
const GITHUB_API_BASE = 'https://api.github.com';
// Cache for release info (in-memory cache, resets on server restart)
let cachedReleaseInfo: ReleaseInfo | null = null;
let cacheTimestamp: number = 0;
const CACHE_DURATION = 1000 * 60 * 10; // 10 minutes
/**
* Fetches the latest release from GitHub and extracts download links
*/
export async function getLatestRelease(): Promise<ReleaseInfo> {
// Return cached data if still valid
const now = Date.now();
if (cachedReleaseInfo && now - cacheTimestamp < CACHE_DURATION) {
return cachedReleaseInfo;
}
try {
const response = await fetch(`${GITHUB_API_BASE}/repos/${GITHUB_REPO}/releases/latest`, {
next: { revalidate: 600 }, // Revalidate every 10 minutes
headers: {
Accept: 'application/vnd.github.v3+json',
},
});
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status}`);
}
const release = await response.json();
const version = release.tag_name;
const assets = release.assets || [];
// Extract download links based on file patterns
const downloadLinks: Partial<DownloadLinks> = {};
for (const asset of assets) {
const name = asset.name.toLowerCase();
const url = asset.browser_download_url;
if (name.includes('aarch64') || name.includes('arm64')) {
downloadLinks.macArm = url;
} else if (name.includes('x64') && name.includes('.dmg')) {
downloadLinks.macIntel = url;
} else if (name.includes('.msi')) {
downloadLinks.windows = url;
} else if (name.includes('.appimage') || name.includes('.deb')) {
downloadLinks.linux = url;
}
}
// Fallback: construct URLs if not found in assets
const baseUrl = `https://github.com/${GITHUB_REPO}/releases/download/${version}`;
const releaseInfo: ReleaseInfo = {
version,
downloadLinks: {
macArm: downloadLinks.macArm || `${baseUrl}/voicebox_${version.replace('v', '')}_aarch64.dmg`,
macIntel: downloadLinks.macIntel || `${baseUrl}/voicebox_${version.replace('v', '')}_x64.dmg`,
windows: downloadLinks.windows || `${baseUrl}/voicebox_${version.replace('v', '')}_x64_en-US.msi`,
linux: downloadLinks.linux || `${baseUrl}/voicebox_x86_64-unknown-linux-gnu.AppImage`,
},
};
// Update cache
cachedReleaseInfo = releaseInfo;
cacheTimestamp = now;
return releaseInfo;
} catch (error) {
console.error('Failed to fetch latest release:', error);
throw error;
}
}