rsundaraws's picture
Pin caption slot heights so the embed height is constant across stages
7636b66 verified
Raw History Blame Contribute Delete
30 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The Strands Robots data loop</title>
<style>
html, body { margin: 0; padding: 0; background: #FFFFFF; }
body { padding: 4px 10px 10px; }
.sr-wrap { max-width: 760px; margin: 0 auto; }
</style>
</head>
<body>
<div class="sr-wrap">
<div class="sr-loop"></div>
<style>
/* Animated build of arch_dataloop.svg. Same 1000x640 canvas, same four cards,
same palette. Orange is data in motion, exactly as it is in the static figure
where the return arrow is the only orange line. */
.sr-loop {
--navy: #1A3B5C;
--orange: #FF6B35;
--grey: #5A6470;
--body: #42505E;
--bg: #FAFAFA;
--hair: #C7D0DA;
--accent: var(--navy);
position: relative;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.sr-loop svg { display: block; width: 100%; height: auto; }
.sr-loop text { user-select: none; }
/* each stage owns the hue its card is stroked with in the static figure */
.sr-loop[data-active="record"] { --accent: #1A3B5C; }
.sr-loop[data-active="store"] { --accent: #FFAD03; }
.sr-loop[data-active="train"] { --accent: #6CAE3E; }
.sr-loop[data-active="deploy"] { --accent: #7A4F91; }
/* ---- cards: resting state is the static figure, unchanged ---- */
.sr-loop .card { cursor: pointer; }
.sr-loop .card-box {
transition: stroke-width .3s ease, filter .3s ease;
}
.sr-loop .card:hover .card-box { stroke-width: 2.6; }
.sr-loop .card:focus { outline: none; }
.sr-loop .card:focus-visible .card-box { stroke-width: 3; stroke-dasharray: 5 3; }
/* the active card is the only one at full strength; the rest recede */
.sr-loop .card { transition: opacity .35s ease; }
.sr-loop[data-active] .card { opacity: .5; }
.sr-loop[data-active] .card.is-active { opacity: 1; }
.sr-loop .card.is-active .card-box { stroke-width: 3; }
/* metric badge, revealed on the active card only */
.sr-loop .badge { opacity: 0; transition: opacity .3s ease, transform .3s ease; }
.sr-loop .badge-pill { fill: var(--accent); }
.sr-loop .badge-text { fill: #FFFFFF; font-size: 12.5px; font-weight: 700; }
.sr-loop .card.is-active .badge { opacity: 1; }
/* ---- arrows: grey at rest, the traversed one carries the accent ---- */
.sr-loop .hop { stroke: var(--navy); transition: stroke .35s ease, opacity .35s ease; }
.sr-loop[data-active] .hop { opacity: .28; }
.sr-loop .hop.is-live { opacity: 1; stroke: var(--accent); }
/* the return arrow keeps the orange its label carries, live or not */
.sr-loop .hop.hop-return { stroke: var(--orange); }
/* the bytes in flight */
.sr-loop .mover { fill: var(--orange); opacity: 0; }
.sr-loop .mover-trail { stroke: var(--orange); fill: none; opacity: 0; }
/* ---- hub: the shared backend, always lit ---- */
.sr-loop .spoke { stroke: var(--hair); transition: stroke .35s ease, opacity .35s ease; }
.sr-loop .spoke.is-live { stroke: var(--accent); opacity: 1; }
.sr-loop .hub-ring { transition: stroke .35s ease; }
.sr-loop .hub-pulse {
fill: none; stroke: var(--accent); opacity: 0;
transform-box: fill-box; transform-origin: 50% 50%;
}
.sr-loop .hub-pulse.beat { animation: srPulse 1.05s ease-out; }
@keyframes srPulse {
from { opacity: .55; transform: scale(1); }
to { opacity: 0; transform: scale(1.34); }
}
/* ---- caption strip: HTML, below the canvas, so it keeps its own type size.
Inside the SVG it would scale with the viewBox and read at ~9px in a blog
column. ---- */
.sr-loop .cap {
border-top: 1px solid var(--hair);
margin-top: 14px;
padding-top: 13px;
}
.sr-loop .cap-eyebrow {
color: var(--accent); font-size: 12px; font-weight: 700; letter-spacing: .07em;
transition: color .35s ease;
}
/* each text slot holds the tallest stage's line count, so the hint below it
never jumps when a caption wraps one line further */
.sr-loop .cap-line {
color: var(--body); font-size: 15px; line-height: 1.5; margin: 7px 0 0;
max-width: 68ch; min-height: 45px;
}
.sr-loop .cap-src {
color: var(--grey); font-size: 13px; line-height: 1.45; margin: 7px 0 0;
max-width: 78ch; min-height: 38px;
}
.sr-loop .cap-hint {
color: var(--grey); font-size: 12.5px; margin: 12px 0 0;
}
.sr-loop .cap-hint b { color: var(--accent); font-weight: 700; transition: color .35s ease; }
.sr-loop .swap { animation: srSwap .34s ease; }
@keyframes srSwap {
from { opacity: 0; }
to { opacity: 1; }
}
/* progress ticks under the title */
.sr-loop .tick { transition: fill .3s ease; }
/* narrow columns wrap the captions further, so the slots grow to match.
Declared after the base rules so it wins on equal specificity. */
@media (max-width: 559px) {
.sr-loop .cap-line { min-height: 90px; }
.sr-loop .cap-src { min-height: 57px; }
}
@media (prefers-reduced-motion: reduce) {
.sr-loop .card, .sr-loop .hop, .sr-loop .spoke, .sr-loop .badge,
.sr-loop .cap-eyebrow, .sr-loop .card-box, .sr-loop .tick { transition: none; }
.sr-loop .swap, .sr-loop .hub-pulse.beat { animation: none; }
.sr-loop .mover, .sr-loop .mover-trail { display: none; }
}
</style>
<script type="text/x-template" id="sr-loop-svg">
<svg viewBox="0 0 1000 640" role="img" aria-labelledby="srTitle srDesc" preserveAspectRatio="xMidYMid meet">
<title id="srTitle">The Strands Robots data loop</title>
<desc id="srDesc">Four stages around one shared Robot(): record, store, train, deploy. Select a stage to see what it moves and what it costs.</desc>
<defs>
<marker id="srArrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="context-stroke"/>
</marker>
<marker id="srArrowLoop" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7.5" markerHeight="7.5" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#FF6B35"/>
</marker>
</defs>
<rect x="0" y="0" width="1000" height="640" fill="#FAFAFA"/>
<text x="500" y="44" text-anchor="middle" font-size="23" font-weight="600" fill="#1A3B5C">The Strands Robots data loop</text>
<text x="500" y="70" text-anchor="middle" font-size="14" fill="#5A6470">One Robot(), one LeRobot format, one hf:// backend</text>
<g id="srTicks">
<rect class="tick" data-tick="0" x="411" y="82" width="40" height="4" rx="2" fill="#C7D0DA"/>
<rect class="tick" data-tick="1" x="457" y="82" width="40" height="4" rx="2" fill="#C7D0DA"/>
<rect class="tick" data-tick="2" x="503" y="82" width="40" height="4" rx="2" fill="#C7D0DA"/>
<rect class="tick" data-tick="3" x="549" y="82" width="40" height="4" rx="2" fill="#C7D0DA"/>
</g>
<!-- ===== hops between stages (grey at rest, accent when traversed) ===== -->
<line class="hop" data-hop="1" x1="368" y1="170" x2="632" y2="170" stroke-width="2" marker-end="url(#srArrow)"/>
<line class="hop" data-hop="2" x1="774" y1="230" x2="774" y2="460" stroke-width="2" marker-end="url(#srArrow)"/>
<line class="hop" data-hop="3" x1="632" y1="520" x2="368" y2="520" stroke-width="2" marker-end="url(#srArrow)"/>
<line class="hop hop-return" data-hop="0" x1="226" y1="460" x2="226" y2="230" stroke-width="2.5" marker-end="url(#srArrowLoop)"/>
<text x="150" y="350" text-anchor="middle" font-size="12" font-weight="600" fill="#FF6B35">new demos</text>
<text x="150" y="366" text-anchor="middle" font-size="12" font-weight="600" fill="#FF6B35">feed back</text>
<!-- ===== spokes to the shared hub ===== -->
<line class="spoke" data-spoke="record" x1="445" y1="300" x2="300" y2="210" stroke-width="1" stroke-dasharray="3 3"/>
<line class="spoke" data-spoke="store" x1="555" y1="300" x2="700" y2="210" stroke-width="1" stroke-dasharray="3 3"/>
<line class="spoke" data-spoke="train" x1="555" y1="390" x2="700" y2="480" stroke-width="1" stroke-dasharray="3 3"/>
<line class="spoke" data-spoke="deploy" x1="445" y1="390" x2="300" y2="480" stroke-width="1" stroke-dasharray="3 3"/>
<!-- ===== the bytes in flight ===== -->
<line class="mover-trail" id="srTrail" x1="0" y1="0" x2="0" y2="0" stroke-width="3" stroke-linecap="round"/>
<circle class="mover" id="srMover" r="5.5" cx="0" cy="0"/>
<!-- ===== centre hub: the shared Robot() ===== -->
<circle class="hub-pulse" cx="500" cy="345" r="80" stroke-width="3"/>
<circle class="hub-ring" cx="500" cy="345" r="80" fill="#FFFFFF" stroke="#1A3B5C" stroke-width="2"/>
<g transform="translate(491.5,288) scale(0.0584)">
<path d="M97.2902 52.7884C85.0674 49.1667 72.2234 56.1389 68.6017 68.3616C64.9801 80.5843 71.9524 93.4283 84.1749 97.0501L235.117 139.775C245.223 142.769 246.357 156.628 236.874 161.226L32.546 260.291C-14.9439 283.316 -9.16107 352.74 41.4835 367.591L189.551 411.009L190.125 411.169C202.183 414.376 214.665 407.396 218.196 395.355C221.784 383.122 214.774 370.296 202.541 366.709L54.4738 323.291C44.3447 320.321 43.1879 306.436 52.6857 301.831L257.014 202.766C304.432 179.776 298.758 110.483 248.233 95.512L97.2902 52.7884Z" fill="#0E0E0E"/>
<path d="M259.147 0.981812C271.389 -2.57498 284.197 4.46571 287.754 16.7074C291.311 28.9492 284.27 41.757 272.028 45.3138L71.1727 103.671C40.7142 112.521 37.1976 154.262 65.7459 168.083L241.343 253.093C307.872 285.302 299.794 382.546 228.862 403.336L30.4041 461.502C18.1707 465.088 5.34708 458.078 1.76153 445.844C-1.8239 433.611 5.18637 420.787 17.4197 417.202L215.878 359.035C246.277 350.125 249.739 308.449 221.226 294.645L45.6297 209.635C-20.9834 177.386 -12.7772 79.9893 58.2928 59.3402L259.147 0.981812Z" fill="#00C95F"/>
</g>
<text x="500" y="346" text-anchor="middle" font-size="15" font-weight="600" fill="#1A3B5C">Robot("so100")</text>
<text x="500" y="366" text-anchor="middle" font-size="12" fill="#5A6470">shared</text>
<text x="500" y="383" text-anchor="middle" font-size="12" fill="#5A6470">DatasetRecorder</text>
<!-- ===== Stage 1: RECORD ===== -->
<g class="card" data-card="record" tabindex="0" role="button" aria-label="Stage 1, Record">
<rect class="card-box" x="92" y="118" width="268" height="104" rx="6" fill="#E8F1F8" stroke="#1A3B5C" stroke-width="1.5"/>
<text x="116" y="148" font-size="13" font-weight="600" fill="#FF6B35">1 &#183; RECORD</text>
<text x="116" y="176" font-size="15.5" font-weight="600" fill="#1A3B5C">start_recording()</text>
<g class="badge">
<rect class="badge-pill" x="116" y="192" width="118" height="22" rx="11"/>
<text class="badge-text" x="175" y="207" text-anchor="middle">no conversion</text>
</g>
</g>
<!-- ===== Stage 2: STORE ===== -->
<g class="card" data-card="store" tabindex="0" role="button" aria-label="Stage 2, Store">
<rect class="card-box" x="640" y="118" width="268" height="104" rx="6" fill="#FFF4E8" stroke="#FFAD03" stroke-width="1.5"/>
<text x="664" y="148" font-size="13" font-weight="600" fill="#FF6B35">2 &#183; STORE</text>
<text x="664" y="176" font-size="15.5" font-weight="600" fill="#1A3B5C">stop_recording(bucket=)</text>
<g class="badge">
<rect class="badge-pill" x="664" y="192" width="110" height="22" rx="11"/>
<text class="badge-text" x="719" y="207" text-anchor="middle">5.5 MB moved</text>
</g>
<g transform="translate(862,126) scale(0.133)">
<path d="M203.21 123.685V123.194C203.21 81.34 169.292 47.411 127.435 47.411C85.5791 47.411 51.648 81.342 51.648 123.194V123.685C51.6529 123.848 203.21 123.522 203.21 123.685Z" fill="#FFD21E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M203.21 123.685V123.194C203.21 81.34 169.292 47.411 127.435 47.411C85.5791 47.411 51.648 81.342 51.648 123.194V123.358C51.646 123.467 51.645 123.576 51.648 123.685C51.6529 123.848 51.6546 124.011 51.653 124.174L51.6581 124.534L51.661 124.663C51.661 124.723 51.6631 124.782 51.6651 124.842C51.6681 124.937 51.67 125.033 51.67 125.128L51.681 125.517L51.697 125.974L51.702 126.124L51.722 126.62C51.73 126.805 51.7401 126.989 51.7491 127.173C51.76 127.375 51.7701 127.564 51.7821 127.753C51.7921 127.927 51.802 128.101 51.815 128.275C51.8258 128.455 51.8358 128.605 51.847 128.754L51.883 129.226C51.8921 129.338 51.898 129.422 51.906 129.503C51.9658 130.224 52.0355 130.945 52.1151 131.664L52.181 132.238L52.2491 132.793L52.322 133.347C52.3753 133.755 52.433 134.162 52.495 134.568L52.558 134.979C52.8435 136.808 53.1971 138.626 53.618 140.429L53.655 140.586L53.746 140.971L53.802 140.904C56.002 138.274 59.158 136.824 62.689 136.824C65.519 136.824 68.4221 137.76 71.3321 139.605C73.2621 140.831 75.3961 143.002 77.5921 145.733C79.6241 142.911 82.4721 141.035 85.7301 140.523C86.3513 140.425 86.9792 140.376 87.6081 140.375C95.0441 140.375 99.523 146.828 101.215 152.633C102.051 154.594 106.08 163.526 112.156 169.568C121.392 178.795 123.703 188.316 119.132 198.511H119.148C119.459 198.546 119.772 198.578 120.087 198.607C120.46 198.643 120.714 198.665 121.127 198.7L121.507 198.73C121.869 198.758 122.596 198.807 122.596 198.807L123.114 198.838L123.703 198.869L124.294 198.895L124.816 198.915L125.305 198.929C125.394 198.933 125.483 198.936 125.572 198.936L125.668 198.939C126.258 198.953 126.847 198.96 127.437 198.959H128.063L128.51 198.954C128.62 198.949 128.729 198.949 128.84 198.949H129.165C129.224 198.943 129.283 198.941 129.343 198.941H129.522L130.401 198.914L130.982 198.888C131.15 198.882 131.316 198.873 131.482 198.865L131.927 198.84L132.738 198.788L133.761 198.71L134.479 198.647C135.107 198.591 135.733 198.525 136.359 198.45L136.786 198.399C132.287 188.247 134.616 178.767 143.813 169.577C149.876 163.519 153.905 154.587 154.745 152.625C156.438 146.821 160.914 140.368 168.352 140.368C168.981 140.368 169.61 140.418 170.231 140.516C173.486 141.028 176.334 142.904 178.369 145.726C180.566 142.996 182.699 140.823 184.63 139.597C187.539 137.753 190.445 136.817 193.272 136.817C196.388 136.817 199.212 137.947 201.345 140.02C201.422 139.682 201.568 139.006 201.568 139.006C201.749 138.124 202.067 136.437 202.067 136.437C202.218 135.748 202.246 135.402 202.246 135.402L202.398 134.424C202.449 134.081 202.616 132.841 202.616 132.841L202.757 131.629L202.854 130.696C202.964 129.478 203.072 127.891 203.072 127.891L203.122 127.002C203.144 126.56 203.169 125.896 203.169 125.896L203.183 125.425C203.189 125.278 203.196 124.977 203.196 124.977C203.199 124.872 203.202 124.663 203.202 124.663L203.21 124.174V123.685ZM108.638 199.391C114.64 190.59 114.214 183.984 105.98 175.754C97.7441 167.523 92.951 155.487 92.951 155.487C92.951 155.487 91.1621 148.496 87.0821 149.138C83.0021 149.78 80.0091 160.227 88.5521 166.622C97.0941 173.017 86.8521 177.353 83.5641 171.352C80.2761 165.35 71.299 149.923 66.645 146.972C61.991 144.021 58.718 145.675 59.815 151.757C60.36 154.776 65.4281 159.929 70.1631 164.743C74.9671 169.627 79.428 174.163 78.474 175.768C76.581 178.955 69.9141 172.023 69.9141 172.023C69.9141 172.023 49.038 153.025 44.494 157.976C40.304 162.539 46.765 166.418 56.7211 172.397C57.5671 172.905 58.4391 173.429 59.3321 173.969C70.7231 180.865 71.609 182.684 69.992 185.293C69.395 186.257 65.582 183.968 60.892 181.153C52.897 176.352 42.3551 170.023 40.8661 175.688C39.5781 180.591 47.334 183.595 54.368 186.32C60.228 188.59 65.5881 190.666 64.7991 193.484C63.9821 196.406 59.5531 193.969 54.7121 191.305C49.2771 188.314 43.3221 185.038 41.3731 188.735C37.6901 195.725 66.7831 203.954 67.0231 204.015C76.4231 206.453 100.295 211.619 108.638 199.391ZM147.303 199.391C141.301 190.59 141.727 183.984 149.962 175.754C158.197 167.523 162.99 155.487 162.99 155.487C162.99 155.487 164.779 148.496 168.859 149.138C172.939 149.78 175.932 160.227 167.39 166.622C158.847 173.017 169.089 177.353 172.377 171.352C175.666 165.35 184.637 149.923 189.291 146.972C193.945 144.021 197.22 145.675 196.122 151.757C195.578 154.776 190.509 159.929 185.774 164.744C180.97 169.628 176.509 174.163 177.462 175.768C179.355 178.955 186.027 172.019 186.027 172.019C186.027 172.019 206.902 153.022 211.448 157.973C215.637 162.535 209.176 166.415 199.219 172.394C198.348 172.917 197.478 173.441 196.609 173.966C185.218 180.862 184.332 182.681 185.948 185.289C186.546 186.254 190.359 183.964 195.048 181.149C203.044 176.349 213.586 170.019 215.075 175.685C216.364 180.588 208.607 183.592 201.573 186.317C195.713 188.587 190.353 190.663 191.141 193.481C191.957 196.402 196.385 193.965 201.225 191.301C206.66 188.31 212.616 185.032 214.564 188.732C218.248 195.726 189.15 203.947 188.915 204.007C179.515 206.453 155.643 211.619 147.303 199.391Z" fill="#FFD21E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M152.047 102.567C153.229 102.985 154.108 104.257 154.944 105.468C156.074 107.104 157.126 108.627 158.74 107.769C160.644 106.756 162.205 105.202 163.225 103.302C164.246 101.402 164.681 99.2427 164.475 97.096C164.321 95.4908 163.813 93.9398 162.987 92.5548C162.161 91.1697 161.038 89.985 159.7 89.0862C158.361 88.1874 156.839 87.5968 155.245 87.3569C153.65 87.117 152.022 87.2339 150.478 87.699C148.934 88.1639 147.513 88.9653 146.316 90.0455C145.119 91.1257 144.176 92.4578 143.556 93.946C142.936 95.4342 142.653 97.0415 142.728 98.652C142.804 100.263 143.235 101.836 143.992 103.26C144.74 104.667 146.4 104.003 148.152 103.302C149.525 102.753 150.956 102.181 152.047 102.567ZM100.672 102.567C99.49 102.985 98.611 104.258 97.775 105.468C96.645 107.105 95.592 108.627 93.979 107.769C91.5845 106.501 89.7482 104.386 88.8278 101.838C87.9075 99.2895 87.9692 96.4896 89.0008 93.9841C90.0324 91.4786 91.9601 89.4471 94.408 88.2855C96.856 87.1239 99.6488 86.9156 102.242 87.701C104.307 88.3228 106.141 89.5427 107.513 91.2065C108.885 92.8704 109.732 94.9035 109.949 97.049C110.165 99.1945 109.74 101.356 108.728 103.26C107.979 104.667 106.319 104.003 104.567 103.303C103.193 102.753 101.764 102.181 100.672 102.567ZM144.099 149.318C152.242 142.903 155.233 132.429 155.233 125.977C155.233 120.877 151.802 122.482 146.309 125.202L145.999 125.355C140.957 127.852 134.245 131.177 126.877 131.177C119.508 131.177 112.796 127.852 107.755 125.354C102.084 122.545 98.527 120.783 98.527 125.978C98.527 132.634 101.709 143.563 110.443 149.912C111.596 147.573 113.219 145.497 115.211 143.813C117.202 142.129 119.52 140.874 122.018 140.126C122.89 139.866 123.788 141.367 124.707 142.904C125.594 144.386 126.501 145.902 127.423 145.902C128.406 145.902 129.371 144.408 130.314 142.95C131.299 141.425 132.26 139.94 133.189 140.237C137.864 141.738 141.775 144.993 144.099 149.318Z" fill="#32343D"/>
<path d="M144.097 149.317C139.856 152.659 134.219 154.9 126.878 154.9C119.981 154.9 114.587 152.922 110.443 149.911C111.596 147.572 113.219 145.495 115.211 143.812C117.202 142.128 119.52 140.873 122.018 140.125C123.73 139.614 125.545 145.901 127.423 145.901C129.433 145.901 131.37 139.655 133.189 140.236C137.863 141.738 141.773 144.993 144.097 149.317Z" fill="#FF323D"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M81.2 111.64C80.2312 112.288 79.1173 112.687 77.9572 112.801C76.7971 112.916 75.6267 112.742 74.55 112.295C73.6893 111.94 72.9072 111.418 72.2488 110.759C71.5903 110.101 71.0684 109.319 70.713 108.458C70.267 107.381 70.0935 106.211 70.2082 105.051C70.3228 103.891 70.7219 102.777 71.37 101.808C72.1488 100.642 73.2558 99.7333 74.5512 99.1967C75.8466 98.6601 77.272 98.5197 78.6471 98.7935C80.0223 99.0672 81.2853 99.7427 82.2764 100.734C83.2675 101.726 83.9422 102.99 84.215 104.365C84.4883 105.74 84.3477 107.165 83.8113 108.46C83.2748 109.755 82.3654 110.861 81.2 111.64ZM182.613 111.64C181.644 112.288 180.53 112.687 179.37 112.801C178.209 112.916 177.039 112.742 175.962 112.295C175.101 111.939 174.319 111.418 173.661 110.759C173.003 110.101 172.481 109.319 172.125 108.458C171.68 107.381 171.507 106.211 171.621 105.051C171.736 103.891 172.135 102.777 172.782 101.808C173.364 100.936 174.133 100.205 175.032 99.6658C175.931 99.1269 176.938 98.7942 177.981 98.6917C179.025 98.5891 180.078 98.7193 181.064 99.0728C182.051 99.4264 182.947 99.9944 183.688 100.736C184.68 101.727 185.355 102.99 185.628 104.365C185.902 105.74 185.761 107.165 185.224 108.46C184.687 109.755 183.779 110.861 182.613 111.64Z" fill="#FFAD03"/>
</g>
</g>
<!-- ===== Stage 3: TRAIN ===== -->
<g class="card" data-card="train" tabindex="0" role="button" aria-label="Stage 3, Train">
<rect class="card-box" x="640" y="468" width="268" height="104" rx="6" fill="#E8F5EC" stroke="#6CAE3E" stroke-width="1.5"/>
<text x="664" y="498" font-size="13" font-weight="600" fill="#FF6B35">3 &#183; TRAIN</text>
<text x="664" y="526" font-size="15.5" font-weight="600" fill="#1A3B5C">stream_dataset()</text>
<g class="badge">
<rect class="badge-pill" x="664" y="542" width="122" height="22" rx="11"/>
<text class="badge-text" x="725" y="557" text-anchor="middle">100 KB on disk</text>
</g>
</g>
<!-- ===== Stage 4: DEPLOY ===== -->
<g class="card" data-card="deploy" tabindex="0" role="button" aria-label="Stage 4, Deploy">
<rect class="card-box" x="92" y="468" width="268" height="104" rx="6" fill="#F0E8F5" stroke="#7A4F91" stroke-width="1.5"/>
<text x="116" y="498" font-size="13" font-weight="600" fill="#FF6B35">4 &#183; DEPLOY</text>
<text x="116" y="526" font-size="15.5" font-weight="600" fill="#1A3B5C">mode="real"</text>
<g class="badge">
<rect class="badge-pill" x="116" y="542" width="104" height="22" rx="11"/>
<text class="badge-text" x="168" y="557" text-anchor="middle">one keyword</text>
</g>
</g>
<text x="500" y="612" text-anchor="middle" font-size="12.5" fill="#5A6470">The dataset never leaves the LeRobot format. No cloud infrastructure to run.</text>
</svg>
<div class="cap" aria-live="polite">
<div class="cap-eyebrow" id="srCapEyebrow"></div>
<p class="cap-line" id="srCapLine"></p>
<p class="cap-src" id="srCapSrc"></p>
<p class="cap-hint">Hover or click a stage to follow one recording all the way around.</p>
</div>
</script>
<script>
(() => {
const ROOT = 'sr-loop';
const bootstrap = () => {
const s = document.currentScript;
let c = s ? s.previousElementSibling : null;
while (c && !(c.classList && c.classList.contains(ROOT))) c = c.previousElementSibling;
if (!c) {
const cand = Array.from(document.querySelectorAll('.' + ROOT))
.filter((e) => e.dataset.mounted !== 'true');
c = cand[cand.length - 1] || null;
}
if (!c || c.dataset.mounted === 'true') return;
c.dataset.mounted = 'true';
const tpl = document.getElementById('sr-loop-svg');
c.innerHTML = tpl.textContent || tpl.innerHTML;
const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// Each caption states what the stage moves. The two figures come from
// measurements reported in the post; stages 1 and 4 make format and API
// claims, so they carry no number.
const STAGES = [
{
key: 'record',
eyebrow: 'STAGE 1 OF 4 路 RECORD',
line: 'One prompt sets the scene, adds cameras, runs the episode, and writes Parquet shards for state and action plus one MP4 per camera.',
src: 'The layout is exactly what LeRobot writes, so the dataset needs no conversion at any later stage.',
// the hop that delivered the data into this stage, and the spoke it shares
hop: 0,
},
{
key: 'store',
eyebrow: 'STAGE 2 OF 4 路 STORE',
line: 'The bucket is the working layer between recording and training, and Xet deduplicates each sync at the byte level.',
src: "Hugging Face bucket benchmarks: changing 1% of a 500 MB file moved 5.5 MB, not 500 MB.",
hop: 1,
},
{
key: 'train',
eyebrow: 'STAGE 3 OF 4 路 TRAIN',
line: 'Training reads frames over the Hub instead of downloading the dataset first, decoding camera video on the fly from the MP4 shards.',
src: 'After a full read of a pick-and-place dataset, local disk held three meta files, one episode-index parquet, and the README.',
hop: 2,
},
{
key: 'deploy',
eyebrow: 'STAGE 4 OF 4 路 DEPLOY',
line: 'The checkpoint runs on the physical arm through the same Robot() that recorded the data, and the demos it records return to the same bucket.',
src: 'One keyword argument separates the simulated rollout from the hardware one. Nothing else in the loop changes.',
hop: 3,
},
];
const cards = new Map(
Array.from(c.querySelectorAll('.card')).map((el) => [el.getAttribute('data-card'), el])
);
const hops = Array.from(c.querySelectorAll('.hop'));
const spokes = new Map(
Array.from(c.querySelectorAll('.spoke')).map((el) => [el.getAttribute('data-spoke'), el])
);
const ticks = Array.from(c.querySelectorAll('.tick'));
const pulse = c.querySelector('.hub-pulse');
const mover = c.querySelector('#srMover');
const trail = c.querySelector('#srTrail');
const capEyebrow = c.querySelector('#srCapEyebrow');
const capLine = c.querySelector('#srCapLine');
const capSrc = c.querySelector('#srCapSrc');
const seen = new Set();
let current = -1;
let auto = null;
let userTookOver = false;
let raf = null;
// The bytes travel the hop that feeds the selected stage.
const runMover = (hopIdx) => {
if (reduce) return;
const h = hops.find((x) => Number(x.getAttribute('data-hop')) === hopIdx);
if (!h) return;
if (raf) cancelAnimationFrame(raf);
const x1 = +h.getAttribute('x1'), y1 = +h.getAttribute('y1');
const x2 = +h.getAttribute('x2'), y2 = +h.getAttribute('y2');
const DUR = 900;
let t0 = null;
const step = (ts) => {
if (t0 === null) t0 = ts;
const k = Math.min(1, (ts - t0) / DUR);
const e = k < 0.5 ? 2 * k * k : 1 - Math.pow(-2 * k + 2, 2) / 2; // ease-in-out
const x = x1 + (x2 - x1) * e, y = y1 + (y2 - y1) * e;
const bx = x1 + (x2 - x1) * Math.max(0, e - 0.14);
const by = y1 + (y2 - y1) * Math.max(0, e - 0.14);
mover.setAttribute('cx', x.toFixed(1));
mover.setAttribute('cy', y.toFixed(1));
trail.setAttribute('x1', bx.toFixed(1)); trail.setAttribute('y1', by.toFixed(1));
trail.setAttribute('x2', x.toFixed(1)); trail.setAttribute('y2', y.toFixed(1));
const op = k > 0.88 ? (1 - k) / 0.12 : 1;
mover.setAttribute('opacity', op.toFixed(2));
trail.setAttribute('opacity', (op * 0.55).toFixed(2));
if (k < 1) raf = requestAnimationFrame(step);
else {
mover.setAttribute('opacity', '0');
trail.setAttribute('opacity', '0');
raf = null;
pulse.classList.remove('beat');
void pulse.getBoundingClientRect();
pulse.classList.add('beat');
}
};
raf = requestAnimationFrame(step);
};
const select = (i) => {
if (i === current) return;
current = i;
const st = STAGES[i];
seen.add(i);
c.setAttribute('data-active', st.key);
cards.forEach((el, k) => {
const on = k === st.key;
el.classList.toggle('is-active', on);
el.setAttribute('aria-pressed', on ? 'true' : 'false');
});
hops.forEach((h) => {
h.classList.toggle('is-live', Number(h.getAttribute('data-hop')) === st.hop);
});
spokes.forEach((el, k) => el.classList.toggle('is-live', k === st.key));
ticks.forEach((t, k) => {
t.setAttribute('fill', k === i ? 'var(--accent)'
: seen.has(k) ? 'color-mix(in srgb, var(--accent) 45%, #C7D0DA)' : '#C7D0DA');
});
capEyebrow.textContent = st.eyebrow;
capLine.textContent = st.line;
capSrc.textContent = st.src;
if (!reduce) {
[capEyebrow, capLine, capSrc].forEach((el) => {
el.classList.remove('swap');
void el.getBoundingClientRect();
el.classList.add('swap');
});
}
runMover(st.hop);
};
const startAuto = () => {
if (auto || reduce || userTookOver) return;
auto = setInterval(() => {
if (userTookOver) { clearInterval(auto); auto = null; return; }
select((current + 1) % STAGES.length);
}, 3400);
};
const stopAuto = () => { if (auto) { clearInterval(auto); auto = null; } };
const takeOver = () => { userTookOver = true; stopAuto(); };
STAGES.forEach((st, i) => {
const el = cards.get(st.key);
if (!el) return;
el.addEventListener('click', () => { takeOver(); select(i); });
el.addEventListener('mouseenter', () => { if (!userTookOver) { stopAuto(); select(i); } });
el.addEventListener('focus', () => { takeOver(); select(i); });
el.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); takeOver(); select(i); return; }
let n = null;
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') n = (i + 1) % STAGES.length;
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') n = (i - 1 + STAGES.length) % STAGES.length;
else if (e.key === 'Home') n = 0;
else if (e.key === 'End') n = STAGES.length - 1;
if (n !== null) { e.preventDefault(); takeOver(); cards.get(STAGES[n].key).focus(); }
});
});
select(0);
startAuto();
c.addEventListener('mouseleave', startAuto);
};
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bootstrap, { once: true });
else bootstrap();
})();
</script>
</div>
</body>
</html>