Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Reachy Web: the Reachy Mini web dashboard, back as an app</title> | |
| <meta name="description" content="Reachy Web is the good old Reachy Mini web dashboard, back as an installable app. Control your Reachy Mini Lite or Wireless from any browser: live camera, 3D digital twin, moves, app store, volume, no desktop app needed. By Domotick."> | |
| <meta name="keywords" content="Reachy Web, Reachy Mini, Reachy Mini dashboard, Reachy Mini Lite, Reachy Mini Wireless, web dashboard, web app, browser dashboard, robot dashboard, robot control, robot tools, robot web app, control robot from browser, 3D digital twin, live camera, app store, Reachy Mirror, Reachy Eyes, Domotick, Pollen Robotics, Hugging Face Space, reachy_mini_python_app, tableau de bord Reachy Mini, tableau de bord robot, dashboard web robot, application web robot, outils robot, piloter Reachy Mini depuis un navigateur, controle robot en ligne, camera robot en direct, jumeau numerique 3D, boutique d'applications robot"> | |
| <meta name="author" content="Domotick"> | |
| <meta name="robots" content="index, follow"> | |
| <link rel="canonical" href="https://domotick-reachy-web.static.hf.space/"> | |
| <link rel="icon" type="image/svg+xml" href="reachy_web/static/assets/favicon.svg"> | |
| <link rel="icon" type="image/png" href="reachy_web/static/assets/favicon-64.png"> | |
| <!-- Social previews (Signal, WhatsApp, LinkedIn…) need absolute URLs --> | |
| <meta property="og:type" content="website"> | |
| <meta property="og:site_name" content="Reachy Web"> | |
| <meta property="og:title" content="Reachy Web: the dashboard is back"> | |
| <meta property="og:description" content="Control your Reachy Mini from any browser again. The removed web dashboard, back as an installable app."> | |
| <meta property="og:url" content="https://domotick-reachy-web.static.hf.space/"> | |
| <meta property="og:image" content="https://domotick-reachy-web.static.hf.space/reachy_web/static/assets/og.png?v=3"> | |
| <meta property="og:image:width" content="512"> | |
| <meta property="og:image:height" content="512"> | |
| <meta name="twitter:card" content="summary"> | |
| <meta name="twitter:title" content="Reachy Web: the dashboard is back"> | |
| <meta name="twitter:description" content="Control your Reachy Mini from any browser again. The removed web dashboard, back as an installable app."> | |
| <meta name="twitter:image" content="https://domotick-reachy-web.static.hf.space/reachy_web/static/assets/og.png?v=3"> | |
| <script type="application/ld+json"> | |
| { | |
| "@context": "https://schema.org", | |
| "@type": "SoftwareApplication", | |
| "name": "Reachy Web", | |
| "applicationCategory": "DeveloperApplication", | |
| "operatingSystem": "Any (web browser)", | |
| "description": "The Reachy Mini web dashboard, back as an installable app. Control your Reachy Mini Lite or Wireless from any browser: live camera, 3D digital twin, moves, app store, volume, no desktop app needed.", | |
| "url": "https://domotick-reachy-web.static.hf.space/", | |
| "image": "https://domotick-reachy-web.static.hf.space/reachy_web/static/assets/og.png?v=3", | |
| "author": { "@type": "Organization", "name": "Domotick", "url": "https://huggingface.co/Domotick" }, | |
| "keywords": "Reachy Mini, Reachy Mini Lite, Reachy Mini Wireless, dashboard, web dashboard, web app, robot tools, robot control, 3D digital twin, app store, Reachy Mirror, Reachy Eyes, Domotick, Pollen Robotics, tableau de bord robot, application web robot, outils robot, piloter Reachy Mini depuis un navigateur, jumeau numerique 3D", | |
| "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" } | |
| } | |
| </script> | |
| <script>try{var d=document.documentElement,t=localStorage.getItem("theme")||(matchMedia("(prefers-color-scheme: light)").matches?"light":"dark");d.setAttribute("data-theme",t);var p=localStorage.getItem("palette")||"default";p!=="default"&&d.setAttribute("data-palette",p)}catch(e){}</script> | |
| <link rel="stylesheet" href="style.css" /> | |
| <link rel="stylesheet" href="reachy_web/static/css/icons.css" /> | |
| <link rel="stylesheet" href="reachy_web/static/css/logo.css" /> | |
| </head> | |
| <body> | |
| <nav class="sticky top-0 z-10 border-b border-line backdrop-blur-xl bg-[color-mix(in_srgb,var(--bg)_78%,transparent)]"> | |
| <div class="mx-auto flex max-w-[1100px] flex-col items-stretch gap-3.5 px-5 py-2.5 md:flex-row md:items-center md:justify-between"> | |
| <div class="flex items-center justify-center gap-2.5 whitespace-nowrap font-display text-lg font-semibold md:justify-start"> | |
| <!-- INLINE like in the app (an <img>-loaded SVG cannot run scripts, so | |
| its timing would be a fixed loop): logo_anim.js plays a random move | |
| now and then, the wink first. Same markup as base.html. --> | |
| <svg class="topbar-logo h-9 w-9" viewBox="24 13 72 72" role="img" aria-label="Reachy Web"> | |
| <g class="lg-sway lg-sway-l"> | |
| <g class="lg-ant lg-ant-l"> | |
| <path d="M45 46 L37 22" fill="none" stroke="#1b2330" stroke-width="3" stroke-linecap="round"/> | |
| <circle class="lg-ball lg-ball-l" cx="37" cy="22" r="4" fill="#1b2330"/> | |
| </g> | |
| </g> | |
| <g class="lg-sway lg-sway-r"> | |
| <g class="lg-ant lg-ant-r"> | |
| <path d="M75 46 L81.4 26.4" fill="none" stroke="#1b2330" stroke-width="3" stroke-linecap="round"/> | |
| <circle class="lg-ball lg-ball-r" cx="81.4" cy="26.4" r="3.4" fill="#1b2330"/> | |
| </g> | |
| </g> | |
| <rect x="34" y="44" width="52" height="36" rx="13" fill="#fff" stroke="#1b2330" stroke-width="3"/> | |
| <circle class="lg-eye lg-eye-l" cx="48.98" cy="62.21" r="8.14" fill="#1b2330"/> | |
| <circle class="lg-eye lg-eye-r" cx="71.02" cy="62.21" r="7" fill="#1b2330"/> | |
| <path class="lg-wink" d="M63.9 61.5 Q71 68.5 78.1 61.5" fill="none" stroke="#1b2330" stroke-width="3.6" stroke-linecap="round"/> | |
| <g class="lg-glasses" fill="none" stroke="#1b2330" stroke-width="3" stroke-linecap="round"> | |
| <circle cx="48.98" cy="62.21" r="11.5"/> | |
| <circle cx="71.02" cy="62.21" r="10.5"/> | |
| <path d="M37.48 62.21 L34 59.5"/> | |
| <path d="M81.52 62.21 L86 59.5"/> | |
| </g> | |
| </svg> | |
| <span>Reachy Web</span> | |
| <span class="version-pill" id="version-pill">v0.9.0</span> | |
| </div> | |
| <div class="flex flex-wrap items-center justify-center gap-3.5 md:justify-end"> | |
| <div class="relative flex items-center text-mut"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" class="pointer-events-none absolute left-3 h-[15px] w-[15px]"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3c2.5 2.4 4 5.6 4 9s-1.5 6.6-4 9c-2.5-2.4-4-5.6-4-9s1.5-6.6 4-9z"/></svg> | |
| <select id="langSel" aria-label="Language" class="cursor-pointer appearance-none rounded-full border border-line bg-card py-1.5 pl-9 pr-8 text-sm font-semibold text-txt transition hover:border-acc focus:border-acc focus:outline-none"> | |
| <option value="en">English</option> | |
| <option value="fr">Français</option> | |
| <option value="es">Español</option> | |
| <option value="de">Deutsch</option> | |
| <option value="it">Italiano</option> | |
| <option value="pt">Português</option> | |
| <option value="ja">日本語</option> | |
| <option value="ar">العربية</option> | |
| </select> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="pointer-events-none absolute right-3 h-3.5 w-3.5"><path d="M6 9l6 6 6-6"/></svg> | |
| </div> | |
| <div class="flex flex-wrap items-center gap-1.5" id="palettes" title="Palette"></div> | |
| <div class="flex items-center gap-2 text-mut"> | |
| <span class="ic i-weather-night"></span> | |
| <button class="ios" id="themeSw" role="switch" aria-label="Light theme"></button> | |
| <span class="ic i-weather-sunny"></span> | |
| </div> | |
| </div> | |
| </div> | |
| </nav> | |
| <header class="pt-16 pb-8"> | |
| <div class="mx-auto grid max-w-[1100px] items-center gap-9 px-5 md:grid-cols-[1.15fr_1fr] md:gap-11"> | |
| <div> | |
| <p class="mb-3.5 text-sm font-bold uppercase tracking-[1.5px] text-acc">Reachy Mini App</p> | |
| <h1 class="mb-5 text-[clamp(2.4rem,5vw,3.4rem)]">The web dashboard is back.</h1> | |
| <p class="mb-7 max-w-[520px] text-lg text-mut"> | |
| Pollen removed the browser dashboard from the daemon. This app brings it back, and then some: a live camera, a 3D digital twin, a colour-coded log panel, twelve themes and eight languages. Installed like any other Reachy Mini app, no desktop app needed. | |
| </p> | |
| <div class="mb-7 flex flex-col gap-3 sm:flex-row sm:flex-wrap"> | |
| <a class="btn primary text-center" href="#run">Get started</a> | |
| <a class="btn ghost text-center" href="#story">The story</a> | |
| <a class="btn ghost text-center" href="#features">Features</a> | |
| </div> | |
| <div class="flex flex-wrap gap-2"> | |
| <span class="chip"><span class="ic i-power"></span> Daemon Control</span> | |
| <span class="chip"><span class="ic i-store"></span> Apps & Store</span> | |
| <span class="chip"><span class="ic i-camera"></span> Live Camera</span> | |
| <span class="chip"><span class="ic i-cube-outline"></span> 3D Twin</span> | |
| <span class="chip"><span class="ic i-music-note"></span> Moves</span> | |
| <span class="chip"><span class="ic i-text-box-outline"></span> Live Logs</span> | |
| <span class="chip"><span class="ic i-palette"></span> Themes</span> | |
| <span class="chip"><span class="ic i-translate"></span> 8 Languages</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="card"> | |
| <div class="mb-3.5 flex items-center justify-between font-display font-semibold"> | |
| <span><span class="status-dot"></span>reachy-mini.local</span> | |
| <span class="version-pill">online</span> | |
| </div> | |
| <div class="flex items-center justify-between gap-3 border-t border-line px-1 py-3"> | |
| <span class="flex items-center gap-2.5 font-semibold"><span class="ic i-power text-xl text-acc"></span><span>Robot backend<span class="block text-xs font-normal text-mut">9 motors, awake</span></span></span> | |
| <button class="ios on demo-sw" role="switch" aria-label="Robot backend"></button> | |
| </div> | |
| <div class="flex items-center justify-between gap-3 border-t border-line px-1 py-3"> | |
| <span class="flex items-center gap-2.5 font-semibold"><span class="ic i-music-note text-xl text-acc"></span><span>Move player<span class="block text-xs font-normal text-mut">dance_1 playing</span></span></span> | |
| <button class="ios on demo-sw" role="switch" aria-label="Move player"></button> | |
| </div> | |
| <div class="flex items-center justify-between gap-3 border-t border-line px-1 py-3"> | |
| <span class="flex items-center gap-2.5 font-semibold"><span class="ic i-camera text-xl text-acc"></span><span>Live camera<span class="block text-xs font-normal text-mut">MJPEG · WebRTC</span></span></span> | |
| <button class="ios on demo-sw" role="switch" aria-label="Live camera"></button> | |
| </div> | |
| <p class="mt-3.5 text-center text-sm text-mut">This is the vibe, the real one lives at <code class="font-semibold text-acc">http://reachy-mini.local:8042</code></p> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <section id="run" class="py-14"> | |
| <div class="mx-auto max-w-[1100px] px-5"> | |
| <div class="mb-11 text-center"> | |
| <p class="mb-3.5 text-sm font-bold uppercase tracking-[1.5px] text-acc">Getting it running</p> | |
| <h2 class="mb-3 text-3xl">Three ways to run it</h2> | |
| <p class="mx-auto max-w-[680px] text-mut">Through Pollen's app, directly on the robot so it starts at boot, or on your own machine pointed at the robot over the network.</p> | |
| </div> | |
| <div class="grid gap-[18px] md:grid-cols-2"> | |
| <div class="card"> | |
| <h3 class="mb-2 flex items-center gap-2.5 text-xl"><span class="ic i-store text-acc"></span> On the robot, through Pollen's app</h3> | |
| <p class="mb-4 text-[0.95rem] text-mut">There is a delicious irony here: the store that installs the web dashboard lives in the app that replaced it. Grab <strong class="text-txt">Reachy Mini Control</strong> (desktop) or the <strong class="text-txt">Reachy Mini</strong> app on iPhone: the robot then downloads this Space, installs it into its apps venv and runs it, surviving reboots and daemon updates like any other app.</p> | |
| <div class="mb-5 grid gap-3 sm:grid-cols-[1fr_auto] sm:items-center"> | |
| <div class="flex flex-col gap-2"> | |
| <a class="btn ghost flex items-center justify-center gap-2 text-center" | |
| href="https://github.com/pollen-robotics/reachy-mini-desktop-app/releases" target="_blank" rel="noopener"> | |
| <span class="ic i-monitor"></span> Reachy Mini Control (desktop) | |
| </a> | |
| <a class="btn ghost flex items-center justify-center gap-2 text-center" | |
| href="https://apps.apple.com/app/reachy-mini/id6766823749" target="_blank" rel="noopener"> | |
| <span class="ic i-apple"></span> Reachy Mini on the App Store | |
| </a> | |
| <p class="text-xs text-mut">Desktop: macOS is production-ready, Windows and Linux are still work in progress. No Android app yet.</p> | |
| </div> | |
| <!-- a QR scans black on white: the backing stays white in BOTH themes --> | |
| <a class="justify-self-center rounded-xl border border-line bg-white p-2.5" | |
| href="https://apps.apple.com/app/reachy-mini/id6766823749" target="_blank" rel="noopener" | |
| title="Scan to get the iPhone app"> | |
| <img src="reachy_web/static/assets/qr-ios.svg" alt="QR code to the Reachy Mini app on the App Store" class="block h-28 w-28"> | |
| </a> | |
| </div> | |
| <ol class="mb-4 list-none space-y-2.5 text-[0.95rem] text-mut"> | |
| <li><strong class="text-acc">1.</strong> Open the app → <em>Applications</em>.</li> | |
| <li><strong class="text-acc">2.</strong> Turn on <em>Show community apps</em>.</li> | |
| <li><strong class="text-acc">3.</strong> Install <strong class="text-txt">Reachy Web</strong>, then start it.</li> | |
| <li><strong class="text-acc">4.</strong> Open <strong class="text-txt">http://reachy-mini.local:8042</strong> from any device on the network.</li> | |
| </ol> | |
| <p class="mb-3 text-sm text-mut">Neither app at hand? Talk to the daemon directly:</p> | |
| <div class="code-block"><code>curl -X POST http://reachy-mini.local:8000/api/apps/install \ | |
| -H 'Content-Type: application/json' \ | |
| -d '{"name": "reachy_web", "source_kind": "hf_space", | |
| "url": "https://huggingface.co/spaces/Domotick/reachy_web"}' | |
| curl -X POST http://reachy-mini.local:8000/api/apps/start-app/reachy_web</code></div> | |
| </div> | |
| <div class="card"> | |
| <h3 class="mb-2 flex items-center gap-2.5 text-xl"><span class="ic i-power text-acc"></span> Directly on the robot, at boot</h3> | |
| <p class="mb-4 text-[0.95rem] text-mut">No store, no problem: SSH into the robot and run setup.sh. It registers a systemd service, so the dashboard is up at every boot at http://your-robot.local:8042, with no SSH needed afterwards.</p> | |
| <div class="code-block mb-4"><code># SSH into the robot (or the Pi beside a Lite) | |
| ssh pollen@reachy-mini.local | |
| git clone https://huggingface.co/spaces/Domotick/reachy_web | |
| cd reachy_web | |
| ./setup.sh</code></div> | |
| <p class="mb-4 text-[0.95rem] text-mut">This is the way to go for the Wireless version, which installs it on its onboard CM4, or a Lite paired with a Raspberry Pi beside it (install on that Pi, the one already running the daemon).</p> | |
| <p class="mb-4 text-[0.95rem] text-mut">setup.sh builds the virtualenv once, writes reachy-web.service and enables it. It boots without waking the robot; the dashboard wakes it on demand.</p> | |
| <p class="mb-3 text-sm text-mut">Check it, follow its logs, or remove it:</p> | |
| <div class="code-block"><code>systemctl status reachy-web.service | |
| journalctl -u reachy-web.service -f | |
| ./setup.sh --uninstall</code></div> | |
| </div> | |
| <div class="card md:col-span-2"> | |
| <h3 class="mb-2 flex items-center gap-2.5 text-xl"><span class="ic i-cog text-acc"></span> On your machine: <code class="font-mono text-acc">run.sh</code></h3> | |
| <p class="mb-4 text-[0.95rem] text-mut">The dashboard only needs to reach the daemon over HTTP, so it can just as well run on your laptop. Nothing is installed on the robot, ideal for trying it out or hacking on the code.</p> | |
| <div class="code-block mb-4"><code># grab the app (it is just a git repo) | |
| git clone https://huggingface.co/spaces/Domotick/reachy_web | |
| cd reachy_web | |
| ./run.sh</code></div> | |
| <p class="mb-4 text-[0.95rem] text-mut">The script builds its own virtualenv, installs the app, then <strong class="text-txt">scans the network</strong> (mDNS) for a Reachy Mini and points the dashboard at it. Open <strong class="text-txt">http://127.0.0.1:8042</strong> and you are in.</p> | |
| <p class="mb-3 text-sm text-mut">Know your robot's address, or running the daemon locally? Skip the scan:</p> | |
| <div class="code-block"><code>DAEMON_HOST=192.168.1.42:8000 ./run.sh | |
| # port 8042 taken (the desktop app keeps it)? | |
| # run.sh falls back to 8043 and tells you.</code></div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section id="story" class="py-14"> | |
| <div class="mx-auto max-w-[1100px] px-5"> | |
| <div class="mb-11 text-center"> | |
| <p class="mb-3.5 text-sm font-bold uppercase tracking-[1.5px] text-acc">The story</p> | |
| <h2 class="mb-3 text-3xl">Deprecated, then removed, so we brought it back</h2> | |
| <p class="mx-auto max-w-[700px] text-mut">Pollen once shipped a web dashboard inside the daemon: open a browser on the same network as your Reachy Mini and you were in. Then they flagged it deprecated (“please use the Reachy Mini Control app instead”) and finally deleted it in <a class="font-semibold text-acc underline-offset-2 hover:underline" href="https://github.com/pollen-robotics/reachy_mini/pull/933" target="_blank" rel="noopener">PR #933</a>, leaving only a desktop app. But reaching a robot that is already on your network should not require installing a desktop app on every machine, so we rebuilt the dashboard as an installable app, and gave it everything it never had.</p> | |
| </div> | |
| <div class="grid items-start gap-[18px] md:grid-cols-2"> | |
| <figure class="card"> | |
| <div class="mb-3 flex flex-wrap items-center gap-2.5"> | |
| <span class="version-pill">Before</span> | |
| <span class="font-display font-semibold">Pollen’s dashboard: deprecated</span> | |
| </div> | |
| <img src="reachy_web/static/assets/previous.png" alt="The old Pollen web dashboard, under a banner announcing its deprecation" class="w-full rounded-xl border border-line" loading="lazy"> | |
| </figure> | |
| <figure class="card"> | |
| <div class="mb-3 flex flex-wrap items-center gap-2.5"> | |
| <span class="version-pill">After</span> | |
| <span class="font-display font-semibold">Reachy Web: the dashboard reborn</span> | |
| </div> | |
| <img src="reachy_web/static/assets/next.png" alt="Reachy Web, the new dashboard: live camera, 3D twin, app store, themes and languages" class="w-full rounded-xl border border-line" loading="lazy"> | |
| </figure> | |
| </div> | |
| </div> | |
| </section> | |
| <section id="eyes" class="py-14"> | |
| <div class="mx-auto max-w-[1100px] px-5"> | |
| <div class="mb-11 text-center"> | |
| <p class="mb-3.5 text-sm font-bold uppercase tracking-[1.5px] text-acc">Community hardware</p> | |
| <!-- the section mascot: a Reachy with a USB eyes module, blinking then | |
| flashing its LEDs through the colours. Pure inline CSS so it runs | |
| as-is, honours prefers-reduced-motion. --> | |
| <style> | |
| .eyes-bot .ex-sway-l{transform-box:fill-box;transform-origin:100% 100%;animation:exSwayL 7.3s ease-in-out infinite} | |
| .eyes-bot .ex-sway-r{transform-box:fill-box;transform-origin:0% 100%;animation:exSwayR 11.9s ease-in-out infinite} | |
| @keyframes exSwayL{0%,100%{transform:rotate(-5deg)}50%{transform:rotate(5deg)}} | |
| @keyframes exSwayR{0%,100%{transform:rotate(4deg)}50%{transform:rotate(-4deg)}} | |
| .eyes-bot .ex-eL{transform-box:view-box;transform-origin:48.98px 62.21px} | |
| .eyes-bot .ex-eR{transform-box:view-box;transform-origin:71.02px 62.21px} | |
| .eyes-bot .ex-rb{animation:exRb 5s linear infinite} | |
| @keyframes exRb{0%{fill:#ff2d55}16%{fill:#ff9500}33%{fill:#ffd60a}50%{fill:#34c759}66%{fill:#00c7ff}83%{fill:#af52de}100%{fill:#ff2d55}} | |
| .eyes-bot .ex-lid{animation:exBlink 2.8s ease-in-out infinite} | |
| @keyframes exBlink{0%,10%{transform:scaleY(1)}16%{transform:scaleY(.1)}22%{transform:scaleY(1)}28%{transform:scaleY(.1)}34%,100%{transform:scaleY(1)}} | |
| .eyes-bot .ex-zap{animation:exZap 2.8s ease-in-out infinite} | |
| @keyframes exZap{0%,40%,100%{opacity:0;transform:scale(.2)}50%{opacity:1;transform:scale(1.35)}70%{opacity:.7;transform:scale(1)}82%{opacity:0}} | |
| @media (prefers-reduced-motion:reduce){.eyes-bot *{animation:none}} | |
| </style> | |
| <svg class="eyes-bot mx-auto mb-4 block h-24 w-24 text-txt" viewBox="24 14 72 90" style="overflow:visible" role="img" aria-label="A Reachy with LED eyes"> | |
| <defs> | |
| <filter id="ex-g" x="-140%" y="-140%" width="380%" height="380%"><feGaussianBlur stdDeviation="2.4"/></filter> | |
| <radialGradient id="ex-hot"><stop offset="0" stop-color="#fff"/><stop offset=".45" stop-color="#fff" stop-opacity=".5"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></radialGradient> | |
| </defs> | |
| <g class="ex-sway-l"><path d="M45 46 L37 22" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"/><circle cx="37" cy="22" r="4" fill="currentColor"/></g> | |
| <g class="ex-sway-r"><path d="M75 46 L81.4 26.4" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"/><circle cx="81.4" cy="26.4" r="3.4" fill="currentColor"/></g> | |
| <rect x="34" y="44" width="52" height="36" rx="13" fill="#fff" stroke="currentColor" stroke-width="3"/> | |
| <g class="ex-lid ex-eL"><circle class="ex-rb" cx="48.98" cy="62.21" r="8.14"/></g> | |
| <g class="ex-lid ex-eR"><circle class="ex-rb" cx="71.02" cy="62.21" r="7"/></g> | |
| <g class="ex-zap ex-eL"><circle class="ex-rb" cx="48.98" cy="62.21" r="14" filter="url(#ex-g)" opacity=".9"/></g> | |
| <g class="ex-zap ex-eR"><circle class="ex-rb" cx="71.02" cy="62.21" r="13" filter="url(#ex-g)" opacity=".9"/></g> | |
| <g class="ex-zap ex-eL"><circle cx="48.98" cy="62.21" r="6" fill="url(#ex-hot)"/></g> | |
| <g class="ex-zap ex-eR"><circle cx="71.02" cy="62.21" r="6" fill="url(#ex-hot)"/></g> | |
| <rect x="52" y="86" width="16" height="9" rx="1.5" fill="#0f2e1e" stroke="#1b2330" stroke-width="1"/><rect x="54" y="88" width="7" height="4" rx=".6" fill="#c0c4cc"/><line x1="60" y1="80" x2="60" y2="86" stroke="#1b2330" stroke-width="1.4"/><rect x="57.5" y="95" width="5" height="4" rx=".8" fill="#8b8f98"/><circle cx="65" cy="88.4" r=".9" fill="#3ddc84"/> | |
| </svg> | |
| <h2 class="mb-3 text-3xl">Eyes for your Reachy</h2> | |
| <p class="mx-auto max-w-[620px] text-mut">The community fits the Reachy Mini with glowing, expressive eyes. Two projects lead the way; plug the USB kit in and the dashboard grows an Eyes panel.</p> | |
| </div> | |
| <div class="grid grid-cols-1 gap-[18px] md:grid-cols-2"> | |
| <!-- Tom Mulder, ESP32 eyes --> | |
| <div class="card flex flex-col"> | |
| <div class="ex-video group relative mb-4 block aspect-video w-full cursor-pointer overflow-hidden rounded-xl border border-line bg-black" data-yt="OOBtFCLjouE" role="button" tabindex="0" aria-label="Play the ESP32 eyes demo video"> | |
| <img src="https://img.youtube.com/vi/OOBtFCLjouE/hqdefault.jpg" alt="ESP32 eyes demo" loading="lazy" class="h-full w-full object-cover opacity-90 transition group-hover:opacity-100"> | |
| <span class="absolute left-1/2 top-1/2 flex h-14 w-14 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-black/60 text-white transition group-hover:scale-110 group-hover:bg-[#e5232a]"><svg viewBox="0 0 24 24" fill="currentColor" class="ml-0.5 h-6 w-6"><path d="M8 5v14l11-7z"/></svg></span> | |
| </div> | |
| <h3 class="mb-1 flex items-center gap-2.5 text-lg"><span class="ic i-flash text-acc"></span> ESP32 eyes</h3> | |
| <p class="mb-3 text-sm text-mut">by <a class="font-semibold text-txt underline-offset-2 hover:underline" href="https://algoryn.nl" target="_blank" rel="noopener">Tom Mulder</a></p> | |
| <p class="mb-4 text-[0.92rem] text-mut">WiFi LED eyes on an ESP32: a REST API, pre-built emotions (joy, anger, amazement), mDNS discovery, and Reachy can drive its own gaze from the conversation app.</p> | |
| <div class="mb-5 flex flex-wrap gap-1.5"> | |
| <span class="rounded-full border border-line bg-bg px-2.5 py-1 text-xs font-semibold text-mut">ESP32 / S2 / S3 / C3</span> | |
| <span class="rounded-full border border-line bg-bg px-2.5 py-1 text-xs font-semibold text-mut">2x WS2812</span> | |
| <span class="rounded-full border border-line bg-bg px-2.5 py-1 text-xs font-semibold text-mut">REST API</span> | |
| <span class="rounded-full border border-line bg-bg px-2.5 py-1 text-xs font-semibold text-mut">mDNS · rmeyes.local</span> | |
| </div> | |
| <div class="mt-auto flex flex-wrap items-center gap-2.5"> | |
| <a class="inline-flex items-center gap-1.5 rounded-full bg-acc px-4 py-2 text-sm font-bold text-white transition hover:brightness-110" href="https://github.com/algoryn-nl/reachy-mini-esp32-eyes" target="_blank" rel="noopener"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="m8 6-6 6 6 6M16 6l6 6-6 6"/></svg>Build it yourself</a> | |
| <span class="flex-1"></span> | |
| <div class="flex items-center gap-2.5 text-mut"> | |
| <a class="inline-flex h-9 w-9 items-center justify-center rounded-full border border-line transition hover:border-acc hover:text-acc" href="https://github.com/algoryn-nl/reachy-mini-esp32-eyes" target="_blank" rel="noopener" aria-label="GitHub" title="GitHub"><svg viewBox="0 0 24 24" fill="currentColor" class="h-[18px] w-[18px]"><path d="M12 .5C5.4.5 0 5.9 0 12.6c0 5.3 3.4 9.8 8.2 11.4.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.5-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.4-5.5-6 0-1.3.5-2.4 1.2-3.2-.1-.3-.5-1.6.1-3.3 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0C17 5 18 5.3 18 5.3c.6 1.7.2 3 .1 3.3.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.3v3.4c0 .3.2.7.8.6A12 12 0 0 0 24 12.6C24 5.9 18.6.5 12 .5z"/></svg></a> | |
| <a class="inline-flex h-9 w-9 items-center justify-center rounded-full border border-line transition hover:border-acc hover:text-acc" href="https://www.linkedin.com/posts/temulder_i-gave-reachy-mini-access-to-control-its-activity-7417959994995679233-bJ-D" target="_blank" rel="noopener" aria-label="LinkedIn demo" title="LinkedIn demo"><svg viewBox="0 0 24 24" fill="currentColor" class="h-[18px] w-[18px]"><path d="M20.4 20.4h-3.6v-5.6c0-1.3 0-3-1.9-3s-2.1 1.4-2.1 2.9v5.7H9.2V9h3.4v1.6h.1c.5-.9 1.7-1.9 3.4-1.9 3.6 0 4.3 2.4 4.3 5.5v6.2zM5.3 7.4a2.1 2.1 0 1 1 0-4.2 2.1 2.1 0 0 1 0 4.2zM7.1 20.4H3.5V9h3.6v11.4zM22.2 0H1.8C.8 0 0 .8 0 1.7v20.6c0 .9.8 1.7 1.8 1.7h20.4c1 0 1.8-.8 1.8-1.7V1.7C24 .8 23.2 0 22.2 0z"/></svg></a> | |
| <a class="inline-flex h-9 items-center justify-center rounded-full border border-line px-3 text-xs font-bold transition hover:border-acc hover:text-acc" href="https://huggingface.co/algoryn" target="_blank" rel="noopener" aria-label="Hugging Face" title="Hugging Face">🤗</a> | |
| <a class="inline-flex h-9 w-9 items-center justify-center rounded-full border border-line transition hover:border-acc hover:text-acc" href="https://algoryn.nl" target="_blank" rel="noopener" aria-label="Website" title="algoryn.nl"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" class="h-[18px] w-[18px]"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3c2.5 2.4 4 5.6 4 9s-1.5 6.6-4 9c-2.5-2.4-4-5.6-4-9s1.5-6.6 4-9z"/></svg></a> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Daniel Ritchie, Reachy Eyes --> | |
| <div class="card flex flex-col"> | |
| <div class="ex-video group relative mb-4 block aspect-video w-full cursor-pointer overflow-hidden rounded-xl border border-line bg-black" data-yt="pDPKP4a4vfc" role="button" tabindex="0" aria-label="Play the Reachy Mini Eyes demo video"> | |
| <img src="https://img.youtube.com/vi/pDPKP4a4vfc/hqdefault.jpg" alt="Reachy Mini Eyes demo" loading="lazy" class="h-full w-full object-cover opacity-90 transition group-hover:opacity-100"> | |
| <span class="absolute left-1/2 top-1/2 flex h-14 w-14 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-black/60 text-white transition group-hover:scale-110 group-hover:bg-[#e5232a]"><svg viewBox="0 0 24 24" fill="currentColor" class="ml-0.5 h-6 w-6"><path d="M8 5v14l11-7z"/></svg></span> | |
| </div> | |
| <h3 class="mb-1 flex items-center gap-2.5 text-lg"><span class="ic i-monitor text-acc"></span> Reachy Eyes</h3> | |
| <p class="mb-3 text-sm text-mut">by <a class="font-semibold text-txt underline-offset-2 hover:underline" href="https://brainwavecollective.ai" target="_blank" rel="noopener">Daniel Ritchie</a>, Brainwave Collective</p> | |
| <p class="mb-4 text-[0.92rem] text-mut">One of the first eye kits for the Reachy Mini: a USB module with an LED behind each lens, ready-made or DIY. The dashboard auto-detects it and drives colors, moods and effects.</p> | |
| <div class="mb-5 flex flex-wrap gap-1.5"> | |
| <span class="rounded-full border border-line bg-bg px-2.5 py-1 text-xs font-semibold text-mut">USB plug and play</span> | |
| <span class="rounded-full border border-line bg-bg px-2.5 py-1 text-xs font-semibold text-mut">WS2812</span> | |
| <span class="rounded-full border border-line bg-bg px-2.5 py-1 text-xs font-semibold text-mut">Auto-detected</span> | |
| <span class="rounded-full border border-line bg-bg px-2.5 py-1 text-xs font-semibold text-mut">DIY or ready-made</span> | |
| </div> | |
| <div class="mt-auto flex flex-wrap items-center gap-2.5"> | |
| <a class="inline-flex items-center gap-1.5 rounded-full bg-acc px-4 py-2 text-sm font-bold text-white transition hover:brightness-110" href="https://reachyeyes.brainwavecollective.ai/#buy" target="_blank" rel="noopener"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="h-4 w-4"><path d="M6 6h15l-1.5 9h-12z"/><circle cx="9" cy="20" r="1.4"/><circle cx="18" cy="20" r="1.4"/><path d="M6 6 5 3H2"/></svg>Get the kit</a> | |
| <span class="flex-1"></span> | |
| <div class="flex items-center gap-2.5 text-mut"> | |
| <a class="inline-flex h-9 w-9 items-center justify-center rounded-full border border-line transition hover:border-acc hover:text-acc" href="https://github.com/brainwavecollective/reachy-eyes" target="_blank" rel="noopener" aria-label="GitHub" title="GitHub"><svg viewBox="0 0 24 24" fill="currentColor" class="h-[18px] w-[18px]"><path d="M12 .5C5.4.5 0 5.9 0 12.6c0 5.3 3.4 9.8 8.2 11.4.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.5-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.4-5.5-6 0-1.3.5-2.4 1.2-3.2-.1-.3-.5-1.6.1-3.3 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0C17 5 18 5.3 18 5.3c.6 1.7.2 3 .1 3.3.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.3v3.4c0 .3.2.7.8.6A12 12 0 0 0 24 12.6C24 5.9 18.6.5 12 .5z"/></svg></a> | |
| <a class="inline-flex h-9 w-9 items-center justify-center rounded-full border border-line transition hover:border-acc hover:text-acc" href="https://www.linkedin.com/posts/danielritchie123_one-of-the-first-sets-of-reachy-mini-eyes-activity-7424229814674538497-nqEs" target="_blank" rel="noopener" aria-label="LinkedIn" title="LinkedIn"><svg viewBox="0 0 24 24" fill="currentColor" class="h-[18px] w-[18px]"><path d="M20.4 20.4h-3.6v-5.6c0-1.3 0-3-1.9-3s-2.1 1.4-2.1 2.9v5.7H9.2V9h3.4v1.6h.1c.5-.9 1.7-1.9 3.4-1.9 3.6 0 4.3 2.4 4.3 5.5v6.2zM5.3 7.4a2.1 2.1 0 1 1 0-4.2 2.1 2.1 0 0 1 0 4.2zM7.1 20.4H3.5V9h3.6v11.4zM22.2 0H1.8C.8 0 0 .8 0 1.7v20.6c0 .9.8 1.7 1.8 1.7h20.4c1 0 1.8-.8 1.8-1.7V1.7C24 .8 23.2 0 22.2 0z"/></svg></a> | |
| <a class="inline-flex h-9 items-center justify-center rounded-full border border-line px-3 text-xs font-bold transition hover:border-acc hover:text-acc" href="https://huggingface.co/danielritchie" target="_blank" rel="noopener" aria-label="Hugging Face" title="Hugging Face">🤗</a> | |
| <a class="inline-flex h-9 w-9 items-center justify-center rounded-full border border-line transition hover:border-acc hover:text-acc" href="https://brainwavecollective.ai" target="_blank" rel="noopener" aria-label="Website" title="brainwavecollective.ai"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" class="h-[18px] w-[18px]"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3c2.5 2.4 4 5.6 4 9s-1.5 6.6-4 9c-2.5-2.4-4-5.6-4-9s1.5-6.6 4-9z"/></svg></a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-9 text-center"> | |
| <a class="inline-flex items-center gap-2 rounded-full bg-acc px-5 py-2.5 text-sm font-bold text-white transition hover:brightness-110" href="https://discord.gg/WpJ7PE9GV" target="_blank" rel="noopener"><svg viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5"><path d="M20 4.4A19 19 0 0 0 15.3 3l-.2.4a17 17 0 0 1 4.2 1.3 15.7 15.7 0 0 0-13.4 0A17 17 0 0 1 10 3.4L9.8 3A19 19 0 0 0 5 4.4 20 20 0 0 0 1.4 18a19 19 0 0 0 5.8 3l.5-1a13 13 0 0 1-2-1l.5-.4a13.5 13.5 0 0 0 11.6 0l.5.4c-.6.4-1.3.7-2 1l.5 1a19 19 0 0 0 5.8-3A20 20 0 0 0 20 4.4zM8.5 14.7c-1.1 0-2-1-2-2.3s.9-2.3 2-2.3 2 1 2 2.3-.9 2.3-2 2.3zm7 0c-1.1 0-2-1-2-2.3s.9-2.3 2-2.3 2 1 2 2.3-.9 2.3-2 2.3z"/></svg>Discuss the eyes on Discord</a> | |
| </div> | |
| </div> | |
| </section> | |
| <section id="features" class="py-14"> | |
| <div class="mx-auto max-w-[1100px] px-5"> | |
| <div class="mb-11 text-center"> | |
| <p class="mb-3.5 text-sm font-bold uppercase tracking-[1.5px] text-acc">Capabilities</p> | |
| <h2 class="mb-3 text-3xl">Everything it did, and everything it never could</h2> | |
| <p class="mx-auto max-w-[620px] text-mut">Every page of the removed dashboard, unchanged. Plus the features it always deserved.</p> | |
| </div> | |
| <div class="grid gap-[18px] grid-cols-[repeat(auto-fit,minmax(280px,1fr))]"> | |
| <div class="card text-center transition-transform hover:-translate-y-1 hover:border-acc"> | |
| <span class="ic i-power mb-3 block text-4xl text-acc"></span> | |
| <h3 class="mb-2 text-lg">Daemon Control</h3> | |
| <p class="text-[0.92rem] text-mut">Start and stop the robot backend, wake it up or send it to sleep, watch its live status, from the topbar of every page.</p> | |
| </div> | |
| <div class="card text-center transition-transform hover:-translate-y-1 hover:border-acc"> | |
| <span class="ic i-camera mb-3 block text-4xl text-acc"></span> | |
| <h3 class="mb-2 text-lg">Live Camera</h3> | |
| <p class="text-[0.92rem] text-mut">Two ways to watch: direct <strong class="text-acc">MJPEG</strong> (zero re-encode, the robot’s own JPEG frames) or the daemon’s <strong class="text-acc">WebRTC</strong> stream. Pause, snapshot, pick your frame rate, go fullscreen.</p> | |
| </div> | |
| <div class="card text-center transition-transform hover:-translate-y-1 hover:border-acc"> | |
| <span class="ic i-cube-outline mb-3 block text-4xl text-acc"></span> | |
| <h3 class="mb-2 text-lg">3D Digital Twin</h3> | |
| <p class="text-[0.92rem] text-mut">When the camera is off, a live 3D model of your robot takes the stage: URDF meshes driven by the daemon’s joint stream at 20 Hz, with 40 generated skins to dress it up.</p> | |
| </div> | |
| <div class="card text-center transition-transform hover:-translate-y-1 hover:border-acc"> | |
| <span class="ic i-music-note mb-3 block text-4xl text-acc"></span> | |
| <h3 class="mb-2 text-lg">Move Player</h3> | |
| <p class="text-[0.92rem] text-mut">Play the dance and emotion libraries, stop them mid-move, and preview each one with an animated 3D thumbnail before you fire it.</p> | |
| </div> | |
| <div class="card text-center transition-transform hover:-translate-y-1 hover:border-acc"> | |
| <span class="ic i-store mb-3 block text-4xl text-acc"></span> | |
| <h3 class="mb-2 text-lg">App Store</h3> | |
| <p class="text-[0.92rem] text-mut">Browse official and community apps, search with typo tolerance, sort by name, author or ♥, and install, private Hugging Face Spaces included.</p> | |
| </div> | |
| <div class="card text-center transition-transform hover:-translate-y-1 hover:border-acc"> | |
| <span class="ic i-package-variant-closed mb-3 block text-4xl text-acc"></span> | |
| <h3 class="mb-2 text-lg">Apps Manager</h3> | |
| <p class="text-[0.92rem] text-mut">List installed apps, start, stop, update or remove them, with the install logs streaming live over WebSocket.</p> | |
| </div> | |
| <div class="card text-center transition-transform hover:-translate-y-1 hover:border-acc"> | |
| <span class="ic i-text-box-outline mb-3 block text-4xl text-acc"></span> | |
| <h3 class="mb-2 text-lg">Live Log Panel</h3> | |
| <p class="text-[0.92rem] text-mut">A slide-in console: colour-coded levels, timestamps, level filters and text search, pause, copy and clear, while the rest of the dashboard stays visible.</p> | |
| </div> | |
| <div class="card text-center transition-transform hover:-translate-y-1 hover:border-acc"> | |
| <span class="ic i-palette mb-3 block text-4xl text-acc"></span> | |
| <h3 class="mb-2 text-lg">Themes & Palettes</h3> | |
| <p class="text-[0.92rem] text-mut">Light and dark, following your system by default, plus <strong class="text-acc">twelve palettes</strong>, each with its own typeface. Yes, there is a Matrix one.</p> | |
| </div> | |
| <div class="card text-center transition-transform hover:-translate-y-1 hover:border-acc"> | |
| <span class="ic i-translate mb-3 block text-4xl text-acc"></span> | |
| <h3 class="mb-2 text-lg">Eight Languages</h3> | |
| <p class="text-[0.92rem] text-mut">English, French, Spanish, German, Italian, Portuguese, Japanese and Arabic, right-to-left included.</p> | |
| </div> | |
| <div class="card text-center transition-transform hover:-translate-y-1 hover:border-acc"> | |
| <span class="ic i-volume-high mb-3 block text-4xl text-acc"></span> | |
| <h3 class="mb-2 text-lg">Sound & Login</h3> | |
| <p class="text-[0.92rem] text-mut">Speaker and microphone levels in the topbar, and Hugging Face login (OAuth or token) with your avatar right there.</p> | |
| </div> | |
| <div class="card text-center transition-transform hover:-translate-y-1 hover:border-acc"> | |
| <span class="ic i-wifi-4 mb-3 block text-4xl text-acc"></span> | |
| <h3 class="mb-2 text-lg">Wifi, Updates & Cache</h3> | |
| <p class="text-[0.92rem] text-mut">On wireless robots the full settings page is back: scan and join networks, run OTA updates, clear the caches.</p> | |
| </div> | |
| <div class="card text-center transition-transform hover:-translate-y-1 hover:border-acc"> | |
| <span class="ic i-cloud-off-outline mb-3 block text-4xl text-acc"></span> | |
| <h3 class="mb-2 text-lg">Works Offline</h3> | |
| <p class="text-[0.92rem] text-mut">Only the store needs the internet. Lose your connection and the robot, the camera, the moves and the logs all keep working.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section id="changelog" class="py-14"> | |
| <div class="mx-auto max-w-[1100px] px-5"> | |
| <div class="mb-11 text-center"> | |
| <p class="mb-3.5 text-sm font-bold uppercase tracking-[1.5px] text-acc">Updates</p> | |
| <h2 class="mb-3 text-3xl">Changelog</h2> | |
| </div> | |
| <div id="changelog-grid" class="grid gap-[18px] grid-cols-[repeat(auto-fit,minmax(300px,1fr))]"></div> | |
| <div class="mt-6 text-center"> | |
| <details id="changelog-details"> | |
| <summary class="cursor-pointer font-semibold text-acc">View older versions</summary> | |
| <div id="changelog-older" class="mt-5 grid gap-[18px] grid-cols-[repeat(auto-fit,minmax(300px,1fr))]"></div> | |
| </details> | |
| </div> | |
| </div> | |
| </section> | |
| <footer class="border-t border-line px-5 py-9 text-center text-sm text-mut"> | |
| <p>Built with <span class="ic i-heart text-acc"></span> by <a href="https://huggingface.co/Domotick" target="_blank">Domotick</a></p> | |
| </footer> | |
| <script> | |
| // Theme switch (on = light), auto from the system by default, | |
| // saved to localStorage only after a manual change | |
| const themeSw = document.getElementById('themeSw'); | |
| const setTheme = t => { | |
| document.documentElement.setAttribute('data-theme', t); | |
| themeSw.classList.toggle('on', t === 'light'); | |
| }; | |
| themeSw.classList.toggle('on', document.documentElement.getAttribute('data-theme') === 'light'); | |
| themeSw.onclick = () => { | |
| const t = themeSw.classList.contains('on') ? 'dark' : 'light'; | |
| setTheme(t); | |
| localStorage.setItem('theme', t); | |
| }; | |
| matchMedia('(prefers-color-scheme: light)').addEventListener('change', e => { | |
| if (!localStorage.getItem('theme')) setTheme(e.matches ? 'light' : 'dark'); | |
| }); | |
| // Palette dots, each dot shows its palette's accent | |
| const PALETTES = { | |
| default: '#3b82f6', sunset: '#fb923c', ocean: '#22d3ee', violet: '#a78bfa', | |
| forest: '#4ade80', rose: '#fb7185', mint: '#5eead4', amber: '#e9a23b', | |
| slate: '#94a3b8', nordic: '#88c0d0', 'one-dark': '#61afef', midnight: '#818cf8', matrix: '#00ff41' | |
| }; | |
| const palettesEl = document.getElementById('palettes'); | |
| const current = () => document.documentElement.getAttribute('data-palette') || 'default'; | |
| for (const [name, color] of Object.entries(PALETTES)) { | |
| const b = document.createElement('button'); | |
| b.className = 'pdot' + (current() === name ? ' on' : ''); | |
| b.style.background = color; | |
| b.title = name; | |
| b.onclick = () => { | |
| if (name === 'default') document.documentElement.removeAttribute('data-palette'); | |
| else document.documentElement.setAttribute('data-palette', name); | |
| localStorage.setItem('palette', name); | |
| palettesEl.querySelectorAll('.pdot').forEach(d => d.classList.toggle('on', d === b)); | |
| }; | |
| palettesEl.appendChild(b); | |
| } | |
| // Demo panel switches (decorative) | |
| document.querySelectorAll('.demo-sw').forEach(sw => { sw.onclick = () => sw.classList.toggle('on'); }); | |
| // Changelog. Each version's changes are keyed by language (en, fr, es, …); | |
| // the browser language wins, English is the fallback. | |
| const VISIBLE_COUNT = 6; | |
| const clLang = (navigator.language || 'en').slice(0, 2).toLowerCase(); | |
| fetch('changelog.json') | |
| .then(res => res.json()) | |
| .then(data => { | |
| if (data.length > 0) { | |
| document.getElementById('version-pill').textContent = `v${data[0].version}`; | |
| } | |
| const mainGrid = document.getElementById('changelog-grid'); | |
| const olderGrid = document.getElementById('changelog-older'); | |
| data.forEach((item, index) => { | |
| const changes = (item.changes && (item.changes[clLang] || item.changes.en)) || []; | |
| const card = document.createElement('div'); | |
| card.className = 'card changelog-card'; | |
| card.innerHTML = ` | |
| <div class="version-badge">v${item.version}</div> | |
| <span class="date">${item.date}</span> | |
| <ul>${changes.map(c => `<li>${c}</li>`).join('')}</ul> | |
| `; | |
| (index < VISIBLE_COUNT ? mainGrid : olderGrid).appendChild(card); | |
| }); | |
| if (data.length <= VISIBLE_COUNT) { | |
| document.getElementById('changelog-details').style.display = 'none'; | |
| } | |
| }) | |
| .catch(err => console.error('Failed to load changelog:', err)); | |
| // Video preview: a poster facade that swaps in the YouTube player on click, | |
| // so nothing loads from YouTube until the visitor asks for it. | |
| document.querySelectorAll('.ex-video[data-yt]').forEach(el => { | |
| const play = () => { | |
| const id = el.dataset.yt; | |
| el.innerHTML = `<iframe class="h-full w-full" src="https://www.youtube-nocookie.com/embed/${id}?autoplay=1&rel=0" title="Demo video" frameborder="0" allow="autoplay; encrypted-media; picture-in-picture; fullscreen" allowfullscreen></iframe>`; | |
| el.classList.remove('cursor-pointer'); | |
| el.removeAttribute('role'); | |
| el.removeAttribute('tabindex'); | |
| }; | |
| el.addEventListener('click', play); | |
| el.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); play(); } }); | |
| }); | |
| </script> | |
| <script src="reachy_web/static/js/landing-i18n.js"></script> | |
| <script src="reachy_web/static/js/logo_anim.js"></script> | |
| </body> | |
| </html> | |