feat: modal logo artwork + sync colour variants from pollen site
Browse files- welcome modal now uses the Microduck sticker + wordmark press assets
(LFS-tracked PNGs, signed like every other private-Space subresource)
- variants.js replaced with the site's 3d-module version: dedicated
tongue/hips/sideShells slots, black leg motors, uniform yellow/orange
shoe, and orange lower beak on the blue scheme
- swatches honour the per-variant swatch override (purple dot is purple)
- duck.js/variants.js dynamic imports reuse rl.js's ?v= cache-buster
Co-authored-by: Cursor <cursoragent@cursor.com>
- .gitattributes +1 -0
- assets/logo-duck.png +3 -0
- assets/logo-wordmark.png +3 -0
- index.html +29 -23
- rl.js +11 -8
- variants.js +57 -33
.gitattributes
CHANGED
|
@@ -35,4 +35,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
*.stl filter=lfs diff=lfs merge=lfs -text
|
| 37 |
*.wav filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 38 |
*.jpg filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
*.stl filter=lfs diff=lfs merge=lfs -text
|
| 37 |
*.wav filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
*.jpg filter=lfs diff=lfs merge=lfs -text
|
assets/logo-duck.png
ADDED
|
Git LFS Details
|
assets/logo-wordmark.png
ADDED
|
Git LFS Details
|
index.html
CHANGED
|
@@ -281,31 +281,30 @@
|
|
| 281 |
from { transform: translateY(18px) scale(0.94); opacity: 0; }
|
| 282 |
to { transform: none; opacity: 1; }
|
| 283 |
}
|
| 284 |
-
#welcome .
|
| 285 |
-
|
| 286 |
-
height:
|
| 287 |
-
margin: 0 auto
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
justify-content: center;
|
| 291 |
-
font-size: 2rem;
|
| 292 |
-
border-radius: 50%;
|
| 293 |
-
background: rgba(255, 210, 63, 0.12);
|
| 294 |
-
border: 1px solid rgba(255, 210, 63, 0.35);
|
| 295 |
-
animation: badge-bob 3s ease-in-out infinite;
|
| 296 |
}
|
| 297 |
@keyframes badge-bob {
|
| 298 |
-
0%, 100% { transform: translateY(0) rotate(-
|
| 299 |
-
50% { transform: translateY(-6px) rotate(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
}
|
| 301 |
-
#welcome
|
| 302 |
-
|
|
|
|
| 303 |
font-weight: 700;
|
| 304 |
-
letter-spacing: 0.
|
| 305 |
text-transform: uppercase;
|
| 306 |
-
color:
|
| 307 |
}
|
| 308 |
-
#welcome h1 b { color: var(--yellow); font-weight: 700; }
|
| 309 |
#welcome .tagline {
|
| 310 |
margin-top: 0.5rem;
|
| 311 |
font-size: 0.8rem;
|
|
@@ -410,9 +409,10 @@
|
|
| 410 |
<div id="loading">Compiling physics…</div>
|
| 411 |
|
| 412 |
<div id="welcome" hidden>
|
| 413 |
-
<div class="card" role="dialog" aria-modal="true" aria-
|
| 414 |
-
<
|
| 415 |
-
<
|
|
|
|
| 416 |
<p class="tagline">
|
| 417 |
A real robot brain in your browser: MuJoCo physics compiled to
|
| 418 |
WebAssembly, driven by the duck's actual trained neural policies
|
|
@@ -434,6 +434,12 @@
|
|
| 434 |
// or clicking the blurred backdrop. The top-left brand reopens it.
|
| 435 |
(() => {
|
| 436 |
const modal = document.getElementById("welcome");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
const open = () => {
|
| 438 |
modal.hidden = false;
|
| 439 |
modal.classList.remove("closing");
|
|
@@ -464,7 +470,7 @@
|
|
| 464 |
appended: a plain src attribute can 401 when the browser blocks the
|
| 465 |
*.static.hf.space auth cookie inside the hub iframe. -->
|
| 466 |
<script type="module">
|
| 467 |
-
const V = "
|
| 468 |
const sign = new URLSearchParams(location.search).get("__sign");
|
| 469 |
const suffix = sign ? `&__sign=${encodeURIComponent(sign)}` : "";
|
| 470 |
import(`./rl.js?v=${V}${suffix}`);
|
|
|
|
| 281 |
from { transform: translateY(18px) scale(0.94); opacity: 0; }
|
| 282 |
to { transform: none; opacity: 1; }
|
| 283 |
}
|
| 284 |
+
#welcome .logo-duck {
|
| 285 |
+
display: block;
|
| 286 |
+
height: 7.5rem;
|
| 287 |
+
margin: 0 auto 0.4rem;
|
| 288 |
+
filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.45));
|
| 289 |
+
animation: badge-bob 3.2s ease-in-out infinite;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
}
|
| 291 |
@keyframes badge-bob {
|
| 292 |
+
0%, 100% { transform: translateY(0) rotate(-2deg); }
|
| 293 |
+
50% { transform: translateY(-6px) rotate(2deg); }
|
| 294 |
+
}
|
| 295 |
+
#welcome .logo-wordmark {
|
| 296 |
+
display: block;
|
| 297 |
+
width: min(78%, 300px);
|
| 298 |
+
margin: 0 auto;
|
| 299 |
}
|
| 300 |
+
#welcome .subtitle {
|
| 301 |
+
margin-top: 0.55rem;
|
| 302 |
+
font-size: 0.62rem;
|
| 303 |
font-weight: 700;
|
| 304 |
+
letter-spacing: 0.32em;
|
| 305 |
text-transform: uppercase;
|
| 306 |
+
color: rgba(255, 210, 63, 0.75);
|
| 307 |
}
|
|
|
|
| 308 |
#welcome .tagline {
|
| 309 |
margin-top: 0.5rem;
|
| 310 |
font-size: 0.8rem;
|
|
|
|
| 409 |
<div id="loading">Compiling physics…</div>
|
| 410 |
|
| 411 |
<div id="welcome" hidden>
|
| 412 |
+
<div class="card" role="dialog" aria-modal="true" aria-label="Microduck playground">
|
| 413 |
+
<img class="logo-duck" id="logo-duck" alt="" />
|
| 414 |
+
<img class="logo-wordmark" id="logo-wordmark" alt="Microduck" />
|
| 415 |
+
<div class="subtitle">RL playground</div>
|
| 416 |
<p class="tagline">
|
| 417 |
A real robot brain in your browser: MuJoCo physics compiled to
|
| 418 |
WebAssembly, driven by the duck's actual trained neural policies
|
|
|
|
| 434 |
// or clicking the blurred backdrop. The top-left brand reopens it.
|
| 435 |
(() => {
|
| 436 |
const modal = document.getElementById("welcome");
|
| 437 |
+
// Logo images need the same ?__sign JWT as every other subresource
|
| 438 |
+
// on the private Space (rl.js hasn't installed its signer yet).
|
| 439 |
+
const sign = new URLSearchParams(location.search).get("__sign");
|
| 440 |
+
const signedUrl = (u) => (sign ? `${u}?__sign=${encodeURIComponent(sign)}` : u);
|
| 441 |
+
document.getElementById("logo-duck").src = signedUrl("./assets/logo-duck.png");
|
| 442 |
+
document.getElementById("logo-wordmark").src = signedUrl("./assets/logo-wordmark.png");
|
| 443 |
const open = () => {
|
| 444 |
modal.hidden = false;
|
| 445 |
modal.classList.remove("closing");
|
|
|
|
| 470 |
appended: a plain src attribute can 401 when the browser blocks the
|
| 471 |
*.static.hf.space auth cookie inside the hub iframe. -->
|
| 472 |
<script type="module">
|
| 473 |
+
const V = "21"; // bump on rl.js changes to defeat heuristic caching
|
| 474 |
const sign = new URLSearchParams(location.search).get("__sign");
|
| 475 |
const suffix = sign ? `&__sign=${encodeURIComponent(sign)}` : "";
|
| 476 |
import(`./rl.js?v=${V}${suffix}`);
|
rl.js
CHANGED
|
@@ -32,10 +32,14 @@ window.__hfSigned = signed; // duck.js uses it for kinematics + STL requests
|
|
| 32 |
|
| 33 |
// Local modules are imported dynamically through signed() for the same
|
| 34 |
// reason: a static import of ./duck.js would 401 without the cookie.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
const { buildRig, cloneRig, loadKinematics, setJoint, setJawOpen, MODEL_DIR, MESH_VERSION } =
|
| 36 |
-
await import(signed(
|
| 37 |
const { VARIANTS, VARIANT_NAMES, materialHookFor, randomVariantName, applyVariant, specToHex } =
|
| 38 |
-
await import(signed(
|
| 39 |
|
| 40 |
ort.env.wasm.wasmPaths = "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.27.0/dist/";
|
| 41 |
ort.env.wasm.numThreads = 1; // static hosting sends no COOP/COEP headers
|
|
@@ -700,14 +704,16 @@ function syncButtons() {
|
|
| 700 |
}
|
| 701 |
|
| 702 |
// ── Colour swatches: re-skin the rig live, with a quack ─────────────────
|
| 703 |
-
// One representative colour per variant so the dots read at a glance.
|
|
|
|
|
|
|
| 704 |
const SWATCH_SLOT = { classic: "feet", charcoal: "headDome", purple: "feet", blue: "facePlate" };
|
| 705 |
const swatchesEl = document.getElementById("swatches");
|
| 706 |
const swatchBtns = new Map();
|
| 707 |
for (const name of VARIANT_NAMES) {
|
| 708 |
const v = VARIANTS[name];
|
| 709 |
const b = document.createElement("button");
|
| 710 |
-
b.style.background = specToHex(v[SWATCH_SLOT[name] ?? "bodyShell"]);
|
| 711 |
b.setAttribute("aria-label", `${name} colours`);
|
| 712 |
b.addEventListener("click", () => {
|
| 713 |
if (name === currentVariant) return;
|
|
@@ -744,10 +750,7 @@ window.rl = {
|
|
| 744 |
// translucent ducks. Fire-and-forget: any failure just means no ghosts.
|
| 745 |
const r3 = (x) => Math.round(x * 1000) / 1000;
|
| 746 |
try {
|
| 747 |
-
|
| 748 |
-
// Cache-Control: reuse rl.js's own ?v= so ghosts.js updates ship together.
|
| 749 |
-
const selfV = new URL(import.meta.url).searchParams.get("v") ?? "0";
|
| 750 |
-
const { initGhosts } = await import(signed(`./ghosts.js?v=${selfV}`));
|
| 751 |
ghosts = await initGhosts({
|
| 752 |
scene, rig, cloneRig, setJoint, setJawOpen, applyVariant,
|
| 753 |
jointNames: JOINT_NAMES,
|
|
|
|
| 32 |
|
| 33 |
// Local modules are imported dynamically through signed() for the same
|
| 34 |
// reason: a static import of ./duck.js would 401 without the cookie.
|
| 35 |
+
// They also reuse rl.js's own ?v= cache-buster: python http.server (and
|
| 36 |
+
// some CDN edges) send no Cache-Control, so without it browsers can keep
|
| 37 |
+
// serving stale module bytes after a deploy.
|
| 38 |
+
const SELF_V = new URL(import.meta.url).searchParams.get("v") ?? "0";
|
| 39 |
const { buildRig, cloneRig, loadKinematics, setJoint, setJawOpen, MODEL_DIR, MESH_VERSION } =
|
| 40 |
+
await import(signed(`./duck.js?v=${SELF_V}`));
|
| 41 |
const { VARIANTS, VARIANT_NAMES, materialHookFor, randomVariantName, applyVariant, specToHex } =
|
| 42 |
+
await import(signed(`./variants.js?v=${SELF_V}`));
|
| 43 |
|
| 44 |
ort.env.wasm.wasmPaths = "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.27.0/dist/";
|
| 45 |
ort.env.wasm.numThreads = 1; // static hosting sends no COOP/COEP headers
|
|
|
|
| 704 |
}
|
| 705 |
|
| 706 |
// ── Colour swatches: re-skin the rig live, with a quack ─────────────────
|
| 707 |
+
// One representative colour per variant so the dots read at a glance.
|
| 708 |
+
// Variants can force theirs with a `swatch` spec (purple does: its head
|
| 709 |
+
// is warm gray but its identity is the purple accents).
|
| 710 |
const SWATCH_SLOT = { classic: "feet", charcoal: "headDome", purple: "feet", blue: "facePlate" };
|
| 711 |
const swatchesEl = document.getElementById("swatches");
|
| 712 |
const swatchBtns = new Map();
|
| 713 |
for (const name of VARIANT_NAMES) {
|
| 714 |
const v = VARIANTS[name];
|
| 715 |
const b = document.createElement("button");
|
| 716 |
+
b.style.background = specToHex(v.swatch ?? v[SWATCH_SLOT[name] ?? "bodyShell"]);
|
| 717 |
b.setAttribute("aria-label", `${name} colours`);
|
| 718 |
b.addEventListener("click", () => {
|
| 719 |
if (name === currentVariant) return;
|
|
|
|
| 750 |
// translucent ducks. Fire-and-forget: any failure just means no ghosts.
|
| 751 |
const r3 = (x) => Math.round(x * 1000) / 1000;
|
| 752 |
try {
|
| 753 |
+
const { initGhosts } = await import(signed(`./ghosts.js?v=${SELF_V}`));
|
|
|
|
|
|
|
|
|
|
| 754 |
ghosts = await initGhosts({
|
| 755 |
scene, rig, cloneRig, setJoint, setJawOpen, applyVariant,
|
| 756 |
jointNames: JOINT_NAMES,
|
variants.js
CHANGED
|
@@ -37,76 +37,94 @@ const FEET_YELLOW = { color: [0.784, 0.455, 0.023], roughness: 0.5, metalness: 0
|
|
| 37 |
|
| 38 |
// Slots (validated against the four-robot reference photo): headDome
|
| 39 |
// (top shell), facePlate (around the eye), trim (band under the shell +
|
| 40 |
-
// upper beak base), beakUpper (mouth-roof plate), beakLower (
|
| 41 |
-
// eyeRing, lens, bodyShell (trunk
|
| 42 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
export const VARIANTS = {
|
| 44 |
// Front-center robot: warm gray head, cream body, amber eye, orange
|
| 45 |
-
// trim
|
| 46 |
classic: {
|
| 47 |
-
headDome:
|
| 48 |
facePlate: WARM_GRAY,
|
| 49 |
trim: BRIGHT_ORANGE,
|
| 50 |
beakUpper: BRIGHT_ORANGE,
|
| 51 |
-
beakLower:
|
|
|
|
| 52 |
eyeRing: AMBER_YELLOW,
|
| 53 |
lens: LENS,
|
| 54 |
bodyShell: CREAM,
|
|
|
|
| 55 |
legShells: CREAM,
|
| 56 |
-
feet:
|
| 57 |
soles: AMBER_ORANGE,
|
|
|
|
| 58 |
mechDark: DARK,
|
| 59 |
mechGray: GRAY,
|
| 60 |
},
|
| 61 |
// Front-left robot: charcoal shells, slightly lighter face plate,
|
| 62 |
-
// amber eye, orange/amber beak
|
| 63 |
charcoal: {
|
| 64 |
headDome: CHARCOAL,
|
| 65 |
facePlate: CHARCOAL_FACE,
|
| 66 |
trim: BRIGHT_ORANGE,
|
| 67 |
beakUpper: BRIGHT_ORANGE,
|
| 68 |
-
beakLower:
|
|
|
|
| 69 |
eyeRing: AMBER_YELLOW,
|
| 70 |
lens: LENS,
|
| 71 |
bodyShell: CHARCOAL_BODY,
|
|
|
|
| 72 |
legShells: CHARCOAL_BODY,
|
| 73 |
-
feet:
|
| 74 |
-
soles:
|
|
|
|
| 75 |
mechDark: DARK,
|
| 76 |
mechGray: GRAY,
|
| 77 |
},
|
| 78 |
// Back-center robot: light warm gray head, purple eye, YELLOW trim and
|
| 79 |
-
// beak (both plates), cream body
|
| 80 |
purple: {
|
| 81 |
headDome: LIGHT_WARM_GRAY,
|
| 82 |
-
facePlate:
|
| 83 |
trim: CLEAN_YELLOW,
|
| 84 |
beakUpper: CLEAN_YELLOW,
|
| 85 |
beakLower: CLEAN_YELLOW,
|
|
|
|
| 86 |
eyeRing: PURPLE_RING,
|
| 87 |
lens: LENS,
|
| 88 |
bodyShell: CREAM,
|
|
|
|
| 89 |
legShells: CREAM,
|
| 90 |
-
feet:
|
| 91 |
-
soles:
|
|
|
|
| 92 |
mechDark: DARK,
|
| 93 |
mechGray: GRAY,
|
|
|
|
|
|
|
|
|
|
| 94 |
},
|
| 95 |
// Right robot: pale blue dome over a medium blue face plate (two
|
| 96 |
-
// different blues), amber eye, orange/amber beak, blue-gray body
|
| 97 |
-
// clean yellow feet.
|
| 98 |
blue: {
|
| 99 |
headDome: PALE_BLUE,
|
| 100 |
facePlate: MEDIUM_BLUE,
|
| 101 |
trim: BRIGHT_ORANGE,
|
| 102 |
beakUpper: BRIGHT_ORANGE,
|
| 103 |
-
beakLower:
|
|
|
|
| 104 |
eyeRing: AMBER_YELLOW,
|
| 105 |
lens: LENS,
|
| 106 |
-
bodyShell:
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
|
|
|
|
|
|
| 110 |
mechDark: DARK,
|
| 111 |
mechGray: GRAY,
|
| 112 |
},
|
|
@@ -131,17 +149,18 @@ export function meshMaterialsFor(v) {
|
|
| 131 |
"noenoeil.stl": v.eyeRing,
|
| 132 |
"lens.stl": v.lens,
|
| 133 |
"m12_lens_holder.stl": v.mechDark,
|
| 134 |
-
// Beak: mouth-roof plate on top, rigid jaw
|
|
|
|
| 135 |
"soft_mouth_top.stl": v.beakUpper,
|
| 136 |
"jaw.stl": v.beakLower,
|
| 137 |
-
"jaw_soft.stl": v.
|
| 138 |
-
// Body shells
|
| 139 |
"trunk_base.stl": v.bodyShell,
|
| 140 |
-
"left_shell.stl": v.
|
| 141 |
-
"right_shell.stl": v.
|
| 142 |
"upper_leg_left.stl": v.legShells,
|
| 143 |
"upper_leg_right.stl": v.legShells,
|
| 144 |
-
"hip_l.stl": v.
|
| 145 |
// Feet: foot block + ankle bracket, soft sole pad below.
|
| 146 |
"foot_left.stl": v.feet,
|
| 147 |
"foot_right.stl": v.feet,
|
|
@@ -149,8 +168,17 @@ export function meshMaterialsFor(v) {
|
|
| 149 |
"ankle_right.stl": v.feet,
|
| 150 |
"sole_left.stl": v.soles,
|
| 151 |
"sole_right.stl": v.soles,
|
| 152 |
-
// Dark mechanics
|
|
|
|
|
|
|
| 153 |
"xl330.stl": v.mechDark,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
"neck.stl": v.mechDark,
|
| 155 |
"np_f970.stl": v.mechDark,
|
| 156 |
"pcb__raspberry_pi_zero_2_w.stl": v.mechDark,
|
|
@@ -159,15 +187,11 @@ export function meshMaterialsFor(v) {
|
|
| 159 |
"speaker.stl": v.mechDark,
|
| 160 |
// Gray mechanics
|
| 161 |
"upper_leg_rigidity_plate.stl": v.mechGray,
|
| 162 |
-
"leg.stl": v.mechGray,
|
| 163 |
-
"yaw2roll.stl": v.mechGray,
|
| 164 |
"yaw_roll_motion.stl": v.mechGray,
|
| 165 |
"neck_pitch.stl": v.mechGray,
|
| 166 |
-
"bearing_roll.stl": v.mechGray,
|
| 167 |
"motor_support.stl": v.mechGray,
|
| 168 |
"power_support.stl": v.mechGray,
|
| 169 |
"seeed_bearing__configuration__22x16x4.stl": v.mechGray,
|
| 170 |
-
"seeed_bearing__configuration_default.stl": v.mechGray,
|
| 171 |
};
|
| 172 |
}
|
| 173 |
|
|
|
|
| 37 |
|
| 38 |
// Slots (validated against the four-robot reference photo): headDome
|
| 39 |
// (top shell), facePlate (around the eye), trim (band under the shell +
|
| 40 |
+
// upper beak base), beakUpper (mouth-roof plate), beakLower (rigid jaw),
|
| 41 |
+
// tongue (soft pad inside the beak), eyeRing, lens, bodyShell (trunk
|
| 42 |
+
// base), sideShells (left + right trunk shells), legShells (upper-leg
|
| 43 |
+
// shells), feet (shoe upper: foot blocks + ankle brackets), soles (shoe
|
| 44 |
+
// lower: sole pads), hips (hip covers), mechDark, mechGray.
|
| 45 |
+
// The shoe is deliberately identical on all variants: yellow upper
|
| 46 |
+
// (feet), orange lower (soles).
|
| 47 |
export const VARIANTS = {
|
| 48 |
// Front-center robot: warm gray head, cream body, amber eye, orange
|
| 49 |
+
// trim and beak.
|
| 50 |
classic: {
|
| 51 |
+
headDome: CREAM,
|
| 52 |
facePlate: WARM_GRAY,
|
| 53 |
trim: BRIGHT_ORANGE,
|
| 54 |
beakUpper: BRIGHT_ORANGE,
|
| 55 |
+
beakLower: BRIGHT_ORANGE,
|
| 56 |
+
tongue: AMBER_YELLOW,
|
| 57 |
eyeRing: AMBER_YELLOW,
|
| 58 |
lens: LENS,
|
| 59 |
bodyShell: CREAM,
|
| 60 |
+
sideShells: CREAM,
|
| 61 |
legShells: CREAM,
|
| 62 |
+
feet: BRIGHT_ORANGE,
|
| 63 |
soles: AMBER_ORANGE,
|
| 64 |
+
hips: GRAY,
|
| 65 |
mechDark: DARK,
|
| 66 |
mechGray: GRAY,
|
| 67 |
},
|
| 68 |
// Front-left robot: charcoal shells, slightly lighter face plate,
|
| 69 |
+
// amber eye, orange/amber beak.
|
| 70 |
charcoal: {
|
| 71 |
headDome: CHARCOAL,
|
| 72 |
facePlate: CHARCOAL_FACE,
|
| 73 |
trim: BRIGHT_ORANGE,
|
| 74 |
beakUpper: BRIGHT_ORANGE,
|
| 75 |
+
beakLower: BRIGHT_ORANGE,
|
| 76 |
+
tongue: AMBER_YELLOW,
|
| 77 |
eyeRing: AMBER_YELLOW,
|
| 78 |
lens: LENS,
|
| 79 |
bodyShell: CHARCOAL_BODY,
|
| 80 |
+
sideShells: CHARCOAL_BODY,
|
| 81 |
legShells: CHARCOAL_BODY,
|
| 82 |
+
feet: BRIGHT_ORANGE,
|
| 83 |
+
soles: AMBER_ORANGE,
|
| 84 |
+
hips: GRAY,
|
| 85 |
mechDark: DARK,
|
| 86 |
mechGray: GRAY,
|
| 87 |
},
|
| 88 |
// Back-center robot: light warm gray head, purple eye, YELLOW trim and
|
| 89 |
+
// beak (both plates), cream body.
|
| 90 |
purple: {
|
| 91 |
headDome: LIGHT_WARM_GRAY,
|
| 92 |
+
facePlate: WARM_GRAY,
|
| 93 |
trim: CLEAN_YELLOW,
|
| 94 |
beakUpper: CLEAN_YELLOW,
|
| 95 |
beakLower: CLEAN_YELLOW,
|
| 96 |
+
tongue: AMBER_YELLOW,
|
| 97 |
eyeRing: PURPLE_RING,
|
| 98 |
lens: LENS,
|
| 99 |
bodyShell: CREAM,
|
| 100 |
+
sideShells: CREAM,
|
| 101 |
legShells: CREAM,
|
| 102 |
+
feet: CLEAN_YELLOW,
|
| 103 |
+
soles: PURPLE_RING,
|
| 104 |
+
hips: GRAY,
|
| 105 |
mechDark: DARK,
|
| 106 |
mechGray: GRAY,
|
| 107 |
+
// UI swatch override: the head is warm gray, but this colourway's
|
| 108 |
+
// identity is its purple accents - the picker dot shows that.
|
| 109 |
+
swatch: PURPLE_FEET,
|
| 110 |
},
|
| 111 |
// Right robot: pale blue dome over a medium blue face plate (two
|
| 112 |
+
// different blues), amber eye, orange/amber beak, blue-gray body.
|
|
|
|
| 113 |
blue: {
|
| 114 |
headDome: PALE_BLUE,
|
| 115 |
facePlate: MEDIUM_BLUE,
|
| 116 |
trim: BRIGHT_ORANGE,
|
| 117 |
beakUpper: BRIGHT_ORANGE,
|
| 118 |
+
beakLower: BRIGHT_ORANGE,
|
| 119 |
+
tongue: AMBER_YELLOW,
|
| 120 |
eyeRing: AMBER_YELLOW,
|
| 121 |
lens: LENS,
|
| 122 |
+
bodyShell: PALE_BLUE,
|
| 123 |
+
sideShells: PALE_BLUE,
|
| 124 |
+
legShells: PALE_BLUE,
|
| 125 |
+
feet: BRIGHT_ORANGE,
|
| 126 |
+
soles: AMBER_ORANGE,
|
| 127 |
+
hips: MEDIUM_BLUE,
|
| 128 |
mechDark: DARK,
|
| 129 |
mechGray: GRAY,
|
| 130 |
},
|
|
|
|
| 149 |
"noenoeil.stl": v.eyeRing,
|
| 150 |
"lens.stl": v.lens,
|
| 151 |
"m12_lens_holder.stl": v.mechDark,
|
| 152 |
+
// Beak: mouth-roof plate on top, rigid jaw below, soft pad (the
|
| 153 |
+
// tongue) riding on the jaw.
|
| 154 |
"soft_mouth_top.stl": v.beakUpper,
|
| 155 |
"jaw.stl": v.beakLower,
|
| 156 |
+
"jaw_soft.stl": v.tongue,
|
| 157 |
+
// Body: trunk base + the two side shells (shared slot).
|
| 158 |
"trunk_base.stl": v.bodyShell,
|
| 159 |
+
"left_shell.stl": v.sideShells,
|
| 160 |
+
"right_shell.stl": v.sideShells,
|
| 161 |
"upper_leg_left.stl": v.legShells,
|
| 162 |
"upper_leg_right.stl": v.legShells,
|
| 163 |
+
"hip_l.stl": v.hips,
|
| 164 |
// Feet: foot block + ankle bracket, soft sole pad below.
|
| 165 |
"foot_left.stl": v.feet,
|
| 166 |
"foot_right.stl": v.feet,
|
|
|
|
| 168 |
"ankle_right.stl": v.feet,
|
| 169 |
"sole_left.stl": v.soles,
|
| 170 |
"sole_right.stl": v.soles,
|
| 171 |
+
// Dark mechanics. leg.stl is the printed shin that wraps the ankle
|
| 172 |
+
// motor and its bearing ring: in gray they read as two gray motors,
|
| 173 |
+
// so both go dark with the motor they hold.
|
| 174 |
"xl330.stl": v.mechDark,
|
| 175 |
+
"leg.stl": v.mechDark,
|
| 176 |
+
"seeed_bearing__configuration_default.stl": v.mechDark,
|
| 177 |
+
// Hip yaw mechanism (ribbed ring + 4-screw plate): reads as the hip
|
| 178 |
+
// motor, so it goes dark too. The printed hip cover (hip_l.stl)
|
| 179 |
+
// keeps the dedicated `hips` slot.
|
| 180 |
+
"yaw2roll.stl": v.mechDark,
|
| 181 |
+
"bearing_roll.stl": v.mechDark,
|
| 182 |
"neck.stl": v.mechDark,
|
| 183 |
"np_f970.stl": v.mechDark,
|
| 184 |
"pcb__raspberry_pi_zero_2_w.stl": v.mechDark,
|
|
|
|
| 187 |
"speaker.stl": v.mechDark,
|
| 188 |
// Gray mechanics
|
| 189 |
"upper_leg_rigidity_plate.stl": v.mechGray,
|
|
|
|
|
|
|
| 190 |
"yaw_roll_motion.stl": v.mechGray,
|
| 191 |
"neck_pitch.stl": v.mechGray,
|
|
|
|
| 192 |
"motor_support.stl": v.mechGray,
|
| 193 |
"power_support.stl": v.mechGray,
|
| 194 |
"seeed_bearing__configuration__22x16x4.stl": v.mechGray,
|
|
|
|
| 195 |
};
|
| 196 |
}
|
| 197 |
|