feat(web): add v0.10.1 updates and 5 new SEO pages (#383)

* feat(web): add v0.10.1 updates and 5 new SEO pages

## New SEO Pages (5)
- /vs-nerd-dictation/ - Vocalinux vs Nerd Dictation comparison
- /whisper-model-guide/ - Whisper model sizes explained
- /voice-typing-vscode/ - How to dictate in VS Code on Linux
- /writers/ - Voice dictation for writers on Linux
- /gnome-kde/ - Voice typing on GNOME vs KDE Plasma

## Existing Page Updates
- homepage: Update JSON-LD featureList with v0.10.1 features, add new FAQs, link new pages in footer
- troubleshooting: Add suspend/resume recovery, tray resources, settings dialog fixes
- shortcuts: Add sound effects toggle, left/right modifier distinction, PTT improvements
- wayland: Add keyboard layout preservation, clipboard fallback note
- compare: Add engine switching safety notes
- faq: Add suspend/resume and keyboard layout FAQs
- alternatives: Link to new Nerd Dictation comparison
- seo-subpage-shell: Add new pages to navigation
- sitemap: Add 5 new URLs

Fixes #257 (SEO expansion)

* fix(web): add theme toggle to subpages, fix dark mode button contrast, simplify footer nav

- Add light/dark theme toggle to SeoSubpageShell header (all subpages)
- Fix duplicate useState/useEffect imports causing build error
- Fix unclosed div in header markup
- Add dark:text-black to CTA buttons across all 15 subpages for contrast on bright green bg
- Add dark:text-black to footer CTA button in SeoSubpageShell
- Simplify subpage footer to mainNavCategories (5 condensed categories) instead of full navCategories

* fix(web): fix list items stacking, add all new pages to nav, restructure footer

- Fix inline-flex → flex on all li elements across 13 pages so bullet points
  always stack on separate lines instead of running together horizontally
- Add all 5 new SEO pages to subpage nav mainNavCategories (Whisper Models,
  For Writers, VS Code, GNOME vs KDE under Features/Use Cases; new Compare
  dropdown with vs Nerd Dictation, Alternatives, Offline, Open Source)
- Add /vs-nerd-dictation/ to homepage footer Learn column
- Restructure homepage footer from 2 crowded columns into 4 lean categorised
  columns (Get Started, Learn, Use Cases, Support) — responsive 2-col on
  mobile/tablet, 5-col on desktop
This commit is contained in:
Jatin K Malik
2026-04-05 17:25:02 -07:00
committed by GitHub
parent 93032471c3
commit 04c8645e2a
25 changed files with 1665 additions and 207 deletions
+101 -20
View File
@@ -1,6 +1,6 @@
"use client";
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
@@ -17,10 +17,13 @@ import {
Monitor,
Power,
Shield,
Sun,
X,
Zap,
Moon,
} from "lucide-react";
import { VocalinuxLogo } from "@/components/optimized-image";
import { useTheme } from "next-themes";
interface SeoSubpageShellProps {
children: React.ReactNode;
@@ -50,9 +53,19 @@ const navCategories = [
items: [
{ href: "/for-developers/", label: "For Developers", icon: Code2 },
{ href: "/rsi-prevention/", label: "RSI Prevention", icon: Heart },
{ href: "/writers/", label: "For Writers", icon: Heart },
{ href: "/gnome-kde/", label: "GNOME vs KDE", icon: Monitor },
{ href: "/use-cases/", label: "All Use Cases", icon: Zap },
],
},
{
label: "Comparisons",
items: [
{ href: "/vs-nerd-dictation/", label: "vs Nerd Dictation", icon: BookOpen },
{ href: "/whisper-model-guide/", label: "Whisper Models", icon: Cpu },
{ href: "/voice-typing-vscode/", label: "VS Code", icon: Code2 },
],
},
{
label: "Privacy & Ethics",
items: [
@@ -167,6 +180,60 @@ function Breadcrumbs() {
export function SeoSubpageShell({ children }: SeoSubpageShellProps) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
useEffect(() => {
setMounted(true);
}, []);
const mainNavCategories = [
{
label: "Get Started",
items: [
{ href: "/", label: "Home", icon: Home },
{ href: "/install/", label: "Install", icon: BookOpen },
{ href: "/changelog/", label: "Changelog", icon: Zap },
],
},
{
label: "Features",
items: [
{ href: "/compare/", label: "Engine Comparison", icon: Cpu },
{ href: "/whisper-model-guide/", label: "Whisper Models", icon: Cpu },
{ href: "/wayland/", label: "Wayland", icon: Monitor },
{ href: "/gpu-acceleration/", label: "GPU Acceleration", icon: Cpu },
],
},
{
label: "Use Cases",
items: [
{ href: "/for-developers/", label: "For Developers", icon: Code2 },
{ href: "/writers/", label: "For Writers", icon: Heart },
{ href: "/voice-typing-vscode/", label: "VS Code", icon: Code2 },
{ href: "/gnome-kde/", label: "GNOME vs KDE", icon: Monitor },
{ href: "/rsi-prevention/", label: "RSI Prevention", icon: Heart },
],
},
{
label: "Compare",
items: [
{ href: "/vs-nerd-dictation/", label: "vs Nerd Dictation", icon: BookOpen },
{ href: "/alternatives/", label: "Alternatives", icon: BookOpen },
{ href: "/offline/", label: "100% Offline", icon: Shield },
{ href: "/open-source/", label: "Open Source", icon: Code2 },
],
},
{
label: "Support",
items: [
{ href: "/faq/", label: "FAQ", icon: HelpCircle },
{ href: "/troubleshooting/", label: "Troubleshooting", icon: Zap },
{ href: "/shortcuts/", label: "Commands", icon: BookOpen },
{ href: "/privacy/", label: "Privacy Policy", icon: Shield },
],
},
];
return (
<div className="min-h-screen bg-gradient-to-b from-zinc-50 to-white dark:from-zinc-900 dark:to-zinc-950">
@@ -183,33 +250,48 @@ export function SeoSubpageShell({ children }: SeoSubpageShellProps) {
{/* Desktop nav with dropdowns */}
<nav className="hidden items-center gap-1 md:flex">
{navCategories.map((category) => (
{mainNavCategories.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" />
{/* Theme toggle and mobile menu */}
<div className="flex items-center gap-2">
{mounted && (
<button
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
className="w-8 h-8 sm:w-9 sm:h-9 bg-muted hover:bg-muted/80 rounded-md flex items-center justify-center transition-all"
aria-label="Toggle theme"
>
{theme === "dark" ? (
<Sun className="h-4 w-4 text-yellow-400" />
) : (
<Moon className="h-4 w-4 text-slate-700" />
)}
</button>
)}
</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>
{/* 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) => (
{mainNavCategories.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}
@@ -253,16 +335,15 @@ export function SeoSubpageShell({ children }: SeoSubpageShellProps) {
</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"
className="inline-flex items-center gap-1.5 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground dark:text-black 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) => (
{mainNavCategories.map((category) => (
<div key={category.label}>
<h4 className="mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
{category.label}