From 3301c5967e8927f7991b03407bff795b710436f4 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Fri, 6 Mar 2026 16:14:26 -0300 Subject: [PATCH 1/3] feat: enhance SEO and public files for Velxio - Added comprehensive SEO meta tags to `frontend/index.html` including Open Graph and Twitter Card data. - Updated `frontend/public` with new favicon assets and a PWA manifest. - Created a favicon generation script to automate favicon creation from SVG. - Implemented `robots.txt` to allow all crawlers and point to the sitemap. - Added `sitemap.xml` with public routes and priorities for better indexing. --- CLAUDE.md | 8 + frontend/index.html | 201 ++++++++++++++++++++++++- frontend/package.json | 1 + frontend/public/android-chrome-192.png | Bin 0 -> 4798 bytes frontend/public/android-chrome-512.png | Bin 0 -> 14242 bytes frontend/public/apple-touch-icon.png | Bin 0 -> 4451 bytes frontend/public/favicon-16x16.png | Bin 0 -> 532 bytes frontend/public/favicon-32x32.png | Bin 0 -> 751 bytes frontend/public/favicon-48x48.png | Bin 0 -> 1126 bytes frontend/public/favicon.ico | Bin 0 -> 15086 bytes frontend/public/favicon.svg | 17 +++ frontend/public/manifest.webmanifest | 51 +++++++ frontend/public/og-image.svg | 142 +++++++++++++++++ frontend/public/robots.txt | 5 + frontend/public/sitemap.xml | 40 +++++ scripts/generate-favicons.mjs | 77 ++++++++++ 16 files changed, 540 insertions(+), 2 deletions(-) create mode 100644 frontend/public/android-chrome-192.png create mode 100644 frontend/public/android-chrome-512.png create mode 100644 frontend/public/apple-touch-icon.png create mode 100644 frontend/public/favicon-16x16.png create mode 100644 frontend/public/favicon-32x32.png create mode 100644 frontend/public/favicon-48x48.png create mode 100644 frontend/public/favicon.ico create mode 100644 frontend/public/favicon.svg create mode 100644 frontend/public/manifest.webmanifest create mode 100644 frontend/public/og-image.svg create mode 100644 frontend/public/robots.txt create mode 100644 frontend/public/sitemap.xml create mode 100644 scripts/generate-favicons.mjs 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 0000000000000000000000000000000000000000..6298f3d555cb760ab78140c84ebc4084826f1e26 GIT binary patch literal 4798 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 PUJ5vCY-vV+$x>l%SyNFxC8Q{irR+&| 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

*+D8^LDO3@>rbu$ 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 B+86); literal 0 HcmV?d00001 diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..96d47c657e8197b0c274c20c334c95ce7b20b5af GIT binary patch literal 4451 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 literal 0 HcmV?d00001 diff --git a/frontend/public/favicon-16x16.png b/frontend/public/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..59bdac41de2efa6267da43d9ea526a9f4d3604b6 GIT binary patch literal 532 zcmV+v0_**WP)ih?3|1Mz}Bg{X+&;02u* zP{GSWcj66P*f>HrI%E0Q^z^ij7hL!)y6T*&`um(ybp(iMn&rtP>H%4ZXCYPt0gPCd zH6z@^a+=-@iO+@HBcScBK#^^G1OlQ$ghJ-Jl=3I-42gmgetBRg%JIJKBVxiZ@+4<2 zdHJyawIcEE8?&M9p%SI79dAKj=mpMC=y16sU{{SoU+M>}L&m28$ppP5J(uD4a3U?x zC+q2SNeQHK%zX{}Zwrjk2(CZ<#^u-lkZ(*Vgl*x~_8fBd*ATB=hQ2%iTshG8VrhXd zMU0OEk!BbZC2)${5uEShQ6HzY6T!t^#A~9Aaxg8h>I!f@g;F=hBCsompiec@<00p_U4wmsNPMGP#{&JB!2?R*9z#&#aI*v>bP W&(&phE_O-)0000CMVAt4?gfJaLKL5KnxAQGekg@Pgo zDnt<)E;OM?fF=@=C=5peF}9cg+}h{M-d)y@L{5>PVZ|8rr5AO={kpa~zU4l6# zrL^$9V+bK^e9gsTbhij#yLbXpsZ2o#x`V(ORCf&sDN0FK9H;o0fGdE56Uj$ppfs9C zNz}XW1Z5CY`&Ps`79o&zPWxBc#GC&azm1Zp|HW05K}_v_I1?C-BO;y7+(7W_@EI`$ zFtzIlA#SpOj}Ty?Zp0M8)UE>own(LhBvy9sfX?7M4P|iZH<4{S^9hG0>ytU)4=R` zc!ERs=Z5?|g^&(dozV}X7&JOMoB zmFx&CMLDY$M*YD?I#)>4IuE0q%~AiGTa;LuN39_sv1+4K#8*Fgb3zl4gQeh(n7RO7 zViH*xn4Ll`nvIEv?(FY-bqzJYfJKywIP8D`o%(}*0gP#l>%Y_c(b*TUFJSKoP|I)x zmO|eL^p2z0n0SC?xE8VuZIp_(_JPJmv~1XjtcZ=M!+l`uRy+ttIBF|K@S4a#KN`&o zXa@*w`_E{-@QkXdgVgu#KHjMD=h6zL(Mg;s@))+2$Spil0MCi89!NNSB}_aX^UCls zI9ey>tRZ`RBnuDDKtKlTL#ionz_b)i?LovMpNhMCEXTcsFDBn0VhFx%F$FNS`$6!Z zxR@Lku)A_Ki^s(o+)?KQY!?=R%jtA8ndpStn-@4Zs^ke^Fd94@mvMU?Lq>9N?I4(N h!o8kP7XchD{{c<#q8(Obcs~FD002ovPDHLkV1oV#OtSz0 literal 0 HcmV?d00001 diff --git a/frontend/public/favicon-48x48.png b/frontend/public/favicon-48x48.png new file mode 100644 index 0000000000000000000000000000000000000000..ffb1f6f91e1759f85cb00086438c3ffcc4ffb0f6 GIT binary patch literal 1126 zcmV-s1eyDZP)C8!ddrm}sJfr#3zk(MF@jpwgRYZ-f|4v=Is)jXVkq zO-%4rvs5%-C?wHH;v$>^$KCq>_E_B8+u57BS0v$A?9FUuZ@&M0-#0rqbAlu!5(zV# zrFO?5QwTC0$4TIJ%@9J^=-WcyRxC!nwryLuT|j{V7~Y!6(0w8&iRyoZ7%&XF2_X~Y zBToQ~Ph!-cIyD|`RoE-=#zlx2K-Fbg02`mlIQ=MUlQvJ{N))&Ha4n8*`yhrP4no8b z0apNwZ^g(Dmpbw@*Wckvw7&NoiC^DPibl76kR?y-gop`OKvPo!BVT&Vdj_rR?WeAGP(xowwGZTVy9?sPIMu3I#4eeU4wMn%Rne~3xv-Q z!sh^$fv!Qi?d6E~?17)~KmxU*WH)Z7gXW5<%l7fjj3^}|M*BbvEcz5 z9%yk;F3Mut5t`d~m7MzZs8tU!KT8b|Q8)Sqwc;oV;F14*;$GE__h1o?FE1;3LP@~f zvAdMsx(6kqy2%lWkMvR9_$SmV2M^LbJiNGN2Woi9efdlcuWzFijgo*=`?KYk=aGs1 z7(=P@ENPy&z1&?Mp(QfV6qaohqa7f2qGcC$|n>vhtJ}w_c%? z!!IC=VEeKJEbMNldA!)z__ckva7P_)W?O?9t^UJ9+{sav0LDQ$1Ve(~0UnhSAbZv? zfR`G6cxDQ|8wv!3;s4TF3h?(OV^u(y5h}=@mGvcKRX~^#D#)Id^(A9fK$sCK$exw; zC1X`Um=P+-o|W|_BTvAJUrOi-@VDn*P%*47Rmg7G?i$Yo4Ju0jqq7#`<@D@@=j5zu zL`9$2%y(*haY?$IuKHTY1tQXIM_kcaY`3To!}bXq6X%@=pYcp&Um=t)pu*_(r6eGQ zFv&J;LaBy7>N&i6&Y~2Jk^pbUVCp6Q)ix( zoUFQtg_N7z!GsM%NdV(+E$+WobrEcQ1KtbDTZ`D-wXwO|f^t#V6TEwtfxEq&s1zLY z269Vtb9WjZSoz!QUGP8$Y9*s_uIBLDyZ literal 0 HcmV?d00001 diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ca07804eb9f59d640513fd7b65d67c2b483d5974 GIT binary patch literal 15086 zcmeHNF>hQ&5ME;kW6UKd5ej5L76K)KNSufiDr6%N1(CoFhK_^+Q4kuaLXrCcgqR)> zbfF3b)H(V{@DuzGk{^KdnD5(GxA4s9zwy782ZNd7RZOYv@)|!u`cx~z}=TjSl=I2W*&Fw3xx&4h4NET_M z@1MTZx{Z3x`pxYhtOw+jxVBBb-h;9iZc$&7u%+*K$$cyoJK&4w%%+rWDR@3b z{v`HL@;1)x{Sx<5KQLQt36bL>{THSGZV9JvKVk@l_8CbYh#kTG@}1QN#j`E-Yd>-j ziJXVV&3&hFbiLHCZD)CC=UeN$>LvVAzr_{~Rcoz;U+TXne&#ohHXA1g?Ogx*+4A9U zl{{dYZGro-b4r1dS?18+0$vYq4|HuKH^mrFhZ^vz7lgDf>u8K`~O-4Ba zQ_5B5oH6%PTAie{JjOM_5&a?7L}KnsY}`|fq+FB|y{8bu4U@IgvZ;+!(+;iev5Ydx~Q9 zUc1)b;t1FOi~;k~C-3|nJ+(2tOSkcCyVl% zxgX{FYjm}IYVAEP;4X8q=k-VRwR~#rJs#jL^AP?H=Hb-xskQgwf_u%r#s0)Q9Pi-B z<9d82UZa%YBU=IXMdM93x`Y%tL7`m{(%sz4*P? z4(|a=zR~`3FRr)ugp8x_8lo7UonG&VzOu3UJ$^ZtkLh>);_PwxJrD^?_D*f%%_=;C1_exH4fHDbr0V`&bkm-aMrqWyDq zoda!;{f0Y@NYT#&U}(%-Os%u_+E??@KDw^$d6$GWq4kkE$AGn@?RBhNy^Fub(RGcZ z^?mqzzFsUHx3=zKdK@D7p05`xg6Y|Km>vfYN9#RbFP8SLt$UarhX}ss>&1#-dNv-W z$HBwVde7I3rG0Db9;U}3g75iyu_BnBjfd%R@Nl%=^YvnB-`cu|>2ZkQd%j+*2&QM_ zVR{@q9If|!y;$0}t6p>d@i=thv(NL@{1<)x@i>rUmt4g8q4xZv?e+OMQfHp+d4_sV zTKldXu}@=B2Z^&Uzej4DXdU11y3Wr!R-d}&V{s54--aXSW^H4?Clj|T7Hw+fA}~D; zi-@Jy$Wgt&Bc?aTT0Nf?$PM-;?g?r!O7_SLu$h0vEcrzHfj#+Z@po_Jj##C4-6(h3 z;ZE-;#()?#_lI%^8vZtndBD8t{lJ{{mB0jcP2R!%e{g^PaDTse$iE-8-@pcgo7|`K zNUnXH7bE_)_Bl*h_csr|KTf}&hWD|>BcJHrtF^cB+I?%j#ml$6xxZiIEQ4a>ce7&~V;@j|b@w@dP=A_N_ zH1>6H@p%4&j;)mLLY_!zA96LN3FLB0V@UZACQ39P{rMRfqf6y$g0|#wA5!wlJTveA E0-!22P5=M^ literal 0 HcmV?d00001 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/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/'); From 369735010b1d837ec749664f58699427975e7b2a Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Fri, 6 Mar 2026 17:18:33 -0300 Subject: [PATCH 2/3] Add check_actions script and strategic plan for VELXIO; update submodule commits to dirty state --- frontend/src/pages/LandingPage.tsx | 340 +++++++++++++++++++++-------- 1 file changed, 254 insertions(+), 86 deletions(-) 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 From c7409afa2d6a02c9a94d54f650828e8f2bb3069e Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Fri, 6 Mar 2026 17:19:27 -0300 Subject: [PATCH 3/3] feat: update LandingPage component with enhanced visuals and improved content --- frontend/src/pages/LandingPage.css | 523 +++++++++++++++++------------ 1 file changed, 301 insertions(+), 222 deletions(-) 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; - } }