diff --git a/CLAUDE.md b/CLAUDE.md index 2063761..19f51c0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -243,6 +243,14 @@ Wire positions auto-update when components move via `updateWirePositions()`. - [frontend/src/pages/UserProfilePage.tsx](frontend/src/pages/UserProfilePage.tsx) - Profile with project grid - [frontend/src/pages/ProjectPage.tsx](frontend/src/pages/ProjectPage.tsx) - Loads project into editor +### Frontend - SEO & Public Files +- `frontend/index.html` — Full SEO meta tags, OG, Twitter Card, JSON-LD. **Domain is `https://velxio.dev`** — update if domain changes. +- `frontend/public/favicon.svg` — SVG chip favicon (scales to all sizes) +- `frontend/public/og-image.svg` — 1200×630 social preview image (OG/Twitter). Export as PNG for max compatibility. +- `frontend/public/robots.txt` — Allow all crawlers, points to sitemap +- `frontend/public/sitemap.xml` — All public routes with priorities +- `frontend/public/manifest.webmanifest` — PWA manifest, theme color `#007acc` + ### Docker & CI - [Dockerfile.standalone](Dockerfile.standalone) - Multi-stage Docker build - [.github/workflows/docker-publish.yml](.github/workflows/docker-publish.yml) - Publishes to GHCR + Docker Hub on push to master diff --git a/frontend/index.html b/frontend/index.html index 072a57e..1178b48 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,206 @@ - - frontend + + + Velxio — Free Local Arduino Emulator | AVR8 · RP2040 · 48+ Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/frontend/package.json b/frontend/package.json index 29817ab..cbf2afe 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "generate:metadata": "cd .. && npx tsx scripts/generate-component-metadata.ts", + "generate:favicons": "node ../scripts/generate-favicons.mjs", "dev": "npm run generate:metadata && vite", "build": "npm run generate:metadata && tsc -b && vite build", "build:docker": "vite build", diff --git a/frontend/public/android-chrome-192.png b/frontend/public/android-chrome-192.png new file mode 100644 index 0000000..6298f3d Binary files /dev/null and b/frontend/public/android-chrome-192.png differ diff --git a/frontend/public/android-chrome-512.png b/frontend/public/android-chrome-512.png new file mode 100644 index 0000000..07dd305 Binary files /dev/null and b/frontend/public/android-chrome-512.png differ diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png new file mode 100644 index 0000000..96d47c6 Binary files /dev/null and b/frontend/public/apple-touch-icon.png differ diff --git a/frontend/public/favicon-16x16.png b/frontend/public/favicon-16x16.png new file mode 100644 index 0000000..59bdac4 Binary files /dev/null and b/frontend/public/favicon-16x16.png differ diff --git a/frontend/public/favicon-32x32.png b/frontend/public/favicon-32x32.png new file mode 100644 index 0000000..7d10170 Binary files /dev/null and b/frontend/public/favicon-32x32.png differ diff --git a/frontend/public/favicon-48x48.png b/frontend/public/favicon-48x48.png new file mode 100644 index 0000000..ffb1f6f Binary files /dev/null and b/frontend/public/favicon-48x48.png differ diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..ca07804 Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..43c9633 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/frontend/public/manifest.webmanifest b/frontend/public/manifest.webmanifest new file mode 100644 index 0000000..39618e4 --- /dev/null +++ b/frontend/public/manifest.webmanifest @@ -0,0 +1,51 @@ +{ + "name": "Velxio — Arduino Emulator", + "short_name": "Velxio", + "description": "Free local Arduino emulator with real AVR8 CPU emulation, 48+ electronic components, Monaco Editor, and Serial Monitor. No cloud, no latency.", + "start_url": "/editor", + "scope": "/", + "display": "standalone", + "orientation": "landscape", + "background_color": "#0d0d0f", + "theme_color": "#007acc", + "categories": ["developer", "education", "productivity"], + "lang": "en", + "icons": [ + { + "src": "/favicon-16x16.png", + "sizes": "16x16", + "type": "image/png" + }, + { + "src": "/favicon-32x32.png", + "sizes": "32x32", + "type": "image/png" + }, + { + "src": "/android-chrome-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/android-chrome-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/favicon.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any maskable" + } + ], + "screenshots": [ + { + "src": "/og-image.svg", + "sizes": "1200x630", + "type": "image/svg+xml", + "label": "Velxio Arduino Emulator" + } + ] +} diff --git a/frontend/public/og-image.svg b/frontend/public/og-image.svg new file mode 100644 index 0000000..e4a8f89 --- /dev/null +++ b/frontend/public/og-image.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OPEN SOURCE · FREE · LOCAL + + + + + + + + + + + + + + + + + VELXIO + + + + Arduino Emulator — Real AVR8 · 48+ Components · Monaco Editor + + + + + + + arduino-cli + + + avr8js + + + rp2040js + + + TypeScript + + + FastAPI + + + React + + + + + + + + github.com/davidmonterocrespo24/velxio + + diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt new file mode 100644 index 0000000..25208a3 --- /dev/null +++ b/frontend/public/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: / + +# Sitemap +Sitemap: https://velxio.dev/sitemap.xml diff --git a/frontend/public/sitemap.xml b/frontend/public/sitemap.xml new file mode 100644 index 0000000..964b4eb --- /dev/null +++ b/frontend/public/sitemap.xml @@ -0,0 +1,40 @@ + + + + + https://velxio.dev/ + 2026-03-06 + weekly + 1.0 + + + + https://velxio.dev/editor + 2026-03-06 + weekly + 0.9 + + + + https://velxio.dev/examples + 2026-03-06 + monthly + 0.7 + + + + https://velxio.dev/login + 2026-03-06 + yearly + 0.3 + + + + https://velxio.dev/register + 2026-03-06 + yearly + 0.3 + + + diff --git a/frontend/src/pages/LandingPage.css b/frontend/src/pages/LandingPage.css index bebf0c1..d9997e7 100644 --- a/frontend/src/pages/LandingPage.css +++ b/frontend/src/pages/LandingPage.css @@ -1,16 +1,16 @@ /* ── Base ──────────────────────────────────────────────── */ :root { --accent: #007acc; - --accent-glow: rgba(0, 122, 204, 0.35); - --accent2: #00c896; - --bg: #0d0d0f; - --bg-card: #131318; - --bg-alt: #0a0a0d; - --border: #1e1e2a; - --text: #e2e2e8; - --text-muted: #7a7a8c; + --bg: #09090b; + --bg-card: #0f0f12; + --bg-alt: #07070a; + --border: #18181f; + --border-hi: #252530; + --text: #d8d8e0; + --text-muted: #72727e; --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; --mono: 'Fira Code', 'Cascadia Code', 'Consolas', monospace; + --radius: 3px; } .landing { @@ -30,9 +30,9 @@ align-items: center; justify-content: space-between; padding: 0 40px; - height: 56px; - background: rgba(13, 13, 15, 0.88); - backdrop-filter: blur(12px); + height: 52px; + background: rgba(9, 9, 11, 0.92); + backdrop-filter: blur(10px); border-bottom: 1px solid var(--border); } @@ -40,22 +40,23 @@ display: flex; align-items: center; gap: 10px; - font-size: 18px; + font-size: 17px; font-weight: 700; color: var(--text); - letter-spacing: -0.3px; + letter-spacing: -0.2px; + font-family: var(--mono); } .landing-nav-brand svg { - width: 22px; - height: 22px; + width: 20px; + height: 20px; color: var(--accent); } .landing-nav-links { display: flex; align-items: center; - gap: 8px; + gap: 4px; } .nav-link { @@ -65,135 +66,131 @@ padding: 5px 12px; color: var(--text-muted); text-decoration: none; - font-size: 13.5px; - border-radius: 6px; + font-size: 13px; + border-radius: var(--radius); transition: color 0.15s, background 0.15s; } .nav-link svg { - width: 16px; - height: 16px; + width: 15px; + height: 15px; } .nav-link:hover { color: var(--text); - background: rgba(255,255,255,0.06); + background: rgba(255, 255, 255, 0.05); } .nav-btn-primary { - padding: 6px 16px; + padding: 5px 14px; background: var(--accent); color: #fff; text-decoration: none; - font-size: 13.5px; + font-size: 13px; font-weight: 600; - border-radius: 6px; - transition: background 0.15s, box-shadow 0.15s; + border-radius: var(--radius); + transition: background 0.15s; + border: 1px solid rgba(255,255,255,0.1); } .nav-btn-primary:hover { - background: #0990e0; - box-shadow: 0 0 12px var(--accent-glow); + background: #1a8fd1; } /* ── Hero ─────────────────────────────────────────────── */ .landing-hero { - position: relative; - min-height: 88vh; + display: grid; + grid-template-columns: 1fr 1fr; + align-items: center; + gap: 48px; + padding: 80px 80px 80px 80px; + max-width: 1200px; + margin: 0 auto; + min-height: 80vh; +} + +.hero-left { + display: flex; + flex-direction: column; + gap: 0; +} + +.hero-eyebrow { display: flex; align-items: center; - justify-content: center; - padding: 80px 40px 60px; - overflow: hidden; + gap: 8px; + margin-bottom: 20px; } -.hero-glow { - position: absolute; - top: -120px; - left: 50%; - transform: translateX(-50%); - width: 700px; - height: 700px; - background: radial-gradient(ellipse, rgba(0,122,204,0.14) 0%, transparent 70%); - pointer-events: none; -} - -.hero-content { - position: relative; - z-index: 2; - max-width: 720px; - text-align: center; -} - -.hero-badge { - display: inline-block; - margin-bottom: 24px; - padding: 4px 14px; - background: rgba(0, 122, 204, 0.12); - border: 1px solid rgba(0, 122, 204, 0.3); - border-radius: 20px; - font-size: 12px; +.eyebrow-tag { + font-family: var(--mono); + font-size: 10px; font-weight: 600; + letter-spacing: 1.2px; color: var(--accent); - letter-spacing: 0.5px; - text-transform: uppercase; + border: 1px solid rgba(0, 122, 204, 0.3); + padding: 2px 7px; + border-radius: 2px; +} + +.eyebrow-dot { + width: 3px; + height: 3px; + background: var(--border-hi); + border-radius: 50%; } .hero-title { - margin: 0 0 20px; - font-size: clamp(36px, 6vw, 64px); + margin: 0 0 16px; + font-size: clamp(32px, 4.5vw, 56px); font-weight: 800; line-height: 1.1; letter-spacing: -1.5px; - color: #fff; + color: #e8e8f0; } .hero-accent { - background: linear-gradient(90deg, var(--accent), var(--accent2)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; + color: var(--accent); + font-weight: 800; } .hero-subtitle { - margin: 0 0 40px; - font-size: 17px; + margin: 0 0 32px; + font-size: 15.5px; line-height: 1.7; color: var(--text-muted); - max-width: 560px; - margin-left: auto; - margin-right: auto; + max-width: 480px; } .hero-ctas { display: flex; - gap: 14px; - justify-content: center; + gap: 12px; flex-wrap: wrap; + margin-bottom: 28px; } .cta-primary { display: flex; align-items: center; gap: 8px; - padding: 13px 28px; + padding: 11px 24px; background: var(--accent); color: #fff; text-decoration: none; - font-size: 15px; + font-size: 14px; font-weight: 700; - border-radius: 8px; - transition: background 0.15s, box-shadow 0.2s, transform 0.15s; + border-radius: var(--radius); + transition: background 0.15s, transform 0.1s; + border: 1px solid rgba(255,255,255,0.1); } .cta-primary svg { - width: 18px; - height: 18px; + width: 16px; + height: 16px; } .cta-primary:hover { - background: #0990e0; - box-shadow: 0 0 24px var(--accent-glow); + background: #1a8fd1; transform: translateY(-1px); } @@ -201,65 +198,73 @@ display: flex; align-items: center; gap: 8px; - padding: 13px 28px; - background: rgba(255,255,255,0.05); - border: 1px solid var(--border); + padding: 11px 24px; + background: transparent; + border: 1px solid var(--border-hi); color: var(--text); text-decoration: none; - font-size: 15px; + font-size: 14px; font-weight: 600; - border-radius: 8px; - transition: background 0.15s, border-color 0.15s, transform 0.15s; + border-radius: var(--radius); + transition: border-color 0.15s, background 0.15s, transform 0.1s; } .cta-secondary svg { - width: 18px; - height: 18px; + width: 16px; + height: 16px; } .cta-secondary:hover { - background: rgba(255,255,255,0.09); - border-color: #444; + background: rgba(255, 255, 255, 0.04); + border-color: #333; transform: translateY(-1px); } -/* ── Hero decoration (floating chips) ─────────────────── */ -.hero-decoration { - position: absolute; - inset: 0; - pointer-events: none; - z-index: 1; +/* Specs strip */ +.hero-specs { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; } -.deco-chip { - position: absolute; - opacity: 0.04; - animation: floatChip 8s ease-in-out infinite; +.spec-pill { + font-family: var(--mono); + font-size: 11.5px; + color: var(--text-muted); + background: var(--bg-card); + border: 1px solid var(--border); + padding: 3px 8px; + border-radius: 2px; } -.deco-chip:nth-child(1) { top: 8%; left: 5%; width: 64px; } -.deco-chip:nth-child(2) { top: 15%; left: 85%; width: 40px; } -.deco-chip:nth-child(3) { top: 55%; left: 4%; width: 48px; } -.deco-chip:nth-child(4) { top: 70%; left: 88%; width: 56px; } -.deco-chip:nth-child(5) { top: 35%; left: 92%; width: 36px; } -.deco-chip:nth-child(6) { top: 80%; left: 10%; width: 44px; } -.deco-chip:nth-child(7) { top: 25%; left: 2%; width: 32px; } -.deco-chip:nth-child(8) { top: 90%; left: 75%; width: 38px; } -.deco-chip:nth-child(9) { top: 5%; left: 50%; width: 30px; } -.deco-chip:nth-child(10) { top: 60%; left: 95%; width: 42px; } -.deco-chip:nth-child(11) { top: 88%; left: 40%; width: 36px; } -.deco-chip:nth-child(12) { top: 45%; left: 0%; width: 50px; } +.spec-sep { + color: var(--border-hi); + font-size: 11px; +} -@keyframes floatChip { - 0%, 100% { transform: translateY(0) rotate(0deg); } - 50% { transform: translateY(-14px) rotate(8deg); } +/* Hero right — schematic */ +.hero-right { + display: flex; + align-items: center; + justify-content: center; +} + +.schematic-svg { + width: 100%; + max-width: 440px; + height: auto; + border: 1px solid var(--border); + border-radius: 4px; + filter: drop-shadow(0 8px 32px rgba(0, 0, 0, 0.6)); } /* ── Sections ─────────────────────────────────────────── */ .landing-section { - padding: 80px 40px; - max-width: 1100px; + padding: 80px 80px; + max-width: 1200px; margin: 0 auto; + border-top: 1px solid var(--border); } .landing-section-alt { @@ -270,236 +275,276 @@ padding: 80px 0; } -.landing-section-alt > * { - max-width: 1100px; +.landing-section-alt .section-header, +.landing-section-alt .features-grid { + max-width: 1200px; margin-left: auto; margin-right: auto; - padding-left: 40px; - padding-right: 40px; + padding-left: 80px; + padding-right: 80px; +} + +.section-header { + margin-bottom: 40px; +} + +.section-label { + display: block; + font-family: var(--mono); + font-size: 11px; + color: var(--accent); + letter-spacing: 0.5px; + margin-bottom: 10px; + opacity: 0.7; } .section-title { - margin: 0 0 10px; - font-size: clamp(24px, 4vw, 36px); + margin: 0 0 8px; + font-size: clamp(22px, 3vw, 30px); font-weight: 800; - letter-spacing: -0.8px; - text-align: center; - color: #fff; + letter-spacing: -0.6px; + color: #e8e8f0; } .section-sub { - margin: 0 0 48px; - font-size: 16px; + margin: 0; + font-size: 14.5px; color: var(--text-muted); - text-align: center; + max-width: 480px; + line-height: 1.65; } -/* ── Boards grid ──────────────────────────────────────── */ +/* ── Boards ───────────────────────────────────────────── */ .boards-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); - gap: 20px; + grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); + gap: 16px; } .board-card { background: var(--bg-card); border: 1px solid var(--border); - border-radius: 12px; - padding: 28px 20px 20px; + border-radius: var(--radius); + padding: 24px 16px 18px; display: flex; flex-direction: column; align-items: center; gap: 16px; - transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s; + transition: border-color 0.2s; + position: relative; +} + +.board-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: var(--accent); + opacity: 0; + transition: opacity 0.2s; + border-radius: var(--radius) var(--radius) 0 0; } .board-card:hover { - border-color: rgba(0, 122, 204, 0.4); - box-shadow: 0 4px 24px rgba(0, 122, 204, 0.1); - transform: translateY(-3px); + border-color: var(--border-hi); +} + +.board-card:hover::before { + opacity: 1; } .board-svg { width: 100%; - max-width: 220px; + max-width: 210px; height: auto; - filter: drop-shadow(0 4px 12px rgba(0,0,0,0.5)); + filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.5)); } .board-info { display: flex; flex-direction: column; align-items: center; - gap: 4px; + gap: 5px; + width: 100%; + border-top: 1px solid var(--border); + padding-top: 14px; } .board-name { - font-size: 15px; + font-size: 13.5px; font-weight: 700; color: var(--text); } .board-chip { - font-size: 12px; + font-size: 11px; font-family: var(--mono); color: var(--text-muted); - background: rgba(255,255,255,0.05); - padding: 2px 8px; - border-radius: 4px; } -/* ── Features grid ────────────────────────────────────── */ +/* ── Features ─────────────────────────────────────────── */ .features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 20px; + gap: 1px; + background: var(--border); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; } .feature-card { - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: 12px; + background: var(--bg-alt); padding: 28px 24px; - transition: border-color 0.2s, transform 0.2s; + transition: background 0.15s; + border-radius: 0; } .feature-card:hover { - border-color: rgba(0, 122, 204, 0.35); - transform: translateY(-2px); + background: var(--bg-card); } .feature-icon { - width: 44px; - height: 44px; - background: rgba(0, 122, 204, 0.1); - border-radius: 10px; + width: 36px; + height: 36px; display: flex; align-items: center; justify-content: center; - margin-bottom: 16px; + margin-bottom: 14px; color: var(--accent); + border: 1px solid rgba(0, 122, 204, 0.25); + border-radius: 2px; + background: rgba(0, 122, 204, 0.06); } .feature-icon svg { - width: 22px; - height: 22px; + width: 18px; + height: 18px; } .feature-title { - margin: 0 0 8px; - font-size: 15px; + margin: 0 0 7px; + font-size: 14px; font-weight: 700; - color: #fff; + color: #e8e8f0; } .feature-desc { margin: 0; - font-size: 13.5px; + font-size: 13px; line-height: 1.65; color: var(--text-muted); } /* ── Support ──────────────────────────────────────────── */ .landing-support { - background: linear-gradient(135deg, #0d0d0f 0%, #0a1520 100%); + background: var(--bg-alt); border-top: 1px solid var(--border); - text-align: center; - padding: 80px 40px; - max-width: 100%; + padding: 72px 40px; } .support-content { - max-width: 560px; + max-width: 520px; margin: 0 auto; + text-align: center; } .support-icon { display: flex; justify-content: center; - margin-bottom: 20px; - color: #e85d75; - font-size: 0; + margin-bottom: 18px; + color: var(--accent); } .support-icon svg { - width: 44px; - height: 44px; - filter: drop-shadow(0 0 12px rgba(232, 93, 117, 0.5)); + width: 36px; + height: 36px; +} + +.support-title { + margin: 0 0 10px; + font-size: clamp(20px, 2.5vw, 26px); + font-weight: 800; + letter-spacing: -0.5px; + color: #e8e8f0; +} + +.support-sub { + margin: 0 0 28px; + font-size: 14px; + line-height: 1.7; + color: var(--text-muted); } .support-btns { display: flex; - gap: 14px; + gap: 12px; justify-content: center; flex-wrap: wrap; - margin-top: 8px; } .support-btn { display: flex; align-items: center; gap: 8px; - padding: 12px 24px; - font-size: 14px; + padding: 10px 20px; + font-size: 13.5px; font-weight: 600; - border-radius: 8px; + border-radius: var(--radius); text-decoration: none; - transition: opacity 0.15s, transform 0.15s, box-shadow 0.15s; + transition: opacity 0.15s, transform 0.1s; } .support-btn:hover { - opacity: 0.88; + opacity: 0.85; transform: translateY(-1px); } .support-btn-gh { - background: #24292e; - color: #fff; - border: 1px solid #444; -} - -.support-btn-gh:hover { - box-shadow: 0 4px 16px rgba(0,0,0,0.4); + background: #1c2128; + color: #e6edf3; + border: 1px solid #30363d; } .support-btn-pp { - background: #0070ba; + background: #003087; color: #fff; -} - -.support-btn-pp:hover { - box-shadow: 0 4px 16px rgba(0, 112, 186, 0.4); + border: 1px solid rgba(255,255,255,0.1); } .support-btn svg { - width: 18px; - height: 18px; + width: 16px; + height: 16px; flex-shrink: 0; } /* ── Footer ───────────────────────────────────────────── */ .landing-footer { - padding: 32px 40px; + padding: 28px 40px; border-top: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; + background: var(--bg); } .footer-brand { display: flex; align-items: center; gap: 8px; - font-size: 15px; + font-family: var(--mono); + font-size: 14px; font-weight: 700; color: var(--text-muted); } .footer-brand svg { - width: 18px; - height: 18px; + width: 16px; + height: 16px; color: var(--accent); } @@ -520,15 +565,16 @@ } .footer-copy { - font-size: 12px; - color: #444; + font-size: 11.5px; + color: #333; margin: 0; width: 100%; text-align: center; + font-family: var(--mono); } .footer-copy a { - color: #555; + color: #3a3a3a; text-decoration: none; transition: color 0.15s; } @@ -538,7 +584,39 @@ } /* ── Responsive ───────────────────────────────────────── */ -@media (max-width: 768px) { +@media (max-width: 900px) { + .landing-hero { + grid-template-columns: 1fr; + padding: 60px 32px; + min-height: auto; + gap: 40px; + } + + .hero-right { + order: -1; + } + + .schematic-svg { + max-width: 360px; + } + + .hero-title { + font-size: 36px; + } + + .landing-section, + .landing-support { + padding: 60px 32px; + } + + .landing-section-alt .section-header, + .landing-section-alt .features-grid { + padding-left: 32px; + padding-right: 32px; + } +} + +@media (max-width: 600px) { .landing-nav { padding: 0 20px; } @@ -548,21 +626,26 @@ } .landing-hero { - padding: 60px 20px 40px; - min-height: 70vh; - } - - .hero-title { - font-size: 32px; - } - - .hero-subtitle { - font-size: 15px; + padding: 48px 20px; } .landing-section, .landing-support { - padding: 60px 20px; + padding: 48px 20px; + } + + .landing-section-alt .section-header, + .landing-section-alt .features-grid { + padding-left: 20px; + padding-right: 20px; + } + + .hero-title { + font-size: 30px; + } + + .features-grid { + grid-template-columns: 1fr; } .landing-footer { @@ -570,8 +653,4 @@ flex-direction: column; align-items: flex-start; } - - .footer-links { - gap: 16px; - } } diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index 8c7e435..63dc6a1 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -6,7 +6,7 @@ const GITHUB_URL = 'https://github.com/davidmonterocrespo24/velxio'; const PAYPAL_URL = 'https://paypal.me/odoonext'; const GITHUB_SPONSORS_URL = 'https://github.com/sponsors/davidmonterocrespo24'; -/* ── Inline SVG icons ─────────────────────────────────── */ +/* ── Icons ───────────────────────────────────────────── */ const IcoChip = () => ( @@ -65,118 +65,271 @@ const IcoGitHub = () => ( ); -const IcoHeart = () => ( - - +/* ── Circuit Schematic SVG (hero illustration) ───────── */ +const CircuitSchematic = () => ( + ); -/* ── Arduino board SVGs ───────────────────────────────── */ +/* ── Board SVGs ───────────────────────────────────────── */ const BoardUno = () => ( - {/* PCB */} - - {/* MCU */} - - {/* USB */} - - {/* Power jack */} - - {/* Header pins top */} + + + + {[0,1,2,3,4,5,6,7,8,9,11,12,13].map((i) => ( ))} - {/* Header pins bottom */} {[0,1,2,3,4,5].map((i) => ( ))} - {/* LED */} - - {/* Label */} + Arduino Uno ); const BoardNano = () => ( - {/* PCB */} - - {/* MCU */} - - {/* USB mini */} - - {/* Header left */} + + + {[0,1,2,3,4,5,6,7].map((i) => ( ))} - {/* Header right */} {[0,1,2,3,4,5,6,7].map((i) => ( ))} - {/* LED */} - + Arduino Nano ); const BoardPico = () => ( - {/* PCB */} - - {/* RP2040 chip */} - - - {/* USB micro */} - - {/* Header left */} + + + + {[0,1,2,3,4,5,6].map((i) => ( ))} - {/* Header right */} {[0,1,2,3,4,5,6].map((i) => ( ))} - {/* LED */} - + Raspberry Pi Pico ); const BoardMega = () => ( - {/* PCB */} - - {/* MCU - ATmega2560 */} - - {/* USB */} - - {/* Power jack */} - - {/* Top headers */} + + + + {Array.from({length: 18}).map((_, i) => ( ))} - {/* Bottom headers */} {Array.from({length: 18}).map((_, i) => ( ))} - {/* LEDs */} - - + + Arduino Mega 2560 ); -/* ── Features data ────────────────────────────────────── */ +/* ── Features ─────────────────────────────────────────── */ const features = [ - { icon: , title: 'Real AVR8 Emulation', desc: 'Full ATmega328p at 16 MHz — timers, USART, ADC, SPI, I2C, PWM all wired.' }, - { icon: , title: '48+ Components', desc: 'LEDs, LCDs, TFT displays, servos, buzzers, sensors and more from wokwi-elements.' }, - { icon: , title: 'Monaco Editor', desc: 'VS Code-grade C++ editor with syntax highlighting, autocomplete, and minimap.' }, - { icon: , title: 'arduino-cli Backend', desc: 'Compile sketches locally in seconds. No cloud. No latency. No limits.' }, - { icon: , title: 'Serial Monitor', desc: 'Live TX/RX with auto baud-rate detection, send data, and autoscroll.' }, - { icon: , title: 'Library Manager', desc: 'Browse and install the full Arduino library index directly from the UI.' }, + { icon: , title: 'Real AVR8 Emulation', desc: 'Full ATmega328p at 16 MHz — timers, USART, ADC, SPI, I2C and PWM all wired.' }, + { icon: , title: '48+ Components', desc: 'LEDs, LCDs, TFT displays, servos, buzzers, sensors and more from wokwi-elements.' }, + { icon: , title: 'Monaco Editor', desc: 'VS Code-grade C++ editor with syntax highlighting, autocomplete and minimap.' }, + { icon: , title: 'arduino-cli Backend', desc: 'Compile sketches locally in seconds. No cloud. No latency. No limits.' }, + { icon: , title: 'Serial Monitor', desc: 'Live TX/RX with auto baud-rate detection, send data and autoscroll.' }, + { icon: , title: 'Library Manager', desc: 'Browse and install the full Arduino library index directly from the UI.' }, ]; +/* ── Sponsor SVG icon ─────────────────────────────────── */ +const IcoSponsor = () => ( + + + + + +); + /* ── Component ────────────────────────────────────────── */ export const LandingPage: React.FC = () => { const user = useAuthStore((s) => s.user); @@ -207,16 +360,21 @@ export const LandingPage: React.FC = () => { {/* Hero */}
-
-
-
Open Source · Free · Local
+
+
+ OPEN SOURCE + + FREE + + LOCAL +

