feat(web): add 8 new SEO-optimized pages for organic traffic (#257)
* feat(web): add 8 new SEO-optimized pages for organic traffic ## New Pages Added | Page | Purpose | SEO Value | |------|---------|-----------| | `/languages/` | Multilingual support (9+ languages) | International SEO, language-specific keywords | | `/wayland/` | Wayland support guide | Technical SEO, Linux display server queries | | `/rsi-prevention/` | Health & accessibility focus | High-intent health keywords | | `/gpu-acceleration/` | GPU/Vulkan support | Performance-focused technical SEO | | `/open-source/` | FOSS & privacy positioning | Open source community keywords | | `/autostart/` | v0.7.0 feature highlight | Linux desktop integration keywords | | `/for-developers/` | Developer use cases | Developer productivity keywords | | `/offline/` | Privacy-first positioning | Privacy-conscious user keywords | ## Navigation Improvements - Redesigned header with hamburger menu for mobile - Categorized navigation with 5 sections: Get Started, Features, Use Cases, Privacy & Ethics, Support - Added breadcrumbs for better UX and SEO - Footer with complete site map organized by category ## Technical Updates - Updated sitemap.xml with all 21 pages - All pages include JSON-LD structured data (Article schema) - Full SEO metadata with targeted keywords per page ## Build Verification - All 21 pages generated successfully (including dynamic install/[distro] pages) - Build passes without errors * feat(web): add dropdown navigation to SEO subpages - Desktop: 5 dropdown menus (Get Started, Features, Use Cases, Privacy & Ethics, Support) - Mobile: Hamburger menu with categorized pages - All pages now accessible from any SEO subpage - Click outside to close dropdowns - Breadcrumbs and footer links remain unchanged * fix(web): make SEO header dropdowns open on hover and focus * fix(web): remove dead zone between dropdown trigger and menu
This commit is contained in:
Vendored
+1
@@ -1,5 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "vocalinux-website",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vocalinux-website",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"dependencies": {
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/react": "^18.3.3",
|
||||
|
||||
+62
-13
@@ -2,79 +2,128 @@
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://vocalinux.com/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/install/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/install/ubuntu/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/install/fedora/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/install/arch/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/compare/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/changelog/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/faq/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/privacy/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/shortcuts/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/troubleshooting/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/use-cases/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/alternatives/</loc>
|
||||
<lastmod>2026-02-19</lastmod>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<!-- New SEO pages -->
|
||||
<url>
|
||||
<loc>https://vocalinux.com/languages/</loc>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/wayland/</loc>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/rsi-prevention/</loc>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/gpu-acceleration/</loc>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/open-source/</loc>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/autostart/</loc>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/for-developers/</loc>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vocalinux.com/offline/</loc>
|
||||
<lastmod>2026-02-22</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
import Link from "next/link";
|
||||
import { type Metadata } from "next";
|
||||
import { CheckCircle2, ChevronRight, Loader2, Play, Power, Settings, Terminal } from "lucide-react";
|
||||
import { SeoSubpageShell } from "@/components/seo-subpage-shell";
|
||||
import { absoluteUrl, buildPageMetadata } from "@/lib/seo";
|
||||
|
||||
const desktopEnvironments = [
|
||||
{
|
||||
name: "GNOME",
|
||||
autostartMethod: "XDG Autostart",
|
||||
supported: true,
|
||||
notes: "Default on Ubuntu, Fedora, Debian",
|
||||
},
|
||||
{
|
||||
name: "KDE Plasma",
|
||||
autostartMethod: "XDG Autostart",
|
||||
supported: true,
|
||||
notes: "Full support via System Settings",
|
||||
},
|
||||
{
|
||||
name: "Xfce",
|
||||
autostartMethod: "XDG Autostart",
|
||||
supported: true,
|
||||
notes: "Settings → Session and Startup",
|
||||
},
|
||||
{
|
||||
name: "Cinnamon",
|
||||
autostartMethod: "XDG Autostart",
|
||||
supported: true,
|
||||
notes: "System Settings → Startup Applications",
|
||||
},
|
||||
{
|
||||
name: "MATE",
|
||||
autostartMethod: "XDG Autostart",
|
||||
supported: true,
|
||||
notes: "Control Center → Startup Applications",
|
||||
},
|
||||
{
|
||||
name: "LXQt",
|
||||
autostartMethod: "XDG Autostart",
|
||||
supported: true,
|
||||
notes: "Preferences → LXQt Settings → Autostart",
|
||||
},
|
||||
{
|
||||
name: "Sway",
|
||||
autostartMethod: "XDG Autostart / config",
|
||||
supported: true,
|
||||
notes: "Use dex or exec in config",
|
||||
},
|
||||
{
|
||||
name: "Hyprland",
|
||||
autostartMethod: "XDG Autostart / config",
|
||||
supported: true,
|
||||
notes: "exec-once in config or XDG",
|
||||
},
|
||||
];
|
||||
|
||||
const howItWorks = [
|
||||
{
|
||||
step: 1,
|
||||
title: "Enable in Vocalinux",
|
||||
description: "Open Vocalinux and enable 'Start on Login' via the tray menu or Settings dialog.",
|
||||
code: null,
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: "Desktop Entry Created",
|
||||
description: "Vocalinux creates a .desktop file in your autostart directory:",
|
||||
code: "~/.config/autostart/vocalinux.desktop",
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: "Auto-launch on Login",
|
||||
description: "Your desktop environment reads this file and launches Vocalinux when you log in.",
|
||||
code: null,
|
||||
},
|
||||
];
|
||||
|
||||
const managementMethods = [
|
||||
{
|
||||
method: "System Tray",
|
||||
description: "Right-click the Vocalinux tray icon → Start on Login",
|
||||
icon: Power,
|
||||
},
|
||||
{
|
||||
method: "Settings Dialog",
|
||||
description: "Open Settings → General → Start on Login",
|
||||
icon: Settings,
|
||||
},
|
||||
{
|
||||
method: "Command Line",
|
||||
description: "Create/remove the autostart file manually",
|
||||
icon: Terminal,
|
||||
},
|
||||
];
|
||||
|
||||
export const metadata: Metadata = buildPageMetadata({
|
||||
title: "Autostart Voice Dictation on Linux Login | Vocalinux",
|
||||
description:
|
||||
"Configure Vocalinux to start automatically when you log in to Linux. XDG autostart support for GNOME, KDE, Xfce, and all major desktop environments.",
|
||||
path: "/autostart",
|
||||
keywords: [
|
||||
"Linux autostart voice dictation",
|
||||
"auto start on login Linux",
|
||||
"XDG autostart desktop entry",
|
||||
"GNOME startup applications",
|
||||
"KDE autostart",
|
||||
"voice dictation auto launch",
|
||||
"Linux desktop autostart",
|
||||
],
|
||||
});
|
||||
|
||||
export default function AutostartPage() {
|
||||
const articleJsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
headline: "Autostart Voice Dictation on Linux Login",
|
||||
description:
|
||||
"How to configure Vocalinux to start automatically on Linux login. XDG autostart guide for all desktop environments.",
|
||||
dateModified: "2026-02-22",
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: "Jatin K Malik",
|
||||
url: "https://github.com/jatinkrmalik",
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Vocalinux",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: absoluteUrl("/vocalinux.png"),
|
||||
},
|
||||
},
|
||||
mainEntityOfPage: absoluteUrl("/autostart"),
|
||||
};
|
||||
|
||||
return (
|
||||
<SeoSubpageShell>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleJsonLd) }}
|
||||
/>
|
||||
|
||||
<section>
|
||||
<p className="mb-4 inline-flex items-center gap-2 rounded-full border border-primary/30 bg-primary/10 px-4 py-1.5 text-sm font-medium text-primary">
|
||||
<Power className="h-4 w-4" />
|
||||
New in v0.7.0
|
||||
</p>
|
||||
<h1 className="mb-5 text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl">
|
||||
Autostart Voice Dictation on Login
|
||||
</h1>
|
||||
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
|
||||
Have Vocalinux ready to go the moment you log in. Uses the Linux-standard XDG autostart
|
||||
mechanism—works with GNOME, KDE, Xfce, and all major desktop environments.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 grid gap-4 sm:grid-cols-3">
|
||||
{managementMethods.map((method) => {
|
||||
const Icon = method.icon;
|
||||
return (
|
||||
<div
|
||||
key={method.method}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<Icon className="mb-3 h-6 w-6 text-primary" />
|
||||
<h3 className="mb-2 font-semibold">{method.method}</h3>
|
||||
<p className="text-sm text-muted-foreground">{method.description}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-2xl font-bold">How It Works</h2>
|
||||
<div className="space-y-4">
|
||||
{howItWorks.map((step) => (
|
||||
<div
|
||||
key={step.step}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<div className="mb-2 flex items-center gap-3">
|
||||
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-primary text-sm font-bold text-primary-foreground">
|
||||
{step.step}
|
||||
</span>
|
||||
<h3 className="font-semibold">{step.title}</h3>
|
||||
</div>
|
||||
<p className="ml-10 text-sm text-muted-foreground">{step.description}</p>
|
||||
{step.code && (
|
||||
<div className="ml-10 mt-2 rounded-lg bg-zinc-950 p-2">
|
||||
<code className="text-sm text-green-400">{step.code}</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<h2 className="mb-6 text-2xl font-bold">Desktop Environment Compatibility</h2>
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
Vocalinux uses the XDG Autostart specification, which is supported by virtually all Linux
|
||||
desktop environments:
|
||||
</p>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-zinc-200 dark:border-zinc-700">
|
||||
<th className="pb-3 pr-4 font-semibold">Desktop</th>
|
||||
<th className="pb-3 pr-4 font-semibold">Method</th>
|
||||
<th className="pb-3 pr-4 font-semibold">Supported</th>
|
||||
<th className="pb-3 font-semibold">Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{desktopEnvironments.map((de) => (
|
||||
<tr key={de.name} className="border-b border-zinc-100 dark:border-zinc-700/70">
|
||||
<td className="py-3 pr-4 font-medium">{de.name}</td>
|
||||
<td className="py-3 pr-4 text-muted-foreground">{de.autostartMethod}</td>
|
||||
<td className="py-3 pr-4">
|
||||
{de.supported ? (
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700 dark:bg-green-900/30 dark:text-green-400">
|
||||
<CheckCircle2 className="h-3 w-3" />
|
||||
Yes
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">—</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-3 text-muted-foreground">{de.notes}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-blue-200 bg-blue-50 p-6 dark:border-blue-800 dark:bg-blue-900/20">
|
||||
<h2 className="mb-3 text-xl font-bold text-blue-800 dark:text-blue-300">
|
||||
Not a systemd Service
|
||||
</h2>
|
||||
<p className="text-sm text-blue-700 dark:text-blue-400">
|
||||
Vocalinux autostart uses <strong>XDG desktop entries</strong>, not systemd services. This
|
||||
means it starts as a regular desktop application in your graphical session—which is the
|
||||
correct way to launch GUI apps. No background services, no root required.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<h2 className="mb-4 text-2xl font-bold">Manual Setup (Advanced)</h2>
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
If you prefer to manage autostart manually or need to customize the startup behavior:
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<p className="mb-1 text-sm font-medium">Enable autostart:</p>
|
||||
<div className="rounded-lg bg-zinc-950 p-3">
|
||||
<code className="text-sm text-green-400">
|
||||
mkdir -p ~/.config/autostart && cp /usr/share/applications/vocalinux.desktop
|
||||
~/.config/autostart/
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-1 text-sm font-medium">Disable autostart:</p>
|
||||
<div className="rounded-lg bg-zinc-950 p-3">
|
||||
<code className="text-sm text-green-400">rm ~/.config/autostart/vocalinux.desktop</code>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-1 text-sm font-medium">Start minimized (skip welcome dialog):</p>
|
||||
<div className="rounded-lg bg-zinc-950 p-3">
|
||||
<code className="text-sm text-green-400">
|
||||
vocalinux --start-minimized
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl border border-zinc-200 bg-zinc-50 p-8 dark:border-zinc-700 dark:bg-zinc-900/60">
|
||||
<h2 className="mb-4 text-2xl font-bold">Ready to Automate?</h2>
|
||||
<p className="mb-6 text-muted-foreground">
|
||||
Install Vocalinux and enable autostart. Your voice dictation will be ready the moment you
|
||||
log in.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link
|
||||
href="/install/"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Install Now
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/faq/"
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-zinc-300 bg-white px-4 py-2 text-sm font-semibold hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-800 dark:hover:bg-zinc-700"
|
||||
>
|
||||
Common Questions
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</SeoSubpageShell>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
import Link from "next/link";
|
||||
import { type Metadata } from "next";
|
||||
import {
|
||||
Braces,
|
||||
CheckCircle2,
|
||||
ChevronRight,
|
||||
Code2,
|
||||
FileCode2,
|
||||
FileText,
|
||||
MessageSquare,
|
||||
Terminal,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { SeoSubpageShell } from "@/components/seo-subpage-shell";
|
||||
import { absoluteUrl, buildPageMetadata } from "@/lib/seo";
|
||||
|
||||
const useCases = [
|
||||
{
|
||||
title: "Code Comments & Documentation",
|
||||
description:
|
||||
"Dictate inline comments, JSDoc, docstrings, and README sections without leaving your editor.",
|
||||
example: "This function validates user input and returns true if valid",
|
||||
icon: FileCode2,
|
||||
iconColor: "text-blue-500",
|
||||
iconBg: "bg-blue-100 dark:bg-blue-900/30",
|
||||
},
|
||||
{
|
||||
title: "Git Commit Messages",
|
||||
description:
|
||||
"Speak your commit messages naturally—no more brief, unhelpful commits because typing is slow.",
|
||||
example: "fix authentication timeout when server is under heavy load",
|
||||
icon: Terminal,
|
||||
iconColor: "text-orange-500",
|
||||
iconBg: "bg-orange-100 dark:bg-orange-900/30",
|
||||
},
|
||||
{
|
||||
title: "Pull Request Descriptions",
|
||||
description:
|
||||
"Write detailed PR descriptions explaining your changes, rationale, and testing approach.",
|
||||
example: "This PR refactors the user service to improve testability and adds error handling",
|
||||
icon: FileText,
|
||||
iconColor: "text-purple-500",
|
||||
iconBg: "bg-purple-100 dark:bg-purple-900/30",
|
||||
},
|
||||
{
|
||||
title: "Technical Documentation",
|
||||
description:
|
||||
"Create API docs, architecture guides, and technical specifications at speaking speed.",
|
||||
example: "The API accepts a JSON payload with the following fields",
|
||||
icon: FileText,
|
||||
iconColor: "text-emerald-500",
|
||||
iconBg: "bg-emerald-100 dark:bg-emerald-900/30",
|
||||
},
|
||||
{
|
||||
title: "Code Review Comments",
|
||||
description:
|
||||
"Write thorough code review feedback without the fatigue of typing detailed explanations.",
|
||||
example: "Consider extracting this logic into a separate function for reusability",
|
||||
icon: MessageSquare,
|
||||
iconColor: "text-rose-500",
|
||||
iconBg: "bg-rose-100 dark:bg-rose-900/30",
|
||||
},
|
||||
{
|
||||
title: "Pair Programming Notes",
|
||||
description:
|
||||
"Dictate notes while pairing—keep your hands on the keyboard for code, use voice for meta.",
|
||||
example: "Remember to add unit tests for the edge case we just discussed",
|
||||
icon: Zap,
|
||||
iconColor: "text-amber-500",
|
||||
iconBg: "bg-amber-100 dark:bg-amber-900/30",
|
||||
},
|
||||
];
|
||||
|
||||
const voiceCommands = [
|
||||
{ command: "new line", action: "Insert line break" },
|
||||
{ command: "tab", action: "Insert tab character" },
|
||||
{ command: "open parenthesis", action: "Type (" },
|
||||
{ command: "close parenthesis", action: "Type )" },
|
||||
{ command: "open bracket", action: "Type [" },
|
||||
{ command: "close bracket", action: "Type ]" },
|
||||
{ command: "open brace", action: "Type {" },
|
||||
{ command: "close brace", action: "Type }" },
|
||||
{ command: "equals", action: "Type =" },
|
||||
{ command: "semicolon", action: "Type ;" },
|
||||
{ command: "colon", action: "Type :" },
|
||||
{ command: "quote", action: "Type \"" },
|
||||
{ command: "single quote", action: "Type '" },
|
||||
{ command: "underscore", action: "Type _" },
|
||||
{ command: "slash", action: "Type /" },
|
||||
{ command: "backslash", action: "Type \\" },
|
||||
{ command: "period / dot", action: "Type ." },
|
||||
{ command: "comma", action: "Type ," },
|
||||
];
|
||||
|
||||
const benefits = [
|
||||
{
|
||||
title: "Reduce RSI Risk",
|
||||
description: "Developers type 8+ hours daily. Voice dictation lets you rest your hands while staying productive.",
|
||||
},
|
||||
{
|
||||
title: "Maintain Flow State",
|
||||
description: "Speak your thoughts as they come—no context switching to the keyboard for comments and docs.",
|
||||
},
|
||||
{
|
||||
title: "Better Documentation",
|
||||
description: "When documentation is easy to write, you write more of it. Thorough docs become the default.",
|
||||
},
|
||||
{
|
||||
title: "Universal Compatibility",
|
||||
description: "Works in VS Code, JetBrains IDEs, Vim, terminal, browser—every Linux application.",
|
||||
},
|
||||
];
|
||||
|
||||
export const metadata: Metadata = buildPageMetadata({
|
||||
title: "Voice Dictation for Developers & Coders | Vocalinux",
|
||||
description:
|
||||
"Dictate code comments, documentation, and commit messages hands-free. Voice dictation for Linux developers—works in VS Code, JetBrains, terminal, and all apps.",
|
||||
path: "/for-developers",
|
||||
keywords: [
|
||||
"voice dictation for developers",
|
||||
"voice coding Linux",
|
||||
"dictate code comments",
|
||||
"hands-free programming",
|
||||
"developer productivity voice",
|
||||
"code documentation voice",
|
||||
"Linux voice programming",
|
||||
"VS Code voice dictation",
|
||||
],
|
||||
});
|
||||
|
||||
export default function ForDevelopersPage() {
|
||||
const articleJsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
headline: "Voice Dictation for Developers & Coders",
|
||||
description:
|
||||
"How Linux developers use Vocalinux for code comments, documentation, and commit messages.",
|
||||
dateModified: "2026-02-22",
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: "Jatin K Malik",
|
||||
url: "https://github.com/jatinkrmalik",
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Vocalinux",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: absoluteUrl("/vocalinux.png"),
|
||||
},
|
||||
},
|
||||
mainEntityOfPage: absoluteUrl("/for-developers"),
|
||||
};
|
||||
|
||||
return (
|
||||
<SeoSubpageShell>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleJsonLd) }}
|
||||
/>
|
||||
|
||||
<section>
|
||||
<p className="mb-4 inline-flex items-center gap-2 rounded-full border border-primary/30 bg-primary/10 px-4 py-1.5 text-sm font-medium text-primary">
|
||||
<Code2 className="h-4 w-4" />
|
||||
For Developers
|
||||
</p>
|
||||
<h1 className="mb-5 text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl">
|
||||
Voice Dictation for Developers
|
||||
</h1>
|
||||
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
|
||||
Write code comments, documentation, commit messages, and more—hands-free. Keep your hands
|
||||
on the keyboard for code, use your voice for everything else.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{benefits.map((benefit) => (
|
||||
<div
|
||||
key={benefit.title}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-5 text-center dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<h3 className="mb-2 font-semibold">{benefit.title}</h3>
|
||||
<p className="text-sm text-muted-foreground">{benefit.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-2xl font-bold">Developer Use Cases</h2>
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{useCases.map((useCase) => {
|
||||
const Icon = useCase.icon;
|
||||
return (
|
||||
<article
|
||||
key={useCase.title}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<div className="mb-3 flex items-center gap-3">
|
||||
<div className={`rounded-lg p-2 ${useCase.iconBg}`}>
|
||||
<Icon className={`h-4 w-4 ${useCase.iconColor}`} />
|
||||
</div>
|
||||
<h3 className="font-semibold">{useCase.title}</h3>
|
||||
</div>
|
||||
<p className="mb-3 text-sm text-muted-foreground">{useCase.description}</p>
|
||||
<div className="rounded-lg bg-zinc-950 p-3">
|
||||
<code className="text-xs text-green-400">"{useCase.example}"</code>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<h2 className="mb-4 text-2xl font-bold">Helpful Voice Commands for Coding</h2>
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
Use these voice commands for punctuation and symbols commonly used in code:
|
||||
</p>
|
||||
<div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{voiceCommands.map((cmd) => (
|
||||
<div
|
||||
key={cmd.command}
|
||||
className="flex items-center justify-between rounded-lg border border-zinc-100 bg-zinc-50 px-3 py-2 dark:border-zinc-700 dark:bg-zinc-900"
|
||||
>
|
||||
<kbd className="rounded bg-zinc-200 px-2 py-0.5 text-xs font-mono dark:bg-zinc-700">
|
||||
"{cmd.command}"
|
||||
</kbd>
|
||||
<span className="text-xs text-muted-foreground">{cmd.action}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="mt-4 text-sm text-muted-foreground">
|
||||
See the <Link href="/shortcuts/" className="text-primary hover:underline">full voice commands reference</Link> for more.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-blue-200 bg-blue-50 p-6 dark:border-blue-800 dark:bg-blue-900/20">
|
||||
<h2 className="mb-3 text-xl font-bold text-blue-800 dark:text-blue-300">
|
||||
Not Voice Coding—Voice Documentation
|
||||
</h2>
|
||||
<p className="text-sm text-blue-700 dark:text-blue-400">
|
||||
Vocalinux is designed for <strong>dictating text</strong>, not writing code syntax. For
|
||||
actual code, your keyboard is still faster. But for comments, docs, messages, and
|
||||
notes—voice is a game-changer. Use both together for maximum productivity.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<h2 className="mb-4 text-2xl font-bold">Works in Every IDE</h2>
|
||||
<div className="grid gap-4 sm:grid-cols-3">
|
||||
{[
|
||||
"VS Code",
|
||||
"JetBrains (IntelliJ, PyCharm, WebStorm)",
|
||||
"Vim / Neovim",
|
||||
"Emacs",
|
||||
"Sublime Text",
|
||||
"Terminal / Shell",
|
||||
"GitHub Web",
|
||||
"GitLab",
|
||||
"Jupyter Notebooks",
|
||||
].map((ide) => (
|
||||
<div
|
||||
key={ide}
|
||||
className="flex items-center gap-2 rounded-lg border border-zinc-100 bg-zinc-50 px-3 py-2 dark:border-zinc-700 dark:bg-zinc-900"
|
||||
>
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||
<span className="text-sm">{ide}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="mt-4 text-sm text-muted-foreground">
|
||||
Vocalinux works system-wide. If the app accepts text input, Vocalinux can dictate into it.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl border border-zinc-200 bg-zinc-50 p-8 dark:border-zinc-700 dark:bg-zinc-900/60">
|
||||
<h2 className="mb-4 text-2xl font-bold">Start Dictating Your Docs</h2>
|
||||
<p className="mb-6 text-muted-foreground">
|
||||
Install Vocalinux and start writing better documentation with less effort. Your future
|
||||
self (and your team) will thank you.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link
|
||||
href="/install/"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Install Now
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/rsi-prevention/"
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-zinc-300 bg-white px-4 py-2 text-sm font-semibold hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-800 dark:hover:bg-zinc-700"
|
||||
>
|
||||
RSI Prevention
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</SeoSubpageShell>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
import Link from "next/link";
|
||||
import { type Metadata } from "next";
|
||||
import {
|
||||
CheckCircle2,
|
||||
ChevronRight,
|
||||
Cpu,
|
||||
Gauge,
|
||||
MemoryStick,
|
||||
Monitor,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { SeoSubpageShell } from "@/components/seo-subpage-shell";
|
||||
import { absoluteUrl, buildPageMetadata } from "@/lib/seo";
|
||||
|
||||
const gpuBackends = [
|
||||
{
|
||||
name: "Vulkan",
|
||||
supportedGPUs: "AMD, Intel, NVIDIA",
|
||||
description:
|
||||
"Cross-platform GPU API supported by all major GPU vendors. The default and most compatible backend for whisper.cpp.",
|
||||
advantages: [
|
||||
"Works with AMD, Intel, and NVIDIA GPUs",
|
||||
"No vendor-specific drivers required",
|
||||
"Excellent Linux support",
|
||||
"Best option for AMD and Intel GPUs",
|
||||
],
|
||||
icon: Monitor,
|
||||
iconColor: "text-violet-500",
|
||||
iconBg: "bg-violet-100 dark:bg-violet-900/30",
|
||||
recommended: true,
|
||||
},
|
||||
{
|
||||
name: "CUDA",
|
||||
supportedGPUs: "NVIDIA only",
|
||||
description:
|
||||
"NVIDIA's proprietary GPU compute platform. High performance but requires NVIDIA GPU and CUDA toolkit.",
|
||||
advantages: [
|
||||
"Excellent performance on NVIDIA hardware",
|
||||
"Mature ecosystem and tooling",
|
||||
"Works with PyTorch-based Whisper engine",
|
||||
],
|
||||
icon: Cpu,
|
||||
iconColor: "text-green-500",
|
||||
iconBg: "bg-green-100 dark:bg-green-900/30",
|
||||
recommended: false,
|
||||
},
|
||||
{
|
||||
name: "CPU",
|
||||
supportedGPUs: "All systems",
|
||||
description:
|
||||
"Pure CPU processing. Slower but works on any system. Automatic fallback when GPU is unavailable.",
|
||||
advantages: [
|
||||
"Works on any hardware",
|
||||
"No GPU requirements",
|
||||
"Predictable performance",
|
||||
"Lowest power consumption option",
|
||||
],
|
||||
icon: MemoryStick,
|
||||
iconColor: "text-blue-500",
|
||||
iconBg: "bg-blue-100 dark:bg-blue-900/30",
|
||||
recommended: false,
|
||||
},
|
||||
];
|
||||
|
||||
const modelPerformance = [
|
||||
{
|
||||
model: "tiny",
|
||||
size: "~39MB",
|
||||
cpuSpeed: "~0.5s",
|
||||
gpuSpeed: "~0.2s",
|
||||
accuracy: "Good for real-time",
|
||||
recommended: "Real-time dictation, older hardware",
|
||||
},
|
||||
{
|
||||
model: "base",
|
||||
size: "~74MB",
|
||||
cpuSpeed: "~0.8s",
|
||||
gpuSpeed: "~0.3s",
|
||||
accuracy: "Better accuracy",
|
||||
recommended: "Balanced speed/accuracy",
|
||||
},
|
||||
{
|
||||
model: "small",
|
||||
size: "~244MB",
|
||||
cpuSpeed: "~1.5s",
|
||||
gpuSpeed: "~0.5s",
|
||||
accuracy: "High accuracy",
|
||||
recommended: "Most users, good balance",
|
||||
},
|
||||
{
|
||||
model: "medium",
|
||||
size: "~769MB",
|
||||
cpuSpeed: "~4s",
|
||||
gpuSpeed: "~1.2s",
|
||||
accuracy: "Very high accuracy",
|
||||
recommended: "Accuracy-focused use, with GPU",
|
||||
},
|
||||
{
|
||||
model: "large",
|
||||
size: "~1.5GB",
|
||||
cpuSpeed: "~8s",
|
||||
gpuSpeed: "~2.5s",
|
||||
accuracy: "Best accuracy",
|
||||
recommended: "Maximum accuracy, requires GPU",
|
||||
},
|
||||
];
|
||||
|
||||
const hardwareRequirements = [
|
||||
{
|
||||
category: "Minimum (CPU-only)",
|
||||
ram: "4GB RAM",
|
||||
storage: "200MB",
|
||||
gpu: "None required",
|
||||
performance: "Functional but slower processing",
|
||||
},
|
||||
{
|
||||
category: "Recommended (with GPU)",
|
||||
ram: "8GB RAM",
|
||||
storage: "500MB",
|
||||
gpu: "Any Vulkan-compatible GPU",
|
||||
performance: "Real-time dictation with small/base models",
|
||||
},
|
||||
{
|
||||
category: "Optimal",
|
||||
ram: "16GB+ RAM",
|
||||
storage: "2GB+",
|
||||
gpu: "NVIDIA RTX or AMD RX series",
|
||||
performance: "Real-time with any model including large",
|
||||
},
|
||||
];
|
||||
|
||||
export const metadata: Metadata = buildPageMetadata({
|
||||
title: "GPU Accelerated Voice Dictation - AMD, Intel, NVIDIA | Vocalinux",
|
||||
description:
|
||||
"GPU-accelerated speech recognition for Linux. Support for AMD, Intel, and NVIDIA GPUs via Vulkan. Compare CPU vs GPU performance and model sizes.",
|
||||
path: "/gpu-acceleration",
|
||||
keywords: [
|
||||
"GPU voice dictation",
|
||||
"Vulkan speech recognition",
|
||||
"AMD GPU dictation",
|
||||
"NVIDIA CUDA voice typing",
|
||||
"Intel GPU speech to text",
|
||||
"hardware accelerated dictation",
|
||||
"Linux GPU speech recognition",
|
||||
"whisper.cpp GPU",
|
||||
],
|
||||
});
|
||||
|
||||
export default function GpuAccelerationPage() {
|
||||
const articleJsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
headline: "GPU Accelerated Voice Dictation for Linux",
|
||||
description:
|
||||
"Complete guide to GPU acceleration in Vocalinux. Vulkan support for AMD, Intel, and NVIDIA GPUs.",
|
||||
dateModified: "2026-02-22",
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: "Jatin K Malik",
|
||||
url: "https://github.com/jatinkrmalik",
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Vocalinux",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: absoluteUrl("/vocalinux.png"),
|
||||
},
|
||||
},
|
||||
mainEntityOfPage: absoluteUrl("/gpu-acceleration"),
|
||||
};
|
||||
|
||||
return (
|
||||
<SeoSubpageShell>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleJsonLd) }}
|
||||
/>
|
||||
|
||||
<section>
|
||||
<p className="mb-4 inline-flex items-center gap-2 rounded-full border border-primary/30 bg-primary/10 px-4 py-1.5 text-sm font-medium text-primary">
|
||||
<Zap className="h-4 w-4" />
|
||||
Performance
|
||||
</p>
|
||||
<h1 className="mb-5 text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl">
|
||||
GPU Accelerated Voice Dictation
|
||||
</h1>
|
||||
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
|
||||
Leverage your GPU for faster speech recognition. Vocalinux supports AMD, Intel, and NVIDIA
|
||||
GPUs via Vulkan—with automatic fallback to CPU when needed.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-primary/20 bg-primary/5 p-6">
|
||||
<h2 className="mb-4 text-xl font-bold">Quick Check: Do You Have GPU Support?</h2>
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
Run this command to check if your system supports Vulkan GPU acceleration:
|
||||
</p>
|
||||
<div className="rounded-lg bg-zinc-950 p-4">
|
||||
<code className="text-sm text-green-400">vulkaninfo --summary | grep deviceName</code>
|
||||
</div>
|
||||
<p className="mt-3 text-sm text-muted-foreground">
|
||||
If you see your GPU name, you're ready for accelerated dictation!
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-2xl font-bold">Supported GPU Backends</h2>
|
||||
<div className="space-y-6">
|
||||
{gpuBackends.map((backend) => {
|
||||
const Icon = backend.icon;
|
||||
return (
|
||||
<article
|
||||
key={backend.name}
|
||||
className={`rounded-2xl border p-6 ${
|
||||
backend.recommended
|
||||
? "border-primary/50 bg-primary/5"
|
||||
: "border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-800"
|
||||
}`}
|
||||
>
|
||||
<div className="mb-4 flex flex-wrap items-start justify-between gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`rounded-lg p-2.5 ${backend.iconBg}`}>
|
||||
<Icon className={`h-5 w-5 ${backend.iconColor}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold">{backend.name}</h3>
|
||||
<p className="text-sm text-muted-foreground">{backend.supportedGPUs}</p>
|
||||
</div>
|
||||
</div>
|
||||
{backend.recommended && (
|
||||
<span className="rounded-full bg-primary px-3 py-1 text-xs font-semibold text-primary-foreground">
|
||||
Recommended
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="mb-4 text-sm text-muted-foreground">{backend.description}</p>
|
||||
<ul className="space-y-1.5">
|
||||
{backend.advantages.map((advantage) => (
|
||||
<li key={advantage} className="inline-flex items-start gap-2 text-sm">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
{advantage}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<h2 className="mb-6 text-2xl font-bold">Model Performance Comparison</h2>
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
Approximate transcription times for 5 seconds of audio. GPU times assume a mid-range
|
||||
Vulkan-compatible GPU.
|
||||
</p>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-zinc-200 dark:border-zinc-700">
|
||||
<th className="pb-3 pr-4 font-semibold">Model</th>
|
||||
<th className="pb-3 pr-4 font-semibold">Size</th>
|
||||
<th className="pb-3 pr-4 font-semibold">CPU Time</th>
|
||||
<th className="pb-3 pr-4 font-semibold">GPU Time</th>
|
||||
<th className="pb-3 pr-4 font-semibold">Accuracy</th>
|
||||
<th className="pb-3 font-semibold">Best For</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{modelPerformance.map((row) => (
|
||||
<tr key={row.model} className="border-b border-zinc-100 dark:border-zinc-700/70">
|
||||
<td className="py-3 pr-4 font-medium">{row.model}</td>
|
||||
<td className="py-3 pr-4 text-muted-foreground">{row.size}</td>
|
||||
<td className="py-3 pr-4 text-muted-foreground">{row.cpuSpeed}</td>
|
||||
<td className="py-3 pr-4 text-muted-foreground">{row.gpuSpeed}</td>
|
||||
<td className="py-3 pr-4 text-muted-foreground">{row.accuracy}</td>
|
||||
<td className="py-3 text-muted-foreground">{row.recommended}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-2xl font-bold">Hardware Requirements</h2>
|
||||
<div className="grid gap-6 sm:grid-cols-3">
|
||||
{hardwareRequirements.map((tier) => (
|
||||
<article
|
||||
key={tier.category}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<h3 className="mb-3 font-semibold">{tier.category}</h3>
|
||||
<dl className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-muted-foreground">RAM</dt>
|
||||
<dd className="font-medium">{tier.ram}</dd>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-muted-foreground">Storage</dt>
|
||||
<dd className="font-medium">{tier.storage}</dd>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-muted-foreground">GPU</dt>
|
||||
<dd className="font-medium">{tier.gpu}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<p className="mt-4 text-xs text-muted-foreground">{tier.performance}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-amber-200 bg-amber-50 p-6 dark:border-amber-800 dark:bg-amber-900/20">
|
||||
<div className="flex items-start gap-3">
|
||||
<Gauge className="mt-0.5 h-5 w-5 flex-shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<div>
|
||||
<h3 className="mb-2 font-semibold text-amber-800 dark:text-amber-300">
|
||||
Intel GPU Note
|
||||
</h3>
|
||||
<p className="text-sm text-amber-700 dark:text-amber-400">
|
||||
Some Intel integrated GPUs don't fully support the compute features needed for
|
||||
whisper.cpp GPU acceleration. Vocalinux v0.7.0+ automatically detects incompatible
|
||||
Intel GPUs and falls back to CPU processing for reliability.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl border border-zinc-200 bg-zinc-50 p-8 dark:border-zinc-700 dark:bg-zinc-900/60">
|
||||
<h2 className="mb-4 text-2xl font-bold">Get Started with GPU Acceleration</h2>
|
||||
<p className="mb-6 text-muted-foreground">
|
||||
The installer automatically detects your GPU and configures the best backend. Just run
|
||||
and go.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link
|
||||
href="/install/"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Install Now
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/compare/"
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-zinc-300 bg-white px-4 py-2 text-sm font-semibold hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-800 dark:hover:bg-zinc-700"
|
||||
>
|
||||
Compare Engines
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</SeoSubpageShell>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
import Link from "next/link";
|
||||
import { type Metadata } from "next";
|
||||
import { CheckCircle2, ChevronRight, Globe, Languages, Mic } from "lucide-react";
|
||||
import { SeoSubpageShell } from "@/components/seo-subpage-shell";
|
||||
import { absoluteUrl, buildPageMetadata } from "@/lib/seo";
|
||||
|
||||
const supportedLanguages = [
|
||||
{
|
||||
code: "en",
|
||||
name: "English",
|
||||
nativeName: "English",
|
||||
flag: "🇺🇸",
|
||||
description: "Full support across all engines (whisper.cpp, Whisper, VOSK)",
|
||||
engines: ["whisper.cpp", "Whisper", "VOSK"],
|
||||
modelSizes: ["tiny", "base", "small", "medium", "large"],
|
||||
},
|
||||
{
|
||||
code: "es",
|
||||
name: "Spanish",
|
||||
nativeName: "Español",
|
||||
flag: "🇪🇸",
|
||||
description: "Complete Spanish language support for dictation",
|
||||
engines: ["whisper.cpp", "Whisper", "VOSK"],
|
||||
modelSizes: ["tiny", "base", "small", "medium", "large"],
|
||||
},
|
||||
{
|
||||
code: "fr",
|
||||
name: "French",
|
||||
nativeName: "Français",
|
||||
flag: "🇫🇷",
|
||||
description: "French language voice typing and transcription",
|
||||
engines: ["whisper.cpp", "Whisper", "VOSK"],
|
||||
modelSizes: ["tiny", "base", "small", "medium", "large"],
|
||||
},
|
||||
{
|
||||
code: "de",
|
||||
name: "German",
|
||||
nativeName: "Deutsch",
|
||||
flag: "🇩🇪",
|
||||
description: "German dictation with high accuracy",
|
||||
engines: ["whisper.cpp", "Whisper", "VOSK"],
|
||||
modelSizes: ["tiny", "base", "small", "medium", "large"],
|
||||
},
|
||||
{
|
||||
code: "it",
|
||||
name: "Italian",
|
||||
nativeName: "Italiano",
|
||||
flag: "🇮🇹",
|
||||
description: "Italian voice recognition and text dictation",
|
||||
engines: ["whisper.cpp", "Whisper", "VOSK"],
|
||||
modelSizes: ["tiny", "base", "small", "medium", "large"],
|
||||
},
|
||||
{
|
||||
code: "pt",
|
||||
name: "Portuguese",
|
||||
nativeName: "Português",
|
||||
flag: "🇧🇷",
|
||||
description: "Brazilian and European Portuguese support",
|
||||
engines: ["whisper.cpp", "Whisper", "VOSK"],
|
||||
modelSizes: ["tiny", "base", "small", "medium", "large"],
|
||||
},
|
||||
{
|
||||
code: "ru",
|
||||
name: "Russian",
|
||||
nativeName: "Русский",
|
||||
flag: "🇷🇺",
|
||||
description: "Russian language speech-to-text transcription",
|
||||
engines: ["whisper.cpp", "Whisper", "VOSK"],
|
||||
modelSizes: ["tiny", "base", "small", "medium", "large"],
|
||||
},
|
||||
{
|
||||
code: "zh",
|
||||
name: "Chinese",
|
||||
nativeName: "中文",
|
||||
flag: "🇨🇳",
|
||||
description: "Mandarin Chinese voice dictation support",
|
||||
engines: ["whisper.cpp", "Whisper", "VOSK"],
|
||||
modelSizes: ["tiny", "base", "small", "medium", "large"],
|
||||
},
|
||||
{
|
||||
code: "hi",
|
||||
name: "Hindi",
|
||||
nativeName: "हिन्दी",
|
||||
flag: "🇮🇳",
|
||||
description: "Hindi language voice typing and transcription",
|
||||
engines: ["whisper.cpp", "Whisper", "VOSK"],
|
||||
modelSizes: ["tiny", "base", "small", "medium", "large"],
|
||||
},
|
||||
];
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: "Automatic Language Detection",
|
||||
description:
|
||||
"Vocalinux can automatically detect the language you're speaking, no manual switching required.",
|
||||
icon: Globe,
|
||||
},
|
||||
{
|
||||
title: "Multi-Engine Support",
|
||||
description:
|
||||
"All supported languages work across whisper.cpp, OpenAI Whisper, and VOSK engines.",
|
||||
icon: Mic,
|
||||
},
|
||||
{
|
||||
title: "Offline Processing",
|
||||
description:
|
||||
"All language processing happens locally on your machine. No cloud, no data upload.",
|
||||
icon: CheckCircle2,
|
||||
},
|
||||
];
|
||||
|
||||
export const metadata: Metadata = buildPageMetadata({
|
||||
title: "Multilingual Voice Dictation - Spanish, French, German, & More | Vocalinux",
|
||||
description:
|
||||
"Dictate in 9+ languages including Spanish, French, German, Italian, Portuguese, Russian, Chinese, and Hindi. Free offline multilingual voice typing for Linux.",
|
||||
path: "/languages",
|
||||
keywords: [
|
||||
"multilingual voice dictation",
|
||||
"Spanish voice typing",
|
||||
"French speech to text",
|
||||
"German dictation software",
|
||||
"Italian voice recognition",
|
||||
"Portuguese voice typing",
|
||||
"Russian speech recognition",
|
||||
"Chinese dictation",
|
||||
"Hindi voice typing",
|
||||
"Linux multilingual dictation",
|
||||
],
|
||||
});
|
||||
|
||||
export default function LanguagesPage() {
|
||||
const articleJsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
headline: "Multilingual Voice Dictation for Linux - 9+ Languages Supported",
|
||||
description:
|
||||
"Complete guide to Vocalinux multilingual voice dictation. Dictate in Spanish, French, German, Italian, Portuguese, Russian, Chinese, Hindi, and more.",
|
||||
dateModified: "2026-02-22",
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: "Jatin K Malik",
|
||||
url: "https://github.com/jatinkrmalik",
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Vocalinux",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: absoluteUrl("/vocalinux.png"),
|
||||
},
|
||||
},
|
||||
mainEntityOfPage: absoluteUrl("/languages"),
|
||||
};
|
||||
|
||||
return (
|
||||
<SeoSubpageShell>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleJsonLd) }}
|
||||
/>
|
||||
|
||||
<section>
|
||||
<p className="mb-4 inline-flex items-center gap-2 rounded-full border border-primary/30 bg-primary/10 px-4 py-1.5 text-sm font-medium text-primary">
|
||||
<Languages className="h-4 w-4" />
|
||||
9+ Languages
|
||||
</p>
|
||||
<h1 className="mb-5 text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl">
|
||||
Multilingual Voice Dictation for Linux
|
||||
</h1>
|
||||
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
|
||||
Dictate in your native language. Vocalinux supports 9+ languages with full offline
|
||||
processing—no cloud required, no data leaves your machine.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 grid gap-6 sm:grid-cols-3">
|
||||
{features.map((feature) => {
|
||||
const Icon = feature.icon;
|
||||
return (
|
||||
<div
|
||||
key={feature.title}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<Icon className="mb-3 h-8 w-8 text-primary" />
|
||||
<h3 className="mb-2 font-semibold">{feature.title}</h3>
|
||||
<p className="text-sm text-muted-foreground">{feature.description}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-2xl font-bold">Supported Languages</h2>
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{supportedLanguages.map((lang) => (
|
||||
<article
|
||||
key={lang.code}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<div className="mb-3 flex items-center gap-3">
|
||||
<span className="text-3xl">{lang.flag}</span>
|
||||
<div>
|
||||
<h3 className="font-semibold">{lang.name}</h3>
|
||||
<p className="text-sm text-muted-foreground">{lang.nativeName}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mb-3 text-sm text-muted-foreground">{lang.description}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{lang.engines.map((engine) => (
|
||||
<span
|
||||
key={engine}
|
||||
className="rounded-full bg-zinc-100 px-2 py-0.5 text-xs font-medium dark:bg-zinc-700"
|
||||
>
|
||||
{engine}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<h2 className="mb-4 text-2xl font-bold">How Language Support Works</h2>
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<h3 className="mb-3 font-semibold">Language Selection</h3>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
Select your language in Settings → Speech Engine → Language
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
Use "Auto" for automatic language detection (whisper.cpp/Whisper)
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
Switch languages on-the-fly without restarting
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="mb-3 font-semibold">Model Selection</h3>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
Larger models provide better accuracy for all languages
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
"tiny" model works for all supported languages
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
Models are downloaded automatically on first use
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl border border-zinc-200 bg-zinc-50 p-8 dark:border-zinc-700 dark:bg-zinc-900/60">
|
||||
<h2 className="mb-4 text-2xl font-bold">Ready to Dictate in Your Language?</h2>
|
||||
<p className="mb-6 text-muted-foreground">
|
||||
Install Vocalinux and start dictating in your preferred language. All processing happens
|
||||
locally on your Linux machine.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link
|
||||
href="/install/"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Install Now
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/compare/"
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-zinc-300 bg-white px-4 py-2 text-sm font-semibold hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-800 dark:hover:bg-zinc-700"
|
||||
>
|
||||
Compare Engines
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</SeoSubpageShell>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
import Link from "next/link";
|
||||
import { type Metadata } from "next";
|
||||
import {
|
||||
CheckCircle2,
|
||||
ChevronRight,
|
||||
CloudOff,
|
||||
Database,
|
||||
Lock,
|
||||
Shield,
|
||||
WifiOff,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
import { SeoSubpageShell } from "@/components/seo-subpage-shell";
|
||||
import { absoluteUrl, buildPageMetadata } from "@/lib/seo";
|
||||
|
||||
const benefits = [
|
||||
{
|
||||
title: "No Internet Required",
|
||||
description:
|
||||
"Works on airplanes, in tunnels, on remote farms. Your voice never needs to leave your machine.",
|
||||
icon: WifiOff,
|
||||
iconColor: "text-blue-500",
|
||||
iconBg: "bg-blue-100 dark:bg-blue-900/30",
|
||||
},
|
||||
{
|
||||
title: "Zero Data Collection",
|
||||
description:
|
||||
"No telemetry, no analytics, no usage tracking. There's no server to send data to even if we wanted to.",
|
||||
icon: Database,
|
||||
iconColor: "text-emerald-500",
|
||||
iconBg: "bg-emerald-100 dark:bg-emerald-900/30",
|
||||
},
|
||||
{
|
||||
title: "No Account Required",
|
||||
description:
|
||||
"No signup, no login, no password to forget. Download and run—that's it.",
|
||||
icon: Lock,
|
||||
iconColor: "text-violet-500",
|
||||
iconBg: "bg-violet-100 dark:bg-violet-900/30",
|
||||
},
|
||||
{
|
||||
title: "Your Voice Stays Yours",
|
||||
description:
|
||||
"Voice data is biometric. Your voice patterns, accent, and speech habits are yours alone.",
|
||||
icon: Shield,
|
||||
iconColor: "text-rose-500",
|
||||
iconBg: "bg-rose-100 dark:bg-rose-900/30",
|
||||
},
|
||||
];
|
||||
|
||||
const vsCloud = [
|
||||
{ feature: "Requires internet", offline: false, cloud: true },
|
||||
{ feature: "Voice data uploaded to servers", offline: false, cloud: true },
|
||||
{ feature: "Account/signup required", offline: false, cloud: true },
|
||||
{ feature: "Subscription cost", offline: false, cloud: true },
|
||||
{ feature: "Works in airplane mode", offline: true, cloud: false },
|
||||
{ feature: "GDPR/privacy compliant by default", offline: true, cloud: false },
|
||||
{ feature: "Vendor can shut down service", offline: false, cloud: true },
|
||||
{ feature: "Sensitive content safe", offline: true, cloud: false },
|
||||
];
|
||||
|
||||
const sensitiveUseCases = [
|
||||
{
|
||||
title: "Medical Professionals",
|
||||
description: "Patient notes, diagnoses, and medical records must stay private.",
|
||||
},
|
||||
{
|
||||
title: "Legal Professionals",
|
||||
description: "Attorney-client privilege requires absolute data confidentiality.",
|
||||
},
|
||||
{
|
||||
title: "Journalists & Sources",
|
||||
description: "Protecting sources means keeping all communications offline.",
|
||||
},
|
||||
{
|
||||
title: "Corporate Trade Secrets",
|
||||
description: "Product plans, financial data, and strategies shouldn't leave your network.",
|
||||
},
|
||||
{
|
||||
title: "Government & Defense",
|
||||
description: "Classified or sensitive government work requires air-gapped solutions.",
|
||||
},
|
||||
{
|
||||
title: "Personal Journals",
|
||||
description: "Your private thoughts and reflections deserve complete privacy.",
|
||||
},
|
||||
];
|
||||
|
||||
export const metadata: Metadata = buildPageMetadata({
|
||||
title: "100% Offline Voice Dictation - No Cloud, No Upload | Vocalinux",
|
||||
description:
|
||||
"Completely offline voice dictation for Linux. No cloud processing, no data upload, no account required. Your voice data never leaves your computer.",
|
||||
path: "/offline",
|
||||
keywords: [
|
||||
"offline voice dictation",
|
||||
"no cloud speech recognition",
|
||||
"private voice typing",
|
||||
"local speech to text",
|
||||
"offline dictation software",
|
||||
"no internet voice recognition",
|
||||
"airplane mode dictation",
|
||||
"privacy-first speech recognition",
|
||||
],
|
||||
});
|
||||
|
||||
export default function OfflinePage() {
|
||||
const articleJsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
headline: "100% Offline Voice Dictation for Linux",
|
||||
description:
|
||||
"Why Vocalinux is completely offline. No cloud processing, no data upload, maximum privacy.",
|
||||
dateModified: "2026-02-22",
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: "Jatin K Malik",
|
||||
url: "https://github.com/jatinkrmalik",
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Vocalinux",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: absoluteUrl("/vocalinux.png"),
|
||||
},
|
||||
},
|
||||
mainEntityOfPage: absoluteUrl("/offline"),
|
||||
};
|
||||
|
||||
return (
|
||||
<SeoSubpageShell>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleJsonLd) }}
|
||||
/>
|
||||
|
||||
<section>
|
||||
<p className="mb-4 inline-flex items-center gap-2 rounded-full border border-primary/30 bg-primary/10 px-4 py-1.5 text-sm font-medium text-primary">
|
||||
<CloudOff className="h-4 w-4" />
|
||||
100% Offline
|
||||
</p>
|
||||
<h1 className="mb-5 text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl">
|
||||
Offline Voice Dictation
|
||||
</h1>
|
||||
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
|
||||
Your voice never leaves your computer. All speech recognition happens locally using
|
||||
whisper.cpp, VOSK, or OpenAI Whisper models running on your hardware. No cloud. No
|
||||
upload. No exceptions.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{benefits.map((benefit) => {
|
||||
const Icon = benefit.icon;
|
||||
return (
|
||||
<article
|
||||
key={benefit.title}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<div className={`mb-3 inline-flex rounded-lg p-2 ${benefit.iconBg}`}>
|
||||
<Icon className={`h-5 w-5 ${benefit.iconColor}`} />
|
||||
</div>
|
||||
<h3 className="mb-2 font-semibold">{benefit.title}</h3>
|
||||
<p className="text-sm text-muted-foreground">{benefit.description}</p>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<h2 className="mb-6 text-2xl font-bold">Offline vs Cloud Dictation</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-zinc-200 dark:border-zinc-700">
|
||||
<th className="pb-3 pr-4 font-semibold">Feature</th>
|
||||
<th className="pb-3 pr-4 font-semibold text-center">Vocalinux (Offline)</th>
|
||||
<th className="pb-3 font-semibold text-center">Cloud Dictation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{vsCloud.map((row) => (
|
||||
<tr key={row.feature} className="border-b border-zinc-100 dark:border-zinc-700/70">
|
||||
<td className="py-3 pr-4">{row.feature}</td>
|
||||
<td className="py-3 pr-4 text-center">
|
||||
{row.offline ? (
|
||||
<CheckCircle2 className="mx-auto h-5 w-5 text-green-500" />
|
||||
) : (
|
||||
<XCircle className="mx-auto h-5 w-5 text-red-500" />
|
||||
)}
|
||||
</td>
|
||||
<td className="py-3 text-center">
|
||||
{row.cloud ? (
|
||||
<CheckCircle2 className="mx-auto h-5 w-5 text-green-500" />
|
||||
) : (
|
||||
<XCircle className="mx-auto h-5 w-5 text-red-500" />
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-2xl font-bold">When Offline Matters Most</h2>
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{sensitiveUseCases.map((useCase) => (
|
||||
<div
|
||||
key={useCase.title}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<h3 className="mb-2 font-semibold">{useCase.title}</h3>
|
||||
<p className="text-sm text-muted-foreground">{useCase.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-emerald-200 bg-emerald-50 p-6 dark:border-emerald-800 dark:bg-emerald-900/20">
|
||||
<h2 className="mb-3 text-xl font-bold text-emerald-800 dark:text-emerald-300">
|
||||
How Offline Processing Works
|
||||
</h2>
|
||||
<p className="mb-4 text-sm text-emerald-700 dark:text-emerald-400">
|
||||
Vocalinux uses speech recognition models that run entirely on your CPU or GPU:
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm text-emerald-700 dark:text-emerald-400">
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
<strong>whisper.cpp</strong> — C++ port of OpenAI Whisper, runs locally with GPU
|
||||
acceleration
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
<strong>VOSK</strong> — Lightweight offline engine, minimal resources
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
<strong>OpenAI Whisper</strong> — PyTorch-based, runs on NVIDIA GPU or CPU
|
||||
</li>
|
||||
</ul>
|
||||
<p className="mt-4 text-sm text-emerald-700 dark:text-emerald-400">
|
||||
Models are downloaded once (39MB-1.5GB depending on size), then all processing happens
|
||||
locally. No network requests during dictation.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-amber-200 bg-amber-50 p-6 dark:border-amber-800 dark:bg-amber-900/20">
|
||||
<h2 className="mb-3 text-xl font-bold text-amber-800 dark:text-amber-300">
|
||||
Verify It Yourself
|
||||
</h2>
|
||||
<p className="mb-3 text-sm text-amber-700 dark:text-amber-400">
|
||||
Don't trust—verify. Vocalinux is open source. You can:
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm text-amber-700 dark:text-amber-400">
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
Read every line of code on{" "}
|
||||
<a
|
||||
href="https://github.com/jatinkrmalik/vocalinux"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-semibold hover:underline"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
Run with <code className="rounded bg-amber-200 px-1 dark:bg-amber-900">--debug</code> to see all network activity (there is none)
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
Monitor with <code className="rounded bg-amber-200 px-1 dark:bg-amber-900">tcpdump</code> or{" "}
|
||||
<code className="rounded bg-amber-200 px-1 dark:bg-amber-900">wireshark</code>—you'll see silence
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
Disconnect from the internet and verify dictation still works perfectly
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl border border-zinc-200 bg-zinc-50 p-8 dark:border-zinc-700 dark:bg-zinc-900/60">
|
||||
<h2 className="mb-4 text-2xl font-bold">Truly Private Voice Dictation</h2>
|
||||
<p className="mb-6 text-muted-foreground">
|
||||
Install Vocalinux and take control of your voice data. No cloud, no tracking, no
|
||||
compromises.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link
|
||||
href="/install/"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Install Now
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/open-source/"
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-zinc-300 bg-white px-4 py-2 text-sm font-semibold hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-800 dark:hover:bg-zinc-700"
|
||||
>
|
||||
View Source Code
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</SeoSubpageShell>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
import Link from "next/link";
|
||||
import { type Metadata } from "next";
|
||||
import {
|
||||
CheckCircle2,
|
||||
ChevronRight,
|
||||
Code,
|
||||
Eye,
|
||||
GitBranch,
|
||||
Heart,
|
||||
Lock,
|
||||
Scale,
|
||||
Shield,
|
||||
Users,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { SeoSubpageShell } from "@/components/seo-subpage-shell";
|
||||
import { absoluteUrl, buildPageMetadata } from "@/lib/seo";
|
||||
|
||||
const benefits = [
|
||||
{
|
||||
title: "No Vendor Lock-in",
|
||||
description:
|
||||
"Your voice data stays with you. No subscription to cancel, no service to shut down, no company to go out of business.",
|
||||
icon: Lock,
|
||||
iconColor: "text-blue-500",
|
||||
iconBg: "bg-blue-100 dark:bg-blue-900/30",
|
||||
},
|
||||
{
|
||||
title: "Transparent & Auditable",
|
||||
description:
|
||||
"Every line of code is visible. Security researchers and users can verify exactly what the software does.",
|
||||
icon: Eye,
|
||||
iconColor: "text-violet-500",
|
||||
iconBg: "bg-violet-100 dark:bg-violet-900/30",
|
||||
},
|
||||
{
|
||||
title: "Community Driven",
|
||||
description:
|
||||
"Built by Linux users, for Linux users. Features and fixes come from real users, not corporate roadmaps.",
|
||||
icon: Users,
|
||||
iconColor: "text-emerald-500",
|
||||
iconBg: "bg-emerald-100 dark:bg-emerald-900/30",
|
||||
},
|
||||
{
|
||||
title: "Free Forever",
|
||||
description:
|
||||
"GPL v3 licensed. Free to use, modify, and share. No freemium, no premium tier, no hidden costs.",
|
||||
icon: Heart,
|
||||
iconColor: "text-rose-500",
|
||||
iconBg: "bg-rose-100 dark:bg-rose-900/30",
|
||||
},
|
||||
];
|
||||
|
||||
const vsProprietary = [
|
||||
{ feature: "Cost", vocalinux: "Free forever", proprietary: "$10-500/month or $500+ license" },
|
||||
{ feature: "Source Code", vocalinux: "Fully open", proprietary: "Closed, proprietary" },
|
||||
{ feature: "Data Privacy", vocalinux: "100% offline", proprietary: "Often cloud-based" },
|
||||
{ feature: "Linux Support", vocalinux: "Native", proprietary: "Limited or none" },
|
||||
{ feature: "Customization", vocalinux: "Unlimited", proprietary: "Limited to vendor options" },
|
||||
{ feature: "Vendor Dependency", vocalinux: "None", proprietary: "High" },
|
||||
{ feature: "Community Support", vocalinux: "Active GitHub community", proprietary: "Paid support only" },
|
||||
];
|
||||
|
||||
const techStack = [
|
||||
{
|
||||
name: "whisper.cpp",
|
||||
description: "High-performance C++ port of OpenAI's Whisper model",
|
||||
license: "MIT",
|
||||
},
|
||||
{
|
||||
name: "VOSK",
|
||||
description: "Offline speech recognition toolkit",
|
||||
license: "Apache 2.0",
|
||||
},
|
||||
{
|
||||
name: "GTK 3",
|
||||
description: "Cross-platform GUI toolkit for Linux",
|
||||
license: "LGPL",
|
||||
},
|
||||
{
|
||||
name: "Python",
|
||||
description: "Application logic and orchestration",
|
||||
license: "PSF License",
|
||||
},
|
||||
];
|
||||
|
||||
export const metadata: Metadata = buildPageMetadata({
|
||||
title: "Open Source Voice Dictation for Linux | Vocalinux",
|
||||
description:
|
||||
"100% free and open source voice dictation for Linux. GPL v3 licensed, no vendor lock-in, transparent code, community-driven development. Your voice data stays private.",
|
||||
path: "/open-source",
|
||||
keywords: [
|
||||
"open source voice dictation",
|
||||
"free speech to text Linux",
|
||||
"GPL voice recognition",
|
||||
"FOSS dictation software",
|
||||
"Linux voice typing free",
|
||||
"transparent voice recognition",
|
||||
"community voice dictation",
|
||||
"no subscription dictation",
|
||||
],
|
||||
});
|
||||
|
||||
export default function OpenSourcePage() {
|
||||
const articleJsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
headline: "Open Source Voice Dictation for Linux",
|
||||
description:
|
||||
"Why Vocalinux is 100% open source. GPL v3 licensed, community-driven, no vendor lock-in voice dictation for Linux.",
|
||||
dateModified: "2026-02-22",
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: "Jatin K Malik",
|
||||
url: "https://github.com/jatinkrmalik",
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Vocalinux",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: absoluteUrl("/vocalinux.png"),
|
||||
},
|
||||
},
|
||||
mainEntityOfPage: absoluteUrl("/open-source"),
|
||||
};
|
||||
|
||||
return (
|
||||
<SeoSubpageShell>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleJsonLd) }}
|
||||
/>
|
||||
|
||||
<section>
|
||||
<p className="mb-4 inline-flex items-center gap-2 rounded-full border border-emerald-500/30 bg-emerald-500/10 px-4 py-1.5 text-sm font-medium text-emerald-600 dark:text-emerald-400">
|
||||
<Code className="h-4 w-4" />
|
||||
Open Source
|
||||
</p>
|
||||
<h1 className="mb-5 text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl">
|
||||
Free & Open Source Voice Dictation
|
||||
</h1>
|
||||
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
|
||||
Vocalinux is 100% open source under the GPL v3 license. No subscriptions, no vendor
|
||||
lock-in, no hidden costs. Your voice data never leaves your machine.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-emerald-200 bg-emerald-50 p-6 dark:border-emerald-800 dark:bg-emerald-900/20">
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<Scale className="h-6 w-6 text-emerald-600 dark:text-emerald-400" />
|
||||
<h2 className="text-xl font-bold text-emerald-800 dark:text-emerald-300">
|
||||
GPL v3 Licensed
|
||||
</h2>
|
||||
</div>
|
||||
<p className="mb-4 text-emerald-700 dark:text-emerald-400">
|
||||
Vocalinux is released under the GNU General Public License v3.0, which means:
|
||||
</p>
|
||||
<ul className="grid gap-2 sm:grid-cols-2">
|
||||
{[
|
||||
"Free to use for any purpose",
|
||||
"Free to study and modify",
|
||||
"Free to share with others",
|
||||
"Modifications must remain open",
|
||||
].map((item) => (
|
||||
<li key={item} className="inline-flex items-center gap-2 text-sm text-emerald-700 dark:text-emerald-400">
|
||||
<CheckCircle2 className="h-4 w-4" />
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-2xl font-bold">Why Open Source Matters</h2>
|
||||
<div className="grid gap-6 sm:grid-cols-2">
|
||||
{benefits.map((benefit) => {
|
||||
const Icon = benefit.icon;
|
||||
return (
|
||||
<article
|
||||
key={benefit.title}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<div className={`rounded-lg p-2.5 ${benefit.iconBg}`}>
|
||||
<Icon className={`h-5 w-5 ${benefit.iconColor}`} />
|
||||
</div>
|
||||
<h3 className="font-semibold">{benefit.title}</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{benefit.description}</p>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<h2 className="mb-6 text-2xl font-bold">Open Source vs Proprietary</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-zinc-200 dark:border-zinc-700">
|
||||
<th className="pb-3 pr-4 font-semibold">Feature</th>
|
||||
<th className="pb-3 pr-4 font-semibold">Vocalinux</th>
|
||||
<th className="pb-3 font-semibold">Proprietary Alternatives</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{vsProprietary.map((row) => (
|
||||
<tr key={row.feature} className="border-b border-zinc-100 dark:border-zinc-700/70">
|
||||
<td className="py-3 pr-4 font-medium">{row.feature}</td>
|
||||
<td className="py-3 pr-4 text-green-600 dark:text-green-400">{row.vocalinux}</td>
|
||||
<td className="py-3 text-muted-foreground">{row.proprietary}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-2xl font-bold">Built on Open Source</h2>
|
||||
<p className="mb-6 text-muted-foreground">
|
||||
Vocalinux stands on the shoulders of these excellent open source projects:
|
||||
</p>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{techStack.map((tech) => (
|
||||
<div
|
||||
key={tech.name}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<h3 className="font-semibold">{tech.name}</h3>
|
||||
<span className="rounded-full bg-zinc-100 px-2 py-0.5 text-xs font-medium dark:bg-zinc-700">
|
||||
{tech.license}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{tech.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<div className="flex items-start gap-4">
|
||||
<Shield className="mt-1 h-8 w-8 flex-shrink-0 text-primary" />
|
||||
<div>
|
||||
<h2 className="mb-3 text-xl font-bold">Privacy by Design</h2>
|
||||
<p className="mb-4 text-muted-foreground">
|
||||
Because Vocalinux is open source and runs entirely offline, you can verify that your
|
||||
voice data never leaves your computer. No telemetry, no analytics, no cloud
|
||||
processing.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{["No account required", "No internet needed", "No data collection", "Full transparency"].map(
|
||||
(item) => (
|
||||
<span
|
||||
key={item}
|
||||
className="rounded-full bg-zinc-100 px-3 py-1 text-xs font-medium dark:bg-zinc-700"
|
||||
>
|
||||
{item}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-blue-200 bg-blue-50 p-6 dark:border-blue-800 dark:bg-blue-900/20">
|
||||
<div className="flex items-start gap-4">
|
||||
<GitBranch className="mt-1 h-6 w-6 flex-shrink-0 text-blue-600 dark:text-blue-400" />
|
||||
<div>
|
||||
<h3 className="mb-2 font-semibold text-blue-800 dark:text-blue-300">
|
||||
Contribute to Vocalinux
|
||||
</h3>
|
||||
<p className="mb-4 text-sm text-blue-700 dark:text-blue-400">
|
||||
We welcome contributions! Whether it's bug fixes, features, documentation, or
|
||||
translations—join the community.
|
||||
</p>
|
||||
<a
|
||||
href="https://github.com/jatinkrmalik/vocalinux"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-700"
|
||||
>
|
||||
<Zap className="h-4 w-4" />
|
||||
View on GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl border border-zinc-200 bg-zinc-50 p-8 dark:border-zinc-700 dark:bg-zinc-900/60">
|
||||
<h2 className="mb-4 text-2xl font-bold">Join the Open Source Revolution</h2>
|
||||
<p className="mb-6 text-muted-foreground">
|
||||
Install Vocalinux and experience voice dictation that respects your freedom and privacy.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link
|
||||
href="/install/"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Install Free
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/alternatives/"
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-zinc-300 bg-white px-4 py-2 text-sm font-semibold hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-800 dark:hover:bg-zinc-700"
|
||||
>
|
||||
Compare Alternatives
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</SeoSubpageShell>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
import Link from "next/link";
|
||||
import { type Metadata } from "next";
|
||||
import { CheckCircle2, ChevronRight, Heart, Keyboard, Shield, User } from "lucide-react";
|
||||
import { SeoSubpageShell } from "@/components/seo-subpage-shell";
|
||||
import { absoluteUrl, buildPageMetadata } from "@/lib/seo";
|
||||
|
||||
const benefits = [
|
||||
{
|
||||
title: "Reduce Physical Strain",
|
||||
description:
|
||||
"Give your hands, wrists, and fingers a break. Voice dictation lets you type without the physical toll of keyboard use.",
|
||||
icon: Heart,
|
||||
iconColor: "text-rose-500",
|
||||
iconBg: "bg-rose-100 dark:bg-rose-900/30",
|
||||
},
|
||||
{
|
||||
title: "Prevent Repetitive Stress",
|
||||
description:
|
||||
"RSI develops from thousands of repeated motions. Dictating breaks the cycle and allows recovery.",
|
||||
icon: Keyboard,
|
||||
iconColor: "text-amber-500",
|
||||
iconBg: "bg-amber-100 dark:bg-amber-900/30",
|
||||
},
|
||||
{
|
||||
title: "Privacy for Health",
|
||||
description:
|
||||
"Your health data stays private. All voice processing happens locally—no cloud uploads, no third parties.",
|
||||
icon: Shield,
|
||||
iconColor: "text-blue-500",
|
||||
iconBg: "bg-blue-100 dark:bg-blue-900/30",
|
||||
},
|
||||
{
|
||||
title: "Independence",
|
||||
description:
|
||||
"For those with mobility limitations, voice dictation provides computer independence without relying on others.",
|
||||
icon: User,
|
||||
iconColor: "text-emerald-500",
|
||||
iconBg: "bg-emerald-100 dark:bg-emerald-900/30",
|
||||
},
|
||||
];
|
||||
|
||||
const conditions = [
|
||||
{
|
||||
name: "Carpal Tunnel Syndrome",
|
||||
description:
|
||||
"Compression of the median nerve causing numbness, tingling, and weakness in the hand.",
|
||||
howDictationHelps:
|
||||
"Eliminate the wrist movements that aggravate symptoms while maintaining productivity.",
|
||||
},
|
||||
{
|
||||
name: "Repetitive Strain Injury (RSI)",
|
||||
description: "Pain in muscles, nerves, or tendons caused by repetitive movements and overuse.",
|
||||
howDictationHelps:
|
||||
"Break the cycle of repetitive keystrokes and allow your hands to recover.",
|
||||
},
|
||||
{
|
||||
name: "Tendinitis",
|
||||
description: "Inflammation or irritation of a tendon, often affecting the wrist and fingers.",
|
||||
howDictationHelps:
|
||||
"Reduce tendon stress by speaking instead of typing for extended periods.",
|
||||
},
|
||||
{
|
||||
name: "Tennis Elbow (Lateral Epicondylitis)",
|
||||
description: "Overuse injury causing pain on the outside of the elbow and forearm.",
|
||||
howDictationHelps:
|
||||
"Minimize the gripping and wrist motions that exacerbate elbow pain.",
|
||||
},
|
||||
{
|
||||
name: "Thoracic Outlet Syndrome",
|
||||
description:
|
||||
"Compression of nerves or blood vessels in the lower neck and upper chest.",
|
||||
howDictationHelps:
|
||||
"Reduce the forward-head posture and shoulder tension common with keyboard use.",
|
||||
},
|
||||
{
|
||||
name: "General Mobility Limitations",
|
||||
description:
|
||||
"Conditions affecting hand, arm, or finger movement from various causes.",
|
||||
howDictationHelps:
|
||||
"Full computer control without physical keyboard interaction required.",
|
||||
},
|
||||
];
|
||||
|
||||
const statistics = [
|
||||
{ value: "8+", label: "Hours per day average computer use for office workers" },
|
||||
{ value: "50%", label: "Of computer users experience RSI symptoms" },
|
||||
{ value: "100+", label: "Words per minute achievable with voice dictation" },
|
||||
{ value: "100%", label: "Offline - no data ever leaves your computer" },
|
||||
];
|
||||
|
||||
export const metadata: Metadata = buildPageMetadata({
|
||||
title: "Voice Dictation for RSI & Carpal Tunnel Prevention | Vocalinux",
|
||||
description:
|
||||
"Prevent and manage RSI, carpal tunnel, and repetitive strain injuries with voice dictation. Hands-free typing for Linux users with 100% offline privacy.",
|
||||
path: "/rsi-prevention",
|
||||
keywords: [
|
||||
"RSI voice dictation",
|
||||
"carpal tunnel voice typing",
|
||||
"hands-free typing",
|
||||
"repetitive strain injury prevention",
|
||||
"voice typing for wrist pain",
|
||||
"dictation software for RSI",
|
||||
"ergonomic typing alternative",
|
||||
"Linux voice dictation accessibility",
|
||||
],
|
||||
});
|
||||
|
||||
export default function RsiPreventionPage() {
|
||||
const articleJsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
headline: "Voice Dictation for RSI & Carpal Tunnel Prevention",
|
||||
description:
|
||||
"How voice dictation helps prevent and manage repetitive strain injuries. Hands-free typing solution for Linux users.",
|
||||
dateModified: "2026-02-22",
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: "Jatin K Malik",
|
||||
url: "https://github.com/jatinkrmalik",
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Vocalinux",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: absoluteUrl("/vocalinux.png"),
|
||||
},
|
||||
},
|
||||
mainEntityOfPage: absoluteUrl("/rsi-prevention"),
|
||||
};
|
||||
|
||||
return (
|
||||
<SeoSubpageShell>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleJsonLd) }}
|
||||
/>
|
||||
|
||||
<section>
|
||||
<p className="mb-4 inline-flex items-center gap-2 rounded-full border border-rose-500/30 bg-rose-500/10 px-4 py-1.5 text-sm font-medium text-rose-600 dark:text-rose-400">
|
||||
<Heart className="h-4 w-4" />
|
||||
Health & Accessibility
|
||||
</p>
|
||||
<h1 className="mb-5 text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl">
|
||||
Voice Dictation for RSI Prevention
|
||||
</h1>
|
||||
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
|
||||
Suffering from carpal tunnel, wrist pain, or repetitive strain? Voice dictation lets you
|
||||
keep working without the physical toll. All processing happens locally on your Linux
|
||||
machine.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{statistics.map((stat) => (
|
||||
<div
|
||||
key={stat.label}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-5 text-center dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<p className="mb-1 text-3xl font-bold text-primary">{stat.value}</p>
|
||||
<p className="text-sm text-muted-foreground">{stat.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-2xl font-bold">Why Voice Dictation Helps</h2>
|
||||
<div className="grid gap-6 sm:grid-cols-2">
|
||||
{benefits.map((benefit) => {
|
||||
const Icon = benefit.icon;
|
||||
return (
|
||||
<article
|
||||
key={benefit.title}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<div className={`rounded-lg p-2.5 ${benefit.iconBg}`}>
|
||||
<Icon className={`h-5 w-5 ${benefit.iconColor}`} />
|
||||
</div>
|
||||
<h3 className="font-semibold">{benefit.title}</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{benefit.description}</p>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-2xl font-bold">Conditions Helped by Voice Dictation</h2>
|
||||
<div className="space-y-4">
|
||||
{conditions.map((condition) => (
|
||||
<article
|
||||
key={condition.name}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<h3 className="mb-2 font-semibold">{condition.name}</h3>
|
||||
<p className="mb-3 text-sm text-muted-foreground">{condition.description}</p>
|
||||
<div className="rounded-lg bg-green-50 p-3 dark:bg-green-900/20">
|
||||
<p className="text-sm text-green-700 dark:text-green-400">
|
||||
<strong>How dictation helps:</strong> {condition.howDictationHelps}
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<h2 className="mb-4 text-2xl font-bold">Getting Started with Hands-Free Typing</h2>
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<h3 className="mb-3 font-semibold">Tips for Transitioning</h3>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
Start by dictating emails and documents, then expand to other tasks
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
Use voice commands for punctuation to maintain natural speech flow
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
Take breaks—voice dictation is still work, and vocal rest matters too
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
Combine with ergonomic equipment for maximum benefit
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="mb-3 font-semibold">Vocalinux Advantages</h3>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
Double-tap Ctrl to start/stop—no mouse required
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
System tray indicator shows recording status
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
Pleasant audio feedback confirms start/stop
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
|
||||
Works in ALL Linux applications—universal compatibility
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl border border-rose-200 bg-rose-50 p-8 dark:border-rose-800 dark:bg-rose-900/20">
|
||||
<h2 className="mb-4 text-2xl font-bold text-rose-800 dark:text-rose-300">
|
||||
Take Care of Your Hands
|
||||
</h2>
|
||||
<p className="mb-6 text-rose-700 dark:text-rose-400">
|
||||
Your health matters. If you're experiencing pain, numbness, or tingling, consult a
|
||||
healthcare professional. Voice dictation is a tool to help, not a substitute for proper
|
||||
medical care.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link
|
||||
href="/install/"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Install Vocalinux
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/use-cases/"
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-rose-300 bg-white px-4 py-2 text-sm font-semibold text-rose-700 hover:bg-rose-50 dark:border-rose-700 dark:bg-rose-900/50 dark:text-rose-300 dark:hover:bg-rose-900/70"
|
||||
>
|
||||
See Other Use Cases
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</SeoSubpageShell>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
import Link from "next/link";
|
||||
import { type Metadata } from "next";
|
||||
import { CheckCircle2, ChevronRight, Monitor, Terminal, Zap } from "lucide-react";
|
||||
import { SeoSubpageShell } from "@/components/seo-subpage-shell";
|
||||
import { absoluteUrl, buildPageMetadata } from "@/lib/seo";
|
||||
|
||||
const waylandFeatures = [
|
||||
{
|
||||
title: "Native IBus Integration",
|
||||
description:
|
||||
"Text injection via IBus engine works seamlessly on Wayland compositors like GNOME, KDE, Sway, and Hyprland.",
|
||||
icon: Terminal,
|
||||
},
|
||||
{
|
||||
title: "X11 Fallback Support",
|
||||
description:
|
||||
"Automatic fallback to xdotool when running under XWayland or traditional X11 sessions.",
|
||||
icon: Monitor,
|
||||
},
|
||||
{
|
||||
title: "Desktop Agnostic",
|
||||
description:
|
||||
"Works with GNOME, KDE Plasma, Sway, Hyprland, river, and other Wayland compositors.",
|
||||
icon: Zap,
|
||||
},
|
||||
];
|
||||
|
||||
const compositorSupport = [
|
||||
{
|
||||
name: "GNOME (mutter)",
|
||||
method: "IBus",
|
||||
status: "Full support",
|
||||
notes: "Default on Ubuntu, Fedora, Debian GNOME editions",
|
||||
},
|
||||
{
|
||||
name: "KDE Plasma (KWin)",
|
||||
method: "IBus",
|
||||
status: "Full support",
|
||||
notes: "Works with IBus input method framework",
|
||||
},
|
||||
{
|
||||
name: "Sway",
|
||||
method: "IBus / wtype",
|
||||
status: "Full support",
|
||||
notes: "i3-compatible Wayland compositor",
|
||||
},
|
||||
{
|
||||
name: "Hyprland",
|
||||
method: "IBus / wtype",
|
||||
status: "Full support",
|
||||
notes: "Dynamic tiling Wayland compositor",
|
||||
},
|
||||
{
|
||||
name: "river",
|
||||
method: "IBus / wtype",
|
||||
status: "Full support",
|
||||
notes: "Dynamic tiling Wayland compositor",
|
||||
},
|
||||
{
|
||||
name: "XWayland apps",
|
||||
method: "xdotool",
|
||||
status: "Full support",
|
||||
notes: "X11 apps running under Wayland",
|
||||
},
|
||||
];
|
||||
|
||||
const setupSteps = [
|
||||
{
|
||||
step: 1,
|
||||
title: "Install IBus",
|
||||
description: "Most distributions include IBus by default. If not:",
|
||||
command: "sudo apt install ibus ibus-gtk ibus-gtk3",
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: "Start IBus Daemon",
|
||||
description: "Ensure the IBus daemon is running in your session:",
|
||||
command: "ibus-daemon -drx",
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: "Run Vocalinux",
|
||||
description: "Vocalinux will auto-detect Wayland and configure IBus:",
|
||||
command: "vocalinux",
|
||||
},
|
||||
];
|
||||
|
||||
export const metadata: Metadata = buildPageMetadata({
|
||||
title: "Wayland Voice Dictation Support - GNOME, KDE, Sway | Vocalinux",
|
||||
description:
|
||||
"Full Wayland support for voice dictation on Linux. Works with GNOME, KDE Plasma, Sway, Hyprland, and other Wayland compositors via IBus integration.",
|
||||
path: "/wayland",
|
||||
keywords: [
|
||||
"wayland voice dictation",
|
||||
"GNOME voice typing",
|
||||
"KDE speech to text",
|
||||
"Sway voice recognition",
|
||||
"Hyprland dictation",
|
||||
"wayland text injection",
|
||||
"linux wayland speech recognition",
|
||||
"ibus voice input",
|
||||
],
|
||||
});
|
||||
|
||||
export default function WaylandPage() {
|
||||
const articleJsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
headline: "Wayland Voice Dictation Support for Linux",
|
||||
description:
|
||||
"Complete guide to Vocalinux Wayland support. Voice dictation for GNOME, KDE, Sway, Hyprland, and other Wayland compositors.",
|
||||
dateModified: "2026-02-22",
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: "Jatin K Malik",
|
||||
url: "https://github.com/jatinkrmalik",
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Vocalinux",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: absoluteUrl("/vocalinux.png"),
|
||||
},
|
||||
},
|
||||
mainEntityOfPage: absoluteUrl("/wayland"),
|
||||
};
|
||||
|
||||
return (
|
||||
<SeoSubpageShell>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleJsonLd) }}
|
||||
/>
|
||||
|
||||
<section>
|
||||
<p className="mb-4 inline-flex items-center gap-2 rounded-full border border-primary/30 bg-primary/10 px-4 py-1.5 text-sm font-medium text-primary">
|
||||
<Monitor className="h-4 w-4" />
|
||||
Wayland Support
|
||||
</p>
|
||||
<h1 className="mb-5 text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl">
|
||||
Voice Dictation on Wayland
|
||||
</h1>
|
||||
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
|
||||
Full voice dictation support for modern Wayland compositors. Works with GNOME, KDE Plasma,
|
||||
Sway, Hyprland, and more. No X11 required.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 grid gap-6 sm:grid-cols-3">
|
||||
{waylandFeatures.map((feature) => {
|
||||
const Icon = feature.icon;
|
||||
return (
|
||||
<div
|
||||
key={feature.title}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<Icon className="mb-3 h-8 w-8 text-primary" />
|
||||
<h3 className="mb-2 font-semibold">{feature.title}</h3>
|
||||
<p className="text-sm text-muted-foreground">{feature.description}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<h2 className="mb-6 text-2xl font-bold">Supported Wayland Compositors</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left">
|
||||
<thead>
|
||||
<tr className="border-b border-zinc-200 dark:border-zinc-700">
|
||||
<th className="pb-3 pr-4 text-sm font-semibold">Compositor</th>
|
||||
<th className="pb-3 pr-4 text-sm font-semibold">Method</th>
|
||||
<th className="pb-3 pr-4 text-sm font-semibold">Status</th>
|
||||
<th className="pb-3 text-sm font-semibold">Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-sm">
|
||||
{compositorSupport.map((item) => (
|
||||
<tr
|
||||
key={item.name}
|
||||
className="border-b border-zinc-100 dark:border-zinc-700/70"
|
||||
>
|
||||
<td className="py-3 pr-4 font-medium">{item.name}</td>
|
||||
<td className="py-3 pr-4 text-muted-foreground">{item.method}</td>
|
||||
<td className="py-3 pr-4">
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700 dark:bg-green-900/30 dark:text-green-400">
|
||||
<CheckCircle2 className="h-3 w-3" />
|
||||
{item.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-3 text-muted-foreground">{item.notes}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-2xl font-bold">Quick Setup Guide</h2>
|
||||
<div className="space-y-4">
|
||||
{setupSteps.map((step) => (
|
||||
<div
|
||||
key={step.step}
|
||||
className="rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-800"
|
||||
>
|
||||
<div className="mb-2 flex items-center gap-3">
|
||||
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-primary text-sm font-bold text-primary-foreground">
|
||||
{step.step}
|
||||
</span>
|
||||
<h3 className="font-semibold">{step.title}</h3>
|
||||
</div>
|
||||
<p className="mb-3 ml-10 text-sm text-muted-foreground">{step.description}</p>
|
||||
<div className="ml-10 rounded-lg bg-zinc-950 p-3">
|
||||
<code className="text-sm text-green-400">{step.command}</code>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12 rounded-2xl border border-blue-200 bg-blue-50 p-6 dark:border-blue-800 dark:bg-blue-900/20">
|
||||
<h2 className="mb-3 text-xl font-bold text-blue-800 dark:text-blue-300">
|
||||
How Wayland Text Injection Works
|
||||
</h2>
|
||||
<p className="mb-4 text-sm text-blue-700 dark:text-blue-400">
|
||||
Unlike X11 where applications can simulate keyboard input globally, Wayland's security
|
||||
model requires a different approach. Vocalinux uses:
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm text-blue-700 dark:text-blue-400">
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
<strong>IBus Engine</strong> — A custom IBus input method that receives text from
|
||||
Vocalinux and types it into the focused application
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
<strong>wtype</strong> — Native Wayland typing tool for compositors that support the
|
||||
virtual keyboard protocol
|
||||
</li>
|
||||
<li className="inline-flex items-start gap-2">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
<strong>X11 Fallback</strong> — xdotool for XWayland applications
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl border border-zinc-200 bg-zinc-50 p-8 dark:border-zinc-700 dark:bg-zinc-900/60">
|
||||
<h2 className="mb-4 text-2xl font-bold">Start Dictating on Wayland</h2>
|
||||
<p className="mb-6 text-muted-foreground">
|
||||
Vocalinux automatically detects your display server and configures the appropriate text
|
||||
injection method. Just install and run.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link
|
||||
href="/install/"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Install Now
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/troubleshooting/"
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-zinc-300 bg-white px-4 py-2 text-sm font-semibold hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-800 dark:hover:bg-zinc-700"
|
||||
>
|
||||
Troubleshooting Guide
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</SeoSubpageShell>
|
||||
);
|
||||
}
|
||||
@@ -1,115 +1,287 @@
|
||||
import React from "react";
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { BookOpen, ChevronRight, Cpu, HelpCircle, Home, Map, Rss, Shield, Users } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import {
|
||||
BookOpen,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Code2,
|
||||
Cpu,
|
||||
Globe,
|
||||
Heart,
|
||||
HelpCircle,
|
||||
Home,
|
||||
Menu,
|
||||
Monitor,
|
||||
Power,
|
||||
Shield,
|
||||
X,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { VocalinuxLogo } from "@/components/optimized-image";
|
||||
|
||||
interface SeoSubpageShellProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const navLinks = [
|
||||
const navCategories = [
|
||||
{
|
||||
href: "/",
|
||||
label: "Home",
|
||||
icon: Home,
|
||||
label: "Get Started",
|
||||
items: [
|
||||
{ href: "/", label: "Home", icon: Home },
|
||||
{ href: "/install/", label: "Install Guides", icon: BookOpen },
|
||||
{ href: "/changelog/", label: "Changelog", icon: Zap },
|
||||
],
|
||||
},
|
||||
{
|
||||
href: "/install/",
|
||||
label: "Install Guides",
|
||||
icon: BookOpen,
|
||||
label: "Features",
|
||||
items: [
|
||||
{ href: "/compare/", label: "Engine Comparison", icon: Cpu },
|
||||
{ href: "/languages/", label: "Multilingual", icon: Globe },
|
||||
{ href: "/wayland/", label: "Wayland Support", icon: Monitor },
|
||||
{ href: "/gpu-acceleration/", label: "GPU Acceleration", icon: Cpu },
|
||||
{ href: "/autostart/", label: "Autostart", icon: Power },
|
||||
],
|
||||
},
|
||||
{
|
||||
href: "/compare/",
|
||||
label: "Engine Compare",
|
||||
icon: Cpu,
|
||||
},
|
||||
{
|
||||
href: "/use-cases/",
|
||||
label: "Use Cases",
|
||||
icon: Users,
|
||||
items: [
|
||||
{ href: "/for-developers/", label: "For Developers", icon: Code2 },
|
||||
{ href: "/rsi-prevention/", label: "RSI Prevention", icon: Heart },
|
||||
{ href: "/use-cases/", label: "All Use Cases", icon: Zap },
|
||||
],
|
||||
},
|
||||
{
|
||||
href: "/faq/",
|
||||
label: "FAQ",
|
||||
icon: HelpCircle,
|
||||
label: "Privacy & Ethics",
|
||||
items: [
|
||||
{ href: "/offline/", label: "100% Offline", icon: Shield },
|
||||
{ href: "/open-source/", label: "Open Source", icon: Code2 },
|
||||
{ href: "/privacy/", label: "Privacy Policy", icon: Shield },
|
||||
],
|
||||
},
|
||||
{
|
||||
href: "/changelog/",
|
||||
label: "Changelog",
|
||||
icon: Rss,
|
||||
},
|
||||
{
|
||||
href: "/privacy/",
|
||||
label: "Privacy",
|
||||
icon: Shield,
|
||||
},
|
||||
{
|
||||
href: "/alternatives/",
|
||||
label: "Alternatives",
|
||||
icon: Map,
|
||||
label: "Support",
|
||||
items: [
|
||||
{ href: "/faq/", label: "FAQ", icon: HelpCircle },
|
||||
{ href: "/troubleshooting/", label: "Troubleshooting", icon: Zap },
|
||||
{ href: "/shortcuts/", label: "Voice Commands", icon: BookOpen },
|
||||
{ href: "/alternatives/", label: "Alternatives", icon: BookOpen },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function DropdownMenu({
|
||||
category,
|
||||
}: {
|
||||
category: (typeof navCategories)[0];
|
||||
}) {
|
||||
return (
|
||||
<div className="group relative">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-zinc-100 hover:text-foreground focus:bg-zinc-100 focus:text-foreground focus:outline-none dark:hover:bg-zinc-800 dark:focus:bg-zinc-800"
|
||||
>
|
||||
{category.label}
|
||||
<ChevronDown
|
||||
className="h-3.5 w-3.5 transition-transform group-hover:rotate-180 group-focus-within:rotate-180"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div className="pointer-events-none invisible absolute left-0 top-full z-50 mt-0 w-56 rounded-xl border border-zinc-200 bg-white p-2 opacity-0 shadow-lg transition-all duration-150 group-hover:pointer-events-auto group-hover:visible group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:visible group-focus-within:opacity-100 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
{category.items.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-zinc-100 hover:text-foreground dark:hover:bg-zinc-700"
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
{item.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Breadcrumbs() {
|
||||
const pathname = usePathname();
|
||||
|
||||
if (pathname === "/") return null;
|
||||
|
||||
const segments = pathname.split("/").filter(Boolean);
|
||||
const breadcrumbs = segments.map((segment, index) => {
|
||||
const href = "/" + segments.slice(0, index + 1).join("/");
|
||||
const label = segment
|
||||
.replace(/-/g, " ")
|
||||
.replace(/\b\w/g, (l) => l.toUpperCase());
|
||||
return { href, label };
|
||||
});
|
||||
|
||||
const getPageTitle = (path: string): string => {
|
||||
for (const category of navCategories) {
|
||||
for (const item of category.items) {
|
||||
if (item.href === path || item.href === path + "/") {
|
||||
return item.label;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
path
|
||||
.split("/")
|
||||
.pop()
|
||||
?.replace(/-/g, " ")
|
||||
.replace(/\b\w/g, (l) => l.toUpperCase()) || ""
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="mb-6 flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Link href="/" className="hover:text-foreground transition-colors">
|
||||
Home
|
||||
</Link>
|
||||
{breadcrumbs.map((crumb, index) => (
|
||||
<React.Fragment key={crumb.href}>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
{index === breadcrumbs.length - 1 ? (
|
||||
<span className="text-foreground font-medium">
|
||||
{getPageTitle(crumb.href)}
|
||||
</span>
|
||||
) : (
|
||||
<Link
|
||||
href={crumb.href}
|
||||
className="hover:text-foreground transition-colors"
|
||||
>
|
||||
{crumb.label}
|
||||
</Link>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
export function SeoSubpageShell({ children }: SeoSubpageShellProps) {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-zinc-50 to-white dark:from-zinc-900 dark:to-zinc-950">
|
||||
<header className="border-b border-zinc-200/80 bg-background/80 backdrop-blur-md dark:border-zinc-800/80">
|
||||
<div className="mx-auto flex max-w-6xl items-center justify-between px-4 py-4 sm:px-6">
|
||||
<header className="sticky top-0 z-50 border-b border-zinc-200/80 bg-background/95 backdrop-blur-md dark:border-zinc-800/80">
|
||||
<div className="mx-auto flex max-w-6xl items-center justify-between px-4 py-3 sm:px-6">
|
||||
<Link href="/" className="group flex items-center gap-2">
|
||||
<VocalinuxLogo width={30} height={30} className="h-7 w-7 transition-transform group-hover:scale-110" />
|
||||
<VocalinuxLogo
|
||||
width={30}
|
||||
height={30}
|
||||
className="h-7 w-7 transition-transform group-hover:scale-110"
|
||||
/>
|
||||
<span className="text-base font-bold sm:text-lg">Vocalinux</span>
|
||||
</Link>
|
||||
|
||||
<nav className="hidden items-center gap-2 sm:flex">
|
||||
{navLinks.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className="inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-zinc-100 hover:text-foreground dark:hover:bg-zinc-800"
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
{item.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
{/* Desktop nav with dropdowns */}
|
||||
<nav className="hidden items-center gap-1 md:flex">
|
||||
{navCategories.map((category) => (
|
||||
<DropdownMenu key={category.label} category={category} />
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setMobileMenuOpen(!mobileMenuOpen);
|
||||
}}
|
||||
className="flex items-center justify-center rounded-lg p-2 text-muted-foreground transition-colors hover:bg-zinc-100 hover:text-foreground dark:hover:bg-zinc-800 md:hidden"
|
||||
aria-label={mobileMenuOpen ? "Close menu" : "Open menu"}
|
||||
>
|
||||
{mobileMenuOpen ? (
|
||||
<X className="h-6 w-6" />
|
||||
) : (
|
||||
<Menu className="h-6 w-6" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto flex max-w-6xl gap-2 overflow-x-auto px-4 pb-4 sm:hidden scrollbar-thin">
|
||||
{navLinks.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
title={item.label}
|
||||
className="inline-flex items-center justify-center rounded-lg border border-zinc-200 bg-white p-2 text-muted-foreground transition-colors hover:bg-zinc-50 hover:text-foreground dark:border-zinc-700 dark:bg-zinc-800 dark:hover:bg-zinc-700"
|
||||
aria-label={item.label}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* Mobile menu */}
|
||||
{mobileMenuOpen && (
|
||||
<div className="border-t border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900 md:hidden">
|
||||
<nav className="max-h-[calc(100vh-4rem)] overflow-y-auto px-4 py-4">
|
||||
{navCategories.map((category) => (
|
||||
<div key={category.label} className="mb-4">
|
||||
<h3 className="mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
{category.label}
|
||||
</h3>
|
||||
<div className="space-y-1">
|
||||
{category.items.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-zinc-100 hover:text-foreground dark:hover:bg-zinc-800"
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
{item.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div className="mx-auto w-full max-w-6xl px-4 pb-16 pt-10 sm:px-6 sm:pt-12">{children}</div>
|
||||
<div className="mx-auto w-full max-w-6xl px-4 pb-16 pt-6 sm:px-6 sm:pt-10">
|
||||
<Breadcrumbs />
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<footer className="border-t border-zinc-200 bg-white/70 dark:border-zinc-800 dark:bg-zinc-900/60">
|
||||
<div className="mx-auto flex max-w-6xl flex-col items-start justify-between gap-5 px-4 py-8 sm:flex-row sm:items-center sm:px-6">
|
||||
<div>
|
||||
<p className="text-sm font-semibold">Want the full product overview?</p>
|
||||
<p className="text-sm text-muted-foreground">Go back to the homepage for the live demo and one-command installer.</p>
|
||||
<div className="mx-auto max-w-6xl px-4 py-8 sm:px-6">
|
||||
<div className="mb-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
||||
<div>
|
||||
<p className="text-sm font-semibold">Want the full product overview?</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Go back to the homepage for the live demo and one-command installer.
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center gap-1.5 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Home Page
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Footer links - all pages organized */}
|
||||
<div className="grid gap-6 border-t border-zinc-200 pt-6 dark:border-zinc-700 sm:grid-cols-2 lg:grid-cols-5">
|
||||
{navCategories.map((category) => (
|
||||
<div key={category.label}>
|
||||
<h4 className="mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
{category.label}
|
||||
</h4>
|
||||
<ul className="space-y-1.5">
|
||||
{category.items.map((item) => (
|
||||
<li key={item.href}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center gap-1.5 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Home Page
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Link>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user