From 44f12f0e538844c7d7163e4dc33649151e8bfcd3 Mon Sep 17 00:00:00 2001 From: David Montero Date: Fri, 29 May 2026 18:11:28 +0200 Subject: [PATCH] feat(seo+nav+community): public-project indexing + classroom nav + examples grid Search-engine indexing of public projects - Update robots.txt to also list /sitemap-projects.xml so Googlebot / Bingbot discover every public project's canonical /:username/:slug URL. - Add /docs/github-sync + /classroom entries to seoRoutes.ts so the build-time sitemap.xml picks them up. Navigation polish - AppHeader gains a "For schools" link between Pricing and Download. - LandingPage's pricing section gets a slim banner under the cards pointing institutional visitors to /classroom (visible discovery path, not just a footer link). - Localised header.nav.classroom + landing.pricing.classroomBanner + landing.pricing.classroomCta across all 9 maintained locales (en/es/ pt-br/fr/de/it/ja/ru/zh-cn). Community examples - New CommunityProjectsGrid component lives next to ExamplesGallery on /examples. Fetches /api/projects/featured (Pro-overlay-only endpoint) and renders the top public projects ranked by run_count. Quietly hides itself when the endpoint returns nothing or fails, so the OSS build still ships cleanly. Co-Authored-By: Claude Opus 4.7 --- frontend/public/robots.txt | 6 +- .../examples/CommunityProjectsGrid.tsx | 152 ++++++++++++++++++ frontend/src/components/layout/AppHeader.tsx | 3 + frontend/src/i18n/locales/de/common.json | 7 +- frontend/src/i18n/locales/en/common.json | 7 +- frontend/src/i18n/locales/es/common.json | 7 +- frontend/src/i18n/locales/fr/common.json | 7 +- frontend/src/i18n/locales/it/common.json | 7 +- frontend/src/i18n/locales/ja/common.json | 7 +- frontend/src/i18n/locales/pt-br/common.json | 7 +- frontend/src/i18n/locales/ru/common.json | 7 +- frontend/src/i18n/locales/zh-cn/common.json | 7 +- frontend/src/pages/ExamplesPage.tsx | 2 + frontend/src/pages/LandingPage.css | 28 ++++ frontend/src/pages/LandingPage.tsx | 11 ++ frontend/src/seoRoutes.ts | 13 ++ 16 files changed, 258 insertions(+), 20 deletions(-) create mode 100644 frontend/src/components/examples/CommunityProjectsGrid.tsx diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt index 1ee85006..7c3fee42 100644 --- a/frontend/public/robots.txt +++ b/frontend/public/robots.txt @@ -5,12 +5,14 @@ Allow: / Disallow: /login Disallow: /register -# Dynamic user/project pages — noindex is also set via meta tag +# UUID-based duplicate of /:username/:slug — keep crawlers on the canonical +# user-friendly URL instead of the opaque /project/ alternate. Disallow: /project/ Disallow: /admin # Prevent crawling of API endpoints Disallow: /api/ -# Sitemap +# Sitemap (static build-time routes + dynamic public projects) Sitemap: https://velxio.dev/sitemap.xml +Sitemap: https://velxio.dev/sitemap-projects.xml diff --git a/frontend/src/components/examples/CommunityProjectsGrid.tsx b/frontend/src/components/examples/CommunityProjectsGrid.tsx new file mode 100644 index 00000000..27e36feb --- /dev/null +++ b/frontend/src/components/examples/CommunityProjectsGrid.tsx @@ -0,0 +1,152 @@ +/** + * CommunityProjectsGrid — second section on /examples beneath the + * hardcoded official examples. Fetches `/api/projects/featured` (a + * pro-overlay-only endpoint that returns the top public projects ranked + * by run_count). + * + * Renders nothing when the fetch fails / returns empty so the OSS build + * (which has no projects route at all) degrades to its old layout + * without surfacing an error. + */ + +import { useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; +import { useLocalizedHref } from '../../i18n/useLocalizedNavigate'; + +type FeaturedProject = { + id: string; + name: string; + description: string | null; + slug: string; + owner_username: string; + board_type: string; + run_count: number; + compile_count: number; +}; + +const BOARD_LABELS: Record = { + 'arduino-uno': 'Arduino Uno', + 'arduino-nano': 'Arduino Nano', + 'arduino-mega': 'Arduino Mega', + 'esp32': 'ESP32', + 'esp32-s3': 'ESP32-S3', + 'esp32-c3': 'ESP32-C3', + 'raspberry-pi-pico': 'Pico', + 'raspberry-pi-3': 'Pi 3', + 'attiny85': 'ATtiny85', +}; + +export const CommunityProjectsGrid = () => { + const [projects, setProjects] = useState(null); + const [hidden, setHidden] = useState(false); + const localize = useLocalizedHref(); + + useEffect(() => { + let cancelled = false; + fetch('/api/projects/featured?limit=12', { credentials: 'include' }) + .then(async (r) => { + if (!r.ok) throw new Error(`status ${r.status}`); + return (await r.json()) as FeaturedProject[]; + }) + .then((rows) => { + if (cancelled) return; + if (!rows.length) { + setHidden(true); + return; + } + setProjects(rows); + }) + .catch(() => { if (!cancelled) setHidden(true); }); + return () => { cancelled = true; }; + }, []); + + if (hidden || !projects) return null; + + return ( +
+
+

