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 },