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:
Jatin K Malik
2026-02-22 23:24:43 -08:00
committed by GitHub
parent 6e02e928ae
commit bc45108274
12 changed files with 2744 additions and 92 deletions
+1
View File
@@ -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.
+2 -2
View File
@@ -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
View File
@@ -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>
+305
View File
@@ -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>
);
}
+300
View File
@@ -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">&quot;{useCase.example}&quot;</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">
&quot;{cmd.command}&quot;
</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>
);
}
+355
View File
@@ -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&apos;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&apos;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>
);
}
+287
View File
@@ -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>
);
}
+309
View File
@@ -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&apos;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&apos;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>
);
}
+317
View File
@@ -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&apos;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>
);
}
+284
View File
@@ -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&apos;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>
);
}
+273
View File
@@ -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&apos;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>
);
}
+249 -77
View File
@@ -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>