Howdy_Friend / index.html
RickDakan's picture
Look button: head tilt at rest, looking up or straight ahead
68dec37 verified
Raw History Blame Contribute Delete
6.6 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>Howdy Friend</title>
<link rel="icon" href="/public/icon.svg" type="image/svg+xml" />
<!-- Node global shim: the SDK host bundle references process.env.NODE_ENV and we load it unbundled from a CDN. -->
<script>
window.process = window.process || { env: { NODE_ENV: "production" } };
window.global = window.global || window;
</script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&display=swap" rel="stylesheet" />
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/@pollen-robotics/reachy-mini-sdk@1.8.0/+esm" />
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/@pollen-robotics/reachy-mini-sdk@1.8.0/host/dist/entry/auto.js" />
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/@pollen-robotics/reachy-mini-sdk@1.8.0/host/dist/entry/embed.js" />
<!-- Theme bootstrap (host passes ?theme=dark|light when iframing us) -->
<script>
(function () {
try {
var params = new URLSearchParams(window.location.search);
var raw = params.get("theme");
var mode;
if (raw === "dark" || raw === "light") mode = raw;
else if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches === false) mode = "light";
else mode = "dark";
document.documentElement.setAttribute("data-theme", mode);
} catch (_) {
document.documentElement.setAttribute("data-theme", "dark");
}
})();
</script>
<!-- HF Spaces OAuth placeholders. Substituted by HF at serve time because hf_oauth: true. -->
<script>
(function () {
var clientId = "__OAUTH_CLIENT_ID__";
var scopes = "__OAUTH_SCOPES__";
var spaceHost = "__SPACE_HOST__";
var spaceId = "__SPACE_ID__";
var looksSubstituted = clientId && clientId.indexOf("__") !== 0;
if (looksSubstituted) {
window.huggingface = window.huggingface || {};
window.huggingface.variables = {
OAUTH_CLIENT_ID: clientId,
OAUTH_SCOPES: scopes && scopes.indexOf("__") !== 0 ? scopes : "openid profile",
SPACE_HOST: spaceHost && spaceHost.indexOf("__") !== 0 ? spaceHost : "",
SPACE_ID: spaceId && spaceId.indexOf("__") !== 0 ? spaceId : "",
};
}
})();
</script>
<style>
:root {
--bg: #101214;
--fg: #f3f1ea;
--muted: #9aa0a6;
--accent: #f2c94c;
--ok: #2bb673;
--panel: #1a1d20;
}
[data-theme="light"] {
--bg: #f7f5ef;
--fg: #16181a;
--muted: #5d6368;
--panel: #ffffff;
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--fg);
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
#root { display: none; }
#app { display: none; min-height: 100dvh; flex-direction: column; padding: 0.75rem;
padding-bottom: calc(0.75rem + env(safe-area-inset-bottom)); gap: 0.75rem; }
body.embed #app { display: flex; }
body.host #root { display: block; }
.stage { position: relative; width: 100%; aspect-ratio: 4 / 3; background: #000;
border-radius: 12px; overflow: hidden; }
.stage video, .stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.stage canvas { pointer-events: none; }
.status { display: flex; align-items: center; gap: 0.6rem; font-size: 1.05rem; min-height: 1.6rem; }
.status .dot { width: 0.7rem; height: 0.7rem; border-radius: 50%; background: var(--muted); flex: none;
transition: background 0.2s; }
.status.tracking .dot { background: var(--accent); }
.status.reacting .dot { background: var(--ok); }
.status small { color: var(--muted); }
.controls { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; }
button { min-height: 48px; border: 1px solid transparent; border-radius: 10px; background: var(--panel);
color: var(--fg); font: inherit; font-size: 1rem; padding: 0.6rem 0.9rem; cursor: pointer; }
button[aria-pressed="true"] { border-color: var(--accent); }
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
button.wide { grid-column: 1 / -1; }
.log { font-size: 0.8rem; color: var(--muted); white-space: pre-wrap; max-height: 14rem; overflow: auto; }
/* Big-screen mode: camera + overlay fill the page, everything else hidden */
body.big #app { padding: 0; gap: 0; }
body.big .stage { position: fixed; inset: 0; width: 100vw; height: 100vh; aspect-ratio: auto; border-radius: 0; z-index: 10; }
body.big .controls, body.big .log { display: none; }
body.big .status { position: fixed; left: 0; right: 0; bottom: 0; z-index: 11; padding: 0.5rem 1rem; font-size: 1.2rem;
background: linear-gradient(transparent, rgba(0,0,0,0.55)); color: #fff; }
body.big .status small { color: #ddd; }
.stage:fullscreen { border-radius: 0; }
.stage:fullscreen video, .stage:fullscreen canvas { object-fit: contain; }
@media (prefers-reduced-motion: reduce) { .status .dot { transition: none; } }
</style>
</head>
<body>
<div id="root"></div>
<main id="app">
<div class="stage">
<video id="video" autoplay playsinline muted></video>
<canvas id="overlay"></canvas>
</div>
<div id="status" class="status">
<span class="dot"></span>
<span id="statusText">Loading vision models</span>
<small id="statusSub"></small>
</div>
<div class="controls">
<button id="btnTrack" aria-pressed="true">Tracking on</button>
<button id="btnBody" aria-pressed="true">Body turns</button>
<button id="btnMute" aria-pressed="false">Sound on</button>
<button id="btnWave">Test wave reaction</button>
<button id="btnWake">Wake Reachy</button>
<button id="btnBig">Big screen</button>
<button id="btnLook" aria-pressed="true">Look: up</button>
<button id="btnVoice" aria-pressed="false">Voice: off</button>
</div>
<div id="log" class="log"></div>
</main>
<script type="module" src="/app.js"></script>
</body>
</html>