From 0f127c41c7c1bc995da6885b4a2dc72665722866 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Thu, 9 Oct 2025 15:27:32 -0400 Subject: [PATCH 1/3] feat: improve Show Mode UX with orange next cue and fixed scrolling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhanced the Show Mode and shared view link pages with better visual indicators and fixed auto-scroll behavior for live performance use. Visual improvements: - Changed next cue indicator from red to orange for better distinction - Current cue remains green, next cue now orange (more intuitive) - Updated legend indicators to match new color scheme Auto-scroll fixes: - Fixed auto-scroll positioning to account for sticky header heights - Items now scroll to visible position below all sticky elements - Added proper offset calculation (89px total: 49px table + 40px section) - Added 20px padding offset for better visibility Sticky header layering fixes: - Fixed multi-line section headers showing through next headers - Implemented progressive z-index values (z-11, z-12, z-13...) - Each subsequent header now properly appears on top of previous ones - Prevents text bleed-through when headers wrap to multiple lines Changes: - ShowModePage.tsx: Orange next cue, fixed auto-scroll, z-index layering - SharedShowModePage.tsx: Same fixes applied for consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/web/src/pages/SharedShowModePage.tsx | 30 +++++++++++++++++------ apps/web/src/pages/ShowModePage.tsx | 30 +++++++++++++++++------ 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/apps/web/src/pages/SharedShowModePage.tsx b/apps/web/src/pages/SharedShowModePage.tsx index 4bbf5b1..3b81c0b 100644 --- a/apps/web/src/pages/SharedShowModePage.tsx +++ b/apps/web/src/pages/SharedShowModePage.tsx @@ -143,17 +143,23 @@ const SharedShowModePage: React.FC = () => { if (currentRow) { const containerRect = container.getBoundingClientRect(); const rowRect = currentRow.getBoundingClientRect(); - const headerHeight = 49; // Height of sticky header - // Check if row is visible within container + // Account for both table header and any sticky section headers + // Table header is at top-0 with height ~49px, section headers are at top-[40px] + const tableHeaderHeight = 49; + const sectionHeaderHeight = 40; + const totalStickyHeight = tableHeaderHeight + sectionHeaderHeight; // ~89px + const paddingOffset = 20; // Extra padding for visibility + + // Check if row is visible within container, accounting for sticky headers const rowTop = rowRect.top - containerRect.top; const rowBottom = rowRect.bottom - containerRect.top; - const visibleTop = headerHeight; + const visibleTop = totalStickyHeight; const visibleBottom = containerRect.height; - // Scroll if row is not fully visible + // Scroll if row is not fully visible below sticky headers if (rowTop < visibleTop || rowBottom > visibleBottom) { - const scrollOffset = rowTop - visibleTop - 20; // 20px padding + const scrollOffset = rowTop - visibleTop - paddingOffset; container.scrollTo({ top: container.scrollTop + scrollOffset, behavior: "smooth", @@ -274,7 +280,7 @@ const SharedShowModePage: React.FC = () => { Current Cue
- + Next Cue
@@ -318,7 +324,14 @@ const SharedShowModePage: React.FC = () => { const rowStyle: React.CSSProperties = {}; if (item.type === "header") { - rowClass = "bg-gray-700 hover:bg-gray-600 font-semibold sticky top-[40px] z-10"; + // Calculate z-index based on header position to prevent text bleed-through + // Each subsequent header should have a higher z-index than the previous + const headerIndex = items + .slice(0, index + 1) + .filter((i) => i.type === "header").length; + const zIndex = 10 + headerIndex; + rowClass = "bg-gray-700 hover:bg-gray-600 font-semibold sticky top-[40px]"; + rowStyle.zIndex = zIndex; } else { if (item.highlightColor && !isCurrent && !isNext) { rowStyle.backgroundColor = item.highlightColor; @@ -328,7 +341,8 @@ const SharedShowModePage: React.FC = () => { "bg-green-600/40 hover:bg-green-600/50 border-l-4 border-green-400"; delete rowStyle.backgroundColor; } else if (isNext) { - rowClass = "bg-red-600/40 hover:bg-red-600/50 border-l-4 border-red-400"; + rowClass = + "bg-orange-600/40 hover:bg-orange-600/50 border-l-4 border-orange-400"; delete rowStyle.backgroundColor; } } diff --git a/apps/web/src/pages/ShowModePage.tsx b/apps/web/src/pages/ShowModePage.tsx index d4583fd..74f92c1 100644 --- a/apps/web/src/pages/ShowModePage.tsx +++ b/apps/web/src/pages/ShowModePage.tsx @@ -272,17 +272,23 @@ const ShowModePage: React.FC = () => { if (currentRow) { const containerRect = container.getBoundingClientRect(); const rowRect = currentRow.getBoundingClientRect(); - const headerHeight = 49; // Height of sticky header - // Check if row is visible within container + // Account for both table header and any sticky section headers + // Table header is at top-0 with height ~49px, section headers are at top-[40px] + const tableHeaderHeight = 49; + const sectionHeaderHeight = 40; + const totalStickyHeight = tableHeaderHeight + sectionHeaderHeight; // ~89px + const paddingOffset = 20; // Extra padding for visibility + + // Check if row is visible within container, accounting for sticky headers const rowTop = rowRect.top - containerRect.top; const rowBottom = rowRect.bottom - containerRect.top; - const visibleTop = headerHeight; + const visibleTop = totalStickyHeight; const visibleBottom = containerRect.height; - // Scroll if row is not fully visible + // Scroll if row is not fully visible below sticky headers if (rowTop < visibleTop || rowBottom > visibleBottom) { - const scrollOffset = rowTop - visibleTop - 20; // 20px padding + const scrollOffset = rowTop - visibleTop - paddingOffset; container.scrollTo({ top: container.scrollTop + scrollOffset, behavior: "smooth", @@ -584,7 +590,7 @@ const ShowModePage: React.FC = () => { Current Cue
- + Next Cue
@@ -689,7 +695,14 @@ const ShowModePage: React.FC = () => { const rowStyle: React.CSSProperties = {}; if (item.type === "header") { - rowClass = "bg-gray-700 hover:bg-gray-600 font-semibold sticky top-[40px] z-10"; + // Calculate z-index based on header position to prevent text bleed-through + // Each subsequent header should have a higher z-index than the previous + const headerIndex = runOfShow.items + .slice(0, index + 1) + .filter((i) => i.type === "header").length; + const zIndex = 10 + headerIndex; + rowClass = "bg-gray-700 hover:bg-gray-600 font-semibold sticky top-[40px]"; + rowStyle.zIndex = zIndex; } else { if (item.highlightColor && !isCurrent && !isNext) { rowStyle.backgroundColor = item.highlightColor; @@ -699,7 +712,8 @@ const ShowModePage: React.FC = () => { "bg-green-600/40 hover:bg-green-600/50 border-l-4 border-green-400"; delete rowStyle.backgroundColor; } else if (isNext) { - rowClass = "bg-red-600/40 hover:bg-red-600/50 border-l-4 border-red-400"; + rowClass = + "bg-orange-600/40 hover:bg-orange-600/50 border-l-4 border-orange-400"; delete rowStyle.backgroundColor; } } From 0f0f9e80c6b3e19dcadb7c2e35479ace20edfa66 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Thu, 9 Oct 2025 21:50:39 -0400 Subject: [PATCH 2/3] feat: implement comprehensive SEO optimization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add complete SEO infrastructure with meta tags, structured data, and automated sitemap generation to improve search engine visibility. SEO Infrastructure: - Add automated sitemap generation script (23 URLs with priorities) - Create reusable SEO utilities (canonical URLs, breadcrumbs, helpers) - Add SEOHelmet component for consistent meta tag implementation - Integrate sitemap generation into build process Structured Data (Schema.org): - Add Organization schema with GitHub social proof - Add SoftwareApplication schemas for AcoustIQ Lite & Pro - Add BreadcrumbList schemas for navigation hierarchy - Add FAQ schemas (25 questions across 5 categories) - Remove placeholder rating data to prevent guideline violations Meta Tags & SEO: - Add complete meta tags to 10 major pages: - Landing, Audio, Video, Lighting, Production pages - Analyzer hub, Lite, and Pro pages - Resources and Rates pages - Optimize Landing page description (234→151 chars) - Add canonical URLs to prevent duplicate content - Add Open Graph and Twitter Card tags for social sharing - Add comprehensive keyword targeting Performance & Technical SEO: - Add Netlify performance headers (caching, security) - Add Netlify Lighthouse CI plugin for monitoring - Optimize robots.txt with 20+ disallow rules for auth-gated content - Normalize sitemap URLs with trailing slashes - Remove inaccurate lastmod tags from sitemap Files Created: - apps/web/scripts/generate-sitemap.js - apps/web/src/components/SEOHelmet.tsx - apps/web/src/utils/canonical-url.ts - apps/web/src/utils/breadcrumb-schema.ts - apps/web/src/utils/seo-helpers.ts - apps/web/src/schemas/organization-schema.ts - apps/web/src/schemas/software-schemas.ts - apps/web/src/schemas/faq-schemas.ts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/web/package.json | 3 +- apps/web/public/robots.txt | 30 + apps/web/scripts/generate-sitemap.js | 97 ++ apps/web/src/components/SEOHelmet.tsx | 44 + apps/web/src/pages/AnalyzerLitePage.tsx | 104 +- apps/web/src/pages/AnalyzerPage.tsx | 164 ++- apps/web/src/pages/AnalyzerProPage.tsx | 281 +++-- apps/web/src/pages/AudioPage.tsx | 1199 ++++++++++--------- apps/web/src/pages/Landing.tsx | 38 +- apps/web/src/pages/LightingPage.tsx | 79 +- apps/web/src/pages/ProductionPage.tsx | 849 ++++++------- apps/web/src/pages/RatesPage.tsx | 198 +-- apps/web/src/pages/VideoPage.tsx | 350 +++--- apps/web/src/schemas/faq-schemas.ts | 175 +++ apps/web/src/schemas/organization-schema.ts | 20 + apps/web/src/schemas/software-schemas.ts | 116 ++ apps/web/src/utils/breadcrumb-schema.ts | 87 ++ apps/web/src/utils/canonical-url.ts | 31 + apps/web/src/utils/seo-helpers.ts | 203 ++++ netlify.toml | 42 +- 20 files changed, 2665 insertions(+), 1445 deletions(-) create mode 100644 apps/web/scripts/generate-sitemap.js create mode 100644 apps/web/src/components/SEOHelmet.tsx create mode 100644 apps/web/src/schemas/faq-schemas.ts create mode 100644 apps/web/src/schemas/organization-schema.ts create mode 100644 apps/web/src/schemas/software-schemas.ts create mode 100644 apps/web/src/utils/breadcrumb-schema.ts create mode 100644 apps/web/src/utils/canonical-url.ts create mode 100644 apps/web/src/utils/seo-helpers.ts diff --git a/apps/web/package.json b/apps/web/package.json index bdeadeb..c5e8fbf 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build", + "build": "node scripts/generate-sitemap.js && vite build", + "generate:sitemap": "node scripts/generate-sitemap.js", "lint": "eslint .", "preview": "vite preview" }, diff --git a/apps/web/public/robots.txt b/apps/web/public/robots.txt index c03ab41..8abaf35 100644 --- a/apps/web/public/robots.txt +++ b/apps/web/public/robots.txt @@ -1,6 +1,36 @@ # www.robotstxt.org +# SoundDocs Robots.txt - SEO Optimized User-agent: * Allow: / +# Disallow protected routes (saves crawl budget) +Disallow: /dashboard +Disallow: /profile +Disallow: /shared-with-me +Disallow: /patch-sheet/ +Disallow: /stage-plot/ +Disallow: /production-schedule/ +Disallow: /run-of-show/ +Disallow: /rider/ +Disallow: /corporate-mic-plot/ +Disallow: /theater-mic-plot/ +Disallow: /pixel-map/ +Disallow: /comms-planner/ +Disallow: /all-patch-sheets +Disallow: /all-stage-plots +Disallow: /all-mic-plots +Disallow: /all-pixel-maps +Disallow: /all-comms-plans +Disallow: /all-production-schedules +Disallow: /all-run-of-shows +Disallow: /all-riders + +# Allow shared public documents (optional - can be indexed) +Allow: /shared/ + +# Crawl delay to prevent aggressive crawling +Crawl-delay: 1 + +# Sitemap reference Sitemap: https://sounddocs.org/sitemap.xml diff --git a/apps/web/scripts/generate-sitemap.js b/apps/web/scripts/generate-sitemap.js new file mode 100644 index 0000000..1c220d3 --- /dev/null +++ b/apps/web/scripts/generate-sitemap.js @@ -0,0 +1,97 @@ +// generate-sitemap.js +// Automated sitemap generation for SoundDocs +// Run during build process to ensure all public pages are indexed + +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const BASE_URL = "https://sounddocs.org"; + +// Define all public pages with priority and changefreq +// Priority: 1.0 (highest) to 0.1 (lowest) +// Changefreq: always, hourly, daily, weekly, monthly, yearly, never +const pages = [ + // Core Pages + { url: "/", priority: 1.0, changefreq: "weekly" }, + { url: "/login/", priority: 0.8, changefreq: "monthly" }, + { url: "/signup/", priority: 0.8, changefreq: "monthly" }, + + // Category Pages + { url: "/audio/", priority: 0.9, changefreq: "weekly" }, + { url: "/video/", priority: 0.9, changefreq: "weekly" }, + { url: "/lighting/", priority: 0.9, changefreq: "weekly" }, + { url: "/production/", priority: 0.9, changefreq: "weekly" }, + + // Analyzer Pages + { url: "/analyzer/", priority: 0.9, changefreq: "weekly" }, + { url: "/analyzer/lite/", priority: 0.8, changefreq: "monthly" }, + { url: "/analyzer/pro/", priority: 0.8, changefreq: "monthly" }, + + // Resource Hub + { url: "/resources/", priority: 0.9, changefreq: "weekly" }, + { url: "/resources/rates/", priority: 0.7, changefreq: "monthly" }, + { url: "/resources/audio-formulas/", priority: 0.7, changefreq: "monthly" }, + { url: "/resources/formulas/audio/", priority: 0.7, changefreq: "monthly" }, + { url: "/resources/formulas/video/", priority: 0.7, changefreq: "monthly" }, + { url: "/resources/formulas/lighting/", priority: 0.7, changefreq: "monthly" }, + { url: "/resources/reference-guides/", priority: 0.7, changefreq: "monthly" }, + { url: "/resources/guides/pinouts/", priority: 0.6, changefreq: "monthly" }, + { url: "/resources/guides/frequency-bands/", priority: 0.6, changefreq: "monthly" }, + { url: "/resources/guides/db-chart/", priority: 0.6, changefreq: "monthly" }, + { url: "/resources/guides/glossaries/", priority: 0.6, changefreq: "monthly" }, + + // Legal Pages + { url: "/privacy-policy/", priority: 0.3, changefreq: "yearly" }, + { url: "/terms-of-service/", priority: 0.3, changefreq: "yearly" }, +]; + +/** + * Generates XML sitemap with all public pages + * @returns {string} Complete XML sitemap + */ +const generateSitemap = () => { + const urlElements = pages + .map( + (page) => ` + ${BASE_URL}${page.url} + ${page.changefreq} + ${page.priority} + `, + ) + .join("\n"); + + const sitemap = ` + +${urlElements} +`; + + return sitemap; +}; + +/** + * Write sitemap to public directory + */ +const writeSitemap = () => { + try { + const sitemap = generateSitemap(); + const publicDir = path.resolve(__dirname, "../public"); + const sitemapPath = path.join(publicDir, "sitemap.xml"); + + fs.writeFileSync(sitemapPath, sitemap, "utf8"); + + console.log("✅ Sitemap generated successfully!"); + console.log(`📄 ${pages.length} URLs included`); + console.log(`📍 Location: ${sitemapPath}`); + console.log(`🗓️ Last modified: ${BUILD_DATE}`); + } catch (error) { + console.error("❌ Error generating sitemap:", error); + process.exit(1); + } +}; + +// Execute sitemap generation +writeSitemap(); diff --git a/apps/web/src/components/SEOHelmet.tsx b/apps/web/src/components/SEOHelmet.tsx new file mode 100644 index 0000000..1d55b52 --- /dev/null +++ b/apps/web/src/components/SEOHelmet.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { Helmet } from "react-helmet"; +import { generatePageSEO, type PageSEO } from "../utils/seo-helpers"; + +interface SEOHelmetProps { + config: PageSEO; + children?: React.ReactNode; +} + +/** + * Reusable SEO Helmet component + * Generates all standard meta tags from a configuration object + */ +export const SEOHelmet: React.FC = ({ config, children }) => { + const seo = generatePageSEO(config); + + return ( + + {seo.title} + + {seo.keywords && } + + {/* Canonical URL */} + + + {/* Open Graph */} + + + + + + + + {/* Twitter Card */} + + + + + + {/* Additional custom meta tags */} + {children} + + ); +}; diff --git a/apps/web/src/pages/AnalyzerLitePage.tsx b/apps/web/src/pages/AnalyzerLitePage.tsx index cc6422f..8cafdba 100644 --- a/apps/web/src/pages/AnalyzerLitePage.tsx +++ b/apps/web/src/pages/AnalyzerLitePage.tsx @@ -1,10 +1,14 @@ import React from "react"; import { useNavigate } from "react-router-dom"; +import { Helmet } from "react-helmet"; import { ArrowLeftCircle, Mic } from "lucide-react"; import Header from "../components/Header"; import Footer from "../components/Footer"; import AudioAnalyzerSection from "../components/analyzer/AudioAnalyzerSection"; import { supabase } from "../lib/supabase"; +import { getCanonicalUrl } from "../utils/canonical-url"; +import { generateBreadcrumbSchema, createBreadcrumbs } from "../utils/breadcrumb-schema"; +import { acoustiqLiteSchema } from "../schemas/software-schemas"; const AnalyzerLitePage: React.FC = () => { const navigate = useNavigate(); @@ -18,36 +22,80 @@ const AnalyzerLitePage: React.FC = () => { } }; + const breadcrumbs = createBreadcrumbs.analyzer("lite"); + return ( -
-
- -
-
- -
- -

AcoustIQ Lite

+ <> + + AcoustIQ Lite - Free Browser Audio Analyzer | FFT & RTA | SoundDocs + + + + {/* Canonical URL */} + + + {/* Open Graph */} + + + + + + + {/* Twitter Card */} + + + + + + {/* Structured Data */} + + + + +
+
+ +
+
+ +
+ +

AcoustIQ Lite

+
+

+ Use your browser's microphone for basic single-channel measurements. Ideal for quick + checks without any setup. +

-

- Use your browser's microphone for basic single-channel measurements. Ideal for quick - checks without any setup. -

-
- -
- -
-
- -
-
+ +
+ +
+ + +