- Arduino Emulator
+ Arduino Simulator
in your browser

- Write, compile, and simulate Arduino projects — no hardware required.
- Real AVR8 emulation. 48+ electronic components. Runs entirely on your machine. + Write, compile, and simulate Arduino projects with no hardware required. + Real AVR8 emulation running entirely on your machine.

@@ -228,48 +386,55 @@ export const LandingPage: React.FC = () => { View on GitHub
+
+ ATmega328p + / + RP2040 + / + 16 MHz + / + 48+ components +
- {/* Floating chip grid decoration */} -
- {Array.from({ length: 12 }).map((_, i) => ( -
- -
- ))} +
+
{/* Boards */}
-

Supported Boards

-

Pick your hardware. The emulator adapts.

+
+ // boards +

Supported Hardware

+

Pick your target board. The emulator adapts its register map and timing.

+
Arduino Uno - ATmega328p · AVR8 + ATmega328p · AVR8 · 16 MHz
Arduino Nano - ATmega328p · AVR8 + ATmega328p · AVR8 · 16 MHz
Arduino Mega - ATmega2560 · AVR8 + ATmega2560 · AVR8 · 16 MHz
Raspberry Pi Pico - RP2040 · Dual-core ARM + RP2040 · Dual-core ARM · 133 MHz
@@ -277,8 +442,11 @@ export const LandingPage: React.FC = () => { {/* Features */}
-

Everything you need

-

A complete IDE and simulator, running locally.

+
+ // features +

Everything you need

+

A complete IDE and simulator, running locally with no external dependencies.

+
{features.map((f) => (
@@ -291,11 +459,11 @@ export const LandingPage: React.FC = () => {
{/* Support */} -
+
-
-

Support the project

-

+

+

Support the project

+

Velxio is free and open source. If it saves you time, consider supporting its development.

@@ -303,8 +471,8 @@ export const LandingPage: React.FC = () => { GitHub Sponsors - - + + Donate via PayPal diff --git a/scripts/generate-favicons.mjs b/scripts/generate-favicons.mjs new file mode 100644 index 0000000..491e17c --- /dev/null +++ b/scripts/generate-favicons.mjs @@ -0,0 +1,77 @@ +/** + * Favicon generator — converts favicon.svg into all required sizes. + * Run from project root: node scripts/generate-favicons.mjs + * + * Generates: + * frontend/public/favicon-16x16.png + * frontend/public/favicon-32x32.png + * frontend/public/favicon-48x48.png + * frontend/public/apple-touch-icon.png (180x180) + * frontend/public/android-chrome-192.png + * frontend/public/android-chrome-512.png + * frontend/public/favicon.ico (16+32+48 multi-size) + */ + +import { readFileSync, writeFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(__dirname, '..'); +const PUBLIC = join(ROOT, 'frontend', 'public'); + +// ── install deps on the fly if missing ───────────────────────────── +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); + +async function ensureDep(pkg) { + try { return await import(pkg); } catch {} + console.log(`Installing ${pkg}…`); + const { execSync } = await import('child_process'); + execSync(`npm install --no-save ${pkg}`, { stdio: 'inherit', cwd: ROOT }); + return await import(pkg); +} + +const { Resvg } = await ensureDep('@resvg/resvg-js'); +const pngToIcoMod = await ensureDep('png-to-ico'); +const pngToIco = pngToIcoMod.default ?? pngToIcoMod; + +// ── render helper ─────────────────────────────────────────────────── +const svgSrc = readFileSync(join(PUBLIC, 'favicon.svg')); + +function renderPng(size) { + const resvg = new Resvg(svgSrc, { + fitTo: { mode: 'width', value: size }, + font: { loadSystemFonts: false }, + }); + return resvg.render().asPng(); +} + +// ── generate PNGs ─────────────────────────────────────────────────── +const sizes = [ + { name: 'favicon-16x16.png', size: 16 }, + { name: 'favicon-32x32.png', size: 32 }, + { name: 'favicon-48x48.png', size: 48 }, + { name: 'apple-touch-icon.png', size: 180 }, + { name: 'android-chrome-192.png', size: 192 }, + { name: 'android-chrome-512.png', size: 512 }, +]; + +const pngBuffers = {}; +for (const { name, size } of sizes) { + const buf = renderPng(size); + writeFileSync(join(PUBLIC, name), buf); + pngBuffers[size] = buf; + console.log(`✓ ${name} (${size}x${size})`); +} + +// ── generate favicon.ico (16 + 32 + 48) ──────────────────────────── +const icoBuffer = await pngToIco([ + pngBuffers[16], + pngBuffers[32], + pngBuffers[48], +]); +writeFileSync(join(PUBLIC, 'favicon.ico'), icoBuffer); +console.log('✓ favicon.ico (16+32+48)'); + +console.log('\nAll favicon assets generated in frontend/public/');