marionette / index.html
RemiFabre
Redesign test coverage: matrix only, show all platforms
61f57e3
Raw
History Blame
21.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Marionette - Manual performance recorder for Reachy Mini</title>
<meta name="description" content="Manually guide Reachy Mini to create natural motions, record movement and audio in real time.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--background: hsl(220, 13%, 8%);
--foreground: hsl(210, 40%, 98%);
--card: hsl(220, 13%, 12%);
--card-foreground: hsl(210, 40%, 98%);
--primary: hsl(36, 100%, 50%);
--primary-foreground: hsl(0, 0%, 100%);
--muted: hsl(220, 13%, 18%);
--muted-foreground: hsl(215, 16%, 60%);
--border: hsl(220, 13%, 20%);
--radius: 0.5rem;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: var(--background);
color: var(--foreground);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
}
/* Hero Section */
.hero {
padding: 4rem 0;
}
.hero-grid {
display: grid;
grid-template-columns: 1fr;
gap: 3rem;
align-items: center;
}
@media (min-width: 768px) {
.hero-grid {
grid-template-columns: 1fr 1fr;
}
}
.hero video {
width: 100%;
max-width: 700px;
border-radius: 0.5rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
}
.hero-content {
text-align: center;
}
@media (min-width: 768px) {
.hero-content {
text-align: left;
}
}
.hero-title {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-bottom: 1.5rem;
}
@media (min-width: 768px) {
.hero-title {
justify-content: flex-start;
}
}
.hero-title span {
font-size: 3.75rem;
}
.hero-title h1 {
font-size: 3rem;
font-weight: 700;
}
.tags {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
@media (min-width: 768px) {
.tags {
justify-content: flex-start;
}
}
.tag {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.75rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: 9999px;
background-color: hsla(36, 100%, 50%, 0.15);
color: var(--primary);
}
.hero-description {
font-size: 1.125rem;
color: var(--muted-foreground);
max-width: 36rem;
}
.hero-description strong {
color: var(--foreground);
}
/* Technical Section */
.technical {
padding: 4rem 0;
background-color: var(--card);
}
.technical-grid {
display: grid;
grid-template-columns: 1fr;
gap: 4rem;
align-items: flex-start;
}
@media (min-width: 768px) {
.technical-grid {
grid-template-columns: 1fr 1fr;
}
}
.technical h2 {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 1rem;
margin-top: 2rem;
}
.steps {
display: flex;
flex-direction: column;
gap: 1rem;
list-style: none;
}
.step {
display: flex;
align-items: flex-start;
gap: 1rem;
}
.step-number {
display: flex;
align-items: center;
justify-content: center;
min-width: 1.75rem;
height: 1.75rem;
border-radius: 9999px;
background-color: var(--primary);
color: var(--primary-foreground);
font-size: 0.875rem;
font-weight: 600;
}
.step-content {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.step-title {
font-size: 0.875rem;
font-weight: 600;
}
.step-body {
font-size: 0.875rem;
color: var(--muted-foreground);
}
.step-body a {
text-decoration: underline;
color: var(--muted-foreground);
transition: color 0.2s;
}
.step-body a:hover {
color: var(--primary);
}
.technical video {
width: 100%;
border-radius: 0.5rem;
box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.4);
}
/* Footer */
footer {
border-top: 1px solid var(--border);
padding: 3rem 0;
}
.footer-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
@media (min-width: 768px) {
.footer-grid {
grid-template-columns: repeat(3, 1fr);
}
}
.footer-column {
display: flex;
flex-direction: column;
gap: 1rem;
}
.author-section {
display: flex;
align-items: center;
gap: 1.25rem;
}
.author-link {
display: flex;
align-items: center;
gap: 0.75rem;
text-decoration: none;
color: inherit;
}
.author-link:hover .author-name {
color: var(--primary);
}
.author-link:hover .author-avatar {
border-color: var(--primary);
}
.author-avatar {
width: 3.5rem;
height: 3.5rem;
border-radius: 9999px;
border: 2px solid white;
transition: border-color 0.2s;
}
.author-intro {
font-size: 0.875rem;
color: var(--muted-foreground);
}
.author-name {
font-size: 1.125rem;
font-weight: 600;
transition: color 0.2s;
}
.follow-btn {
display: inline-flex;
align-items: center;
padding: 0.375rem 0.75rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: 9999px;
border: 1px solid var(--border);
background-color: var(--card);
color: var(--foreground);
text-decoration: none;
transition: background-color 0.2s;
}
.follow-btn:hover {
background-color: var(--muted);
}
.social-links {
display: flex;
gap: 0.75rem;
}
.social-link {
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 9999px;
background-color: var(--muted);
color: var(--muted-foreground);
text-decoration: none;
transition: all 0.2s;
}
.social-link:hover {
background-color: var(--primary);
color: var(--primary-foreground);
}
.social-link svg {
width: 1.25rem;
height: 1.25rem;
}
.footer-title {
font-size: 0.875rem;
font-weight: 600;
}
.footer-links {
list-style: none;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.footer-link {
font-size: 0.875rem;
color: var(--muted-foreground);
text-decoration: none;
transition: color 0.2s;
}
.footer-link:hover {
color: var(--primary);
}
.credits-text {
font-size: 0.875rem;
color: var(--muted-foreground);
}
.credits-text a {
color: var(--muted-foreground);
text-decoration: none;
transition: color 0.2s;
}
.credits-text a:hover {
color: var(--primary);
}
/* Test Coverage Section */
.test-coverage {
padding: 4rem 0;
}
.test-coverage h2 {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.test-coverage .section-subtitle {
font-size: 0.9rem;
color: var(--muted-foreground);
margin-bottom: 1.75rem;
}
.tc-table-wrap {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
margin-bottom: 1rem;
}
.tc-table {
border-collapse: separate;
border-spacing: 0;
font-size: 0.8rem;
width: 100%;
}
.tc-table th {
padding: 0.5rem 0.75rem;
text-align: center;
font-size: 0.65rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--muted-foreground);
border-bottom: 1px solid var(--border);
}
.tc-table th:first-child {
text-align: left;
}
.tc-table td {
padding: 0.5rem 0.75rem;
text-align: center;
white-space: nowrap;
border-bottom: 1px solid hsla(220, 13%, 20%, 0.5);
}
.tc-table .tc-platform {
text-align: left;
font-weight: 600;
font-size: 0.8rem;
}
.tc-table .tc-robot-label {
font-weight: 400;
color: var(--muted-foreground);
font-size: 0.75rem;
}
.tc-ok {
color: hsl(160, 84%, 55%);
font-weight: 600;
}
.tc-fail {
color: hsl(0, 84%, 70%);
font-weight: 600;
}
.tc-untested {
color: hsla(220, 13%, 50%, 0.4);
font-size: 0.7rem;
}
.tc-date {
display: block;
font-size: 0.65rem;
color: var(--muted-foreground);
margin-top: 1px;
}
.tc-legend {
display: flex;
gap: 1.25rem;
font-size: 0.7rem;
color: var(--muted-foreground);
margin-bottom: 1rem;
}
.tc-legend span {
display: inline-flex;
align-items: center;
gap: 0.3rem;
}
.tc-cta {
font-size: 0.8rem;
color: var(--muted-foreground);
line-height: 1.5;
max-width: 44rem;
}
.tc-cta a {
color: var(--primary);
text-decoration: none;
}
.tc-cta a:hover {
text-decoration: underline;
}
.tc-footnote {
font-size: 0.65rem;
color: hsla(215, 16%, 60%, 0.5);
margin-top: 0.75rem;
}
.tc-updated {
font-size: 0.7rem;
color: hsla(215, 16%, 60%, 0.6);
margin-top: 0.5rem;
}
</style>
</head>
<body>
<div class="min-h-screen">
<!-- Hero Section -->
<section class="hero">
<div class="container">
<div class="hero-grid">
<div>
<video autoplay loop muted playsinline>
<source src="marionette/assets/reachy-mini-demo-app-marionette.mp4" type="video/mp4">
</video>
</div>
<div class="hero-content">
<div class="hero-title">
<span>🎭</span>
<h1>Marionette</h1>
</div>
<div class="tags">
<span class="tag">motion</span>
<span class="tag">dataset</span>
<span class="tag">Kids</span>
</div>
<p class="hero-description">
<strong>Manual performance recorder for Reachy Mini</strong><br>
Manually guide Reachy Mini to create natural motions, record movement and audio in real time, and automatically save everything in a share-ready format compatible with Hugging Face.
</p>
</div>
</div>
</div>
</section>
<!-- Technical Section -->
<section class="technical">
<div class="container">
<div class="technical-grid">
<div>
<h2>How it works</h2>
<ol class="steps">
<li class="step">
<span class="step-number">1</span>
<div class="step-content">
<div class="step-title">Connect Reachy Mini & launch the app</div>
<div class="step-body">Connect to your Reachy Mini, navigate to the "Applications" tab, find "Marionette" and click "Launch" (make sure you already have the <a href="https://hf.co/reachy-mini/#download" target="_blank" rel="noreferrer">dashboard</a> installed).</div>
</div>
</li>
<li class="step">
<span class="step-number">2</span>
<div class="step-content">
<div class="step-title">Pick duration & audio</div>
<div class="step-body">Choose the recording length, enable or disable sound, and label your motion.</div>
</div>
</li>
<li class="step">
<span class="step-number">3</span>
<div class="step-content">
<div class="step-title">Guide the motion</div>
<div class="step-body">After a 3-2-1 countdown, gently move Reachy Mini's head by hand. Marionette disables torque to give you full manual control, while encoder data and FK poses are recorded at 100 Hz.</div>
</div>
</li>
<li class="step">
<span class="step-number">4</span>
<div class="step-content">
<div class="step-title">Replay & share</div>
<div class="step-body">Review and refine each take directly in the app, then export it automatically to local_dataset/ using the Reachy Mini emotions format (JSON + WAV), ready to share or upload.</div>
</div>
</li>
</ol>
</div>
<div>
<video autoplay loop muted playsinline>
<source src="marionette/assets/reachy-mini-demo-marionette-dashboard.mp4" type="video/mp4">
</video>
</div>
</div>
</div>
</section>
<!-- Test Coverage Section -->
<section class="test-coverage" id="test-coverage-section" style="display:none">
<div class="container">
<h2>Test Coverage</h2>
<p class="section-subtitle" id="tc-subtitle"></p>
<div class="tc-table-wrap" id="tc-matrix"></div>
<div class="tc-legend" id="tc-legend"></div>
<p class="tc-cta">
Help us expand coverage! Run the test suite on your setup and submit a PR with the results.
See <a href="https://huggingface.co/spaces/RemiFabre/marionette/blob/main/marionette/TESTING.md" target="_blank" rel="noopener noreferrer">TESTING.md</a> for instructions.
</p>
<p class="tc-footnote">Safari tested via Playwright's WebKit engine. Chromium covers Chrome, Edge, Brave, and similar browsers.</p>
<p class="tc-updated" id="tc-updated"></p>
</div>
</section>
<script>
(function() {
var ALL_PLATFORMS = [
{ os: 'Linux', robot: 'None' },
{ os: 'Linux', robot: 'Wireless' },
{ os: 'Linux', robot: 'Lite' },
{ os: 'macOS', robot: 'None' },
{ os: 'macOS', robot: 'Wireless' },
{ os: 'macOS', robot: 'Lite' },
{ os: 'Windows', robot: 'None' },
{ os: 'Windows', robot: 'Wireless' },
{ os: 'Windows', robot: 'Lite' }
];
var COLUMNS = [
{ suite: 'unit', browser: null, label: 'Unit' },
{ suite: 'e2e', browser: 'chromium', label: 'Chromium' },
{ suite: 'e2e', browser: 'firefox', label: 'Firefox' },
{ suite: 'e2e', browser: 'webkit', label: 'Safari' }
];
var ROBOT_LABELS = { 'None': 'no robot', 'Wireless': 'Wireless', 'Lite': 'Lite' };
fetch('marionette/static/test_coverage.json', { cache: 'no-store' })
.then(function(r) { return r.ok ? r.json() : Promise.reject(); })
.then(render)
.catch(function() {});
function render(data) {
var matrix = data.matrix || [];
if (!matrix.length) return;
document.getElementById('test-coverage-section').style.display = '';
// Build lookup from actual results
var lookup = {};
matrix.forEach(function(m) {
lookup[m.os + '|' + m.robot + '|' + m.suite + '|' + (m.browser || '')] = m;
});
// Count tested vs total platforms
var testedCount = 0;
ALL_PLATFORMS.forEach(function(p) {
var hasAny = COLUMNS.some(function(c) {
return !!lookup[p.os + '|' + p.robot + '|' + c.suite + '|' + (c.browser || '')];
});
if (hasAny) testedCount++;
});
var catalog = data.test_catalog || [];
var totalTests = catalog.reduce(function(s, c) { return s + c.count; }, 0);
document.getElementById('tc-subtitle').textContent =
totalTests + ' automated tests \u2014 ' + testedCount + ' of ' + ALL_PLATFORMS.length + ' platform combinations verified so far.';
// Build table
var h = '<table class="tc-table"><thead><tr><th>Platform</th>';
COLUMNS.forEach(function(c) { h += '<th>' + c.label + '</th>'; });
h += '</tr></thead><tbody>';
ALL_PLATFORMS.forEach(function(p) {
h += '<tr><td class="tc-platform">' + p.os + ' <span class="tc-robot-label">' + ROBOT_LABELS[p.robot] + '</span></td>';
COLUMNS.forEach(function(c) {
var m = lookup[p.os + '|' + p.robot + '|' + c.suite + '|' + (c.browser || '')];
if (m) {
var ok = m.status === 'pass';
h += '<td><span class="' + (ok ? 'tc-ok' : 'tc-fail') + '">';
h += (ok ? '\u2713' : '\u2717') + ' ' + m.passed + '/' + m.total;
h += '</span><span class="tc-date">' + m.date + '</span></td>';
} else {
h += '<td class="tc-untested">\u2014</td>';
}
});
h += '</tr>';
});
h += '</tbody></table>';
document.getElementById('tc-matrix').innerHTML = h;
// Legend
document.getElementById('tc-legend').innerHTML =
'<span><span class="tc-ok">\u2713</span> Passing</span>' +
'<span><span class="tc-fail">\u2717</span> Failing</span>' +
'<span><span class="tc-untested">\u2014</span> Not yet tested</span>';
if (data.generated_at) {
var d = new Date(data.generated_at);
document.getElementById('tc-updated').textContent =
'Last run: ' + d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
}
}
})();
</script>
<!-- Footer -->
<footer>
<div class="container">
<div class="footer-grid">
<!-- Author -->
<div class="footer-column">
<div class="author-section">
<a href="https://huggingface.co/RemiFabre" target="_blank" rel="noopener noreferrer" class="author-link">
<img src="https://cdn-avatars.huggingface.co/v1/production/uploads/noauth/AkX9RbZqzzQlz797iJeqL.jpeg" alt="RemiFabre" class="author-avatar">
<div>
<p class="author-intro">Hey, I'm</p>
<p class="author-name">RemiFabre</p>
</div>
</a>
<a href="https://huggingface.co/RemiFabre" target="_blank" rel="noopener noreferrer" class="follow-btn">Follow</a>
</div>
<div class="social-links">
<a href="https://twitter.com/RemiFabreRobot" target="_blank" rel="noopener noreferrer" aria-label="Twitter" class="social-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"></path></svg>
</a>
<a href="https://github.com/RemiFabre" target="_blank" rel="noopener noreferrer" aria-label="GitHub" class="social-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"></path><path d="M9 18c-4.51 2-5-2-7-2"></path></svg>
</a>
<a href="https://www.linkedin.com/in/remifabre" target="_blank" rel="noopener noreferrer" aria-label="LinkedIn" class="social-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect width="4" height="12" x="2" y="9"></rect><circle cx="4" cy="4" r="2"></circle></svg>
</a>
</div>
</div>
<!-- Resources -->
<div class="footer-column">
<h4 class="footer-title">Resources</h4>
<ul class="footer-links">
<li><a href="https://huggingface.co/docs/reachy_mini/index" target="_blank" rel="noopener noreferrer" class="footer-link">Read the documentation & FAQ</a></li>
<li><a href="https://hf.co/reachy-mini/#apps" target="_blank" rel="noopener noreferrer" class="footer-link">Browse other apps</a></li>
<li><a href="https://huggingface.co/blog/pollen-robotics/make-and-publish-your-reachy-mini-apps" target="_blank" rel="noopener noreferrer" class="footer-link">Make your own app</a></li>
</ul>
</div>
<!-- Credits -->
<div class="footer-column">
<h4 class="footer-title">Credits</h4>
<p class="credits-text">This app was created with <a href="https://huggingface.co/RemiFabre" target="_blank" rel="noopener noreferrer">love</a>.</p>
</div>
</div>
</div>
</footer>
</div>
</body>
</html>