Featured community projects

+

+ The most-run public circuits on Velxio. Open one to see how it's wired, + remix the code, and run it live. +

+
+
+ {projects.map((p) => ( + +
{p.name || 'Untitled'}
+
+ {BOARD_LABELS[p.board_type] || p.board_type} + · + {p.run_count.toLocaleString()} runs +
+
by @{p.owner_username}
+ {p.description && ( +
{p.description.slice(0, 140)}
+ )} + + ))} +
+
+ ); +}; + +const styles: Record = { + shell: { + maxWidth: 1280, + margin: '40px auto 60px', + padding: '0 24px', + color: '#ddd', + }, + header: { marginBottom: 24 }, + title: { fontSize: 24, fontWeight: 600, color: '#fff', margin: 0 }, + sub: { color: '#aaa', fontSize: 14, margin: '6px 0 0 0', maxWidth: 640 }, + grid: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', + gap: 14, + }, + card: { + display: 'block', + background: '#1e1e1e', + border: '1px solid #2a2a2a', + borderRadius: 8, + padding: '14px 16px', + textDecoration: 'none', + color: 'inherit', + transition: 'border-color 0.12s, transform 0.12s', + }, + cardName: { + fontSize: 15, + fontWeight: 600, + color: '#fff', + marginBottom: 6, + }, + cardMeta: { + display: 'flex', + gap: 6, + alignItems: 'center', + color: '#888', + fontSize: 12, + }, + cardOwner: { + color: '#4fc3f7', + fontSize: 12, + marginTop: 6, + }, + cardDesc: { + color: '#aaa', + fontSize: 13, + marginTop: 8, + lineHeight: 1.45, + display: '-webkit-box', + WebkitLineClamp: 2, + WebkitBoxOrient: 'vertical', + overflow: 'hidden', + }, +}; diff --git a/frontend/src/components/layout/AppHeader.tsx b/frontend/src/components/layout/AppHeader.tsx index 791f1bda..911a0083 100644 --- a/frontend/src/components/layout/AppHeader.tsx +++ b/frontend/src/components/layout/AppHeader.tsx @@ -142,6 +142,9 @@ export const AppHeader: React.FC = ({ autoSave }) => { {t('header.nav.pricing')} + + {t('header.nav.classroom', 'For schools')} + { > + ); }; diff --git a/frontend/src/pages/LandingPage.css b/frontend/src/pages/LandingPage.css index bc9d39aa..4b75f0ae 100644 --- a/frontend/src/pages/LandingPage.css +++ b/frontend/src/pages/LandingPage.css @@ -1368,6 +1368,34 @@ border-color: rgba(255, 255, 255, 0.35); } +.pricing-classroom-banner { + margin: 28px auto 0; + max-width: 920px; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 14px; + padding: 14px 22px; + border-radius: 10px; + background: rgba(0, 184, 212, 0.06); + border: 1px solid rgba(0, 184, 212, 0.2); + color: #b5b5b5; + font-size: 14px; + text-align: center; +} + +.pricing-classroom-banner-cta { + color: #4fc3f7; + font-weight: 600; + text-decoration: none; + white-space: nowrap; +} + +.pricing-classroom-banner-cta:hover { + text-decoration: underline; +} + @media (max-width: 900px) { .pricing-grid { grid-template-columns: 1fr; diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index 9b48963c..ef584eb6 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -1152,6 +1152,17 @@ export const LandingPage: React.FC = () => { +
+ + {t( + 'landing.pricing.classroomBanner', + 'Bringing Velxio into a course? Velxio for Classroom gives every student Pro access from $40/year.', + )} + + + {t('landing.pricing.classroomCta', 'See classroom plans →')} + +
{/* Support */} diff --git a/frontend/src/seoRoutes.ts b/frontend/src/seoRoutes.ts index 472543d6..8cc7c260 100644 --- a/frontend/src/seoRoutes.ts +++ b/frontend/src/seoRoutes.ts @@ -423,6 +423,19 @@ export const SEO_ROUTES: SeoRoute[] = [ }, }, + // ── GitHub Sync docs — Phase 3 D3.5 companion + { + path: '/docs/github-sync', + priority: 0.7, + changefreq: 'monthly', + seoMeta: { + title: 'GitHub Sync — Velxio Pro docs', + description: + "Velxio Pro's GitHub Sync commits every project save (sketch.ino + velxio.json + auto-generated README) to a repo you control. Setup walkthrough, security model and FAQ.", + url: `${DOMAIN}/docs/github-sync`, + }, + }, + // ── Auth / admin (noindex) { path: '/login', noindex: true }, { path: '/register', noindex: true },