Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ <h2>Which treadmills work</h2>

<h2>More</h2>
<ul>
<li><a href="/what-is-belt-control">What is Belt Control?</a> — what it does, who it's
for, and what you need</li>
<li><a href="/compatible-treadmills">Will it work with my treadmill?</a> — every model,
and what each one can show you</li>
<li><a href="/walkingpad-without-the-app">Using a WalkingPad without the KS+Fit app</a></li>
Expand Down
1 change: 1 addition & 0 deletions apps/web/public/compatible-treadmills/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ <h2>If your pad isn't listed</h2>
<footer>
<ul>
<li><a href="/">The app</a></li>
<li><a href="/what-is-belt-control">What is Belt Control?</a></li>
<li><a href="/walkingpad-without-the-app">Without the KS+Fit app</a></li>
<li><a href="/troubleshooting">Troubleshooting</a></li>
<li><a href="/walkingpad-bluetooth-protocol">How it works, technically</a></li>
Expand Down
6 changes: 6 additions & 0 deletions apps/web/public/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://beltcontrol.com/what-is-belt-control</loc>
<lastmod>__LASTMOD__</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://beltcontrol.com/compatible-treadmills</loc>
<lastmod>__LASTMOD__</lastmod>
Expand Down
1 change: 1 addition & 0 deletions apps/web/public/troubleshooting/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ <h2>It worked yesterday. Why do I have to pick my pad again?</h2>
<footer>
<ul>
<li><a href="/">The app</a></li>
<li><a href="/what-is-belt-control">What is Belt Control?</a></li>
<li><a href="/compatible-treadmills">Compatible treadmills</a></li>
<li><a href="/walkingpad-without-the-app">Without the KS+Fit app</a></li>
<li><a href="/walkingpad-bluetooth-protocol">How it works, technically</a></li>
Expand Down
1 change: 1 addition & 0 deletions apps/web/public/walkingpad-bluetooth-protocol/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ <h2>The full reference</h2>
<footer>
<ul>
<li><a href="/">The app</a></li>
<li><a href="/what-is-belt-control">What is Belt Control?</a></li>
<li><a href="/compatible-treadmills">Compatible treadmills</a></li>
<li><a href="/walkingpad-without-the-app">Without the KS+Fit app</a></li>
<li><a href="/troubleshooting">Troubleshooting</a></li>
Expand Down
1 change: 1 addition & 0 deletions apps/web/public/walkingpad-without-the-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ <h2>Does it need an internet connection?</h2>
<footer>
<ul>
<li><a href="/">The app</a></li>
<li><a href="/what-is-belt-control">What is Belt Control?</a></li>
<li><a href="/compatible-treadmills">Compatible treadmills</a></li>
<li><a href="/troubleshooting">Troubleshooting</a></li>
<li><a href="/walkingpad-bluetooth-protocol">How it works, technically</a></li>
Expand Down
131 changes: 131 additions & 0 deletions apps/web/public/what-is-belt-control/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>What Is Belt Control? A Browser Remote for Your Walking Pad</title>
<meta name="description" content="Belt Control is a free web app that starts, stops and sets the speed of a KingSmith or WalkingPad treadmill over Bluetooth, and keeps a private record of every walk. Here's what it does and who it's for.">
<link rel="canonical" href="https://beltcontrol.com/what-is-belt-control">
<meta name="robots" content="index, follow, max-image-preview:large">
<meta property="og:type" content="article">
<meta property="og:site_name" content="Belt Control">
<meta property="og:url" content="https://beltcontrol.com/what-is-belt-control">
<meta property="og:title" content="What is Belt Control?">
<meta property="og:description" content="A free web app that controls a KingSmith or WalkingPad treadmill over Bluetooth and keeps a private record of every walk.">
<meta property="og:image" content="https://beltcontrol.com/og.png">
<meta name="twitter:card" content="summary_large_image">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="stylesheet" href="/content.css">
<meta name="theme-color" content="#f6f7f9" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#0b0e11" media="(prefers-color-scheme: dark)">
</head>
<body>
<div class="page">

<header class="masthead">
<a class="brand" href="/">Belt Control <span>— treadmill control in the browser</span></a>
</header>

<main>
<h1>What is Belt Control?</h1>
<p class="lede">
Belt Control is a remote control for a walking pad that runs in a browser tab. It
connects to a KingSmith or WalkingPad treadmill over Bluetooth, lets you start it, stop
it and set the speed, and keeps a record of every walk. It's free, and you don't need an
account, the phone app or anything else installed.
</p>

<h2>What it's for</h2>
<p>
Walking pads are mostly used under a standing desk: you walk slowly for an hour while you
work. The remote that comes in the box can start and stop the belt, but it doesn't
remember anything. The manufacturer's phone app does, but it means reaching for your
phone, signing in, and keeping that phone next to the treadmill.
</p>
<p>
Belt Control puts the controls in the place you're already looking, the computer on the
desk. It's built for that:
</p>
<ul>
<li><strong>Controls at a glance.</strong> Start, stop, pause on pads that support it, and
one-tap speed presets for the two or three speeds you actually walk at.</li>
<li><strong>Live numbers as you walk.</strong> Time, distance, steps and calories, as far
as your treadmill reports them. Numbers a pad sends but can't be trusted are shown as
such and left out of your totals.</li>
<li><strong>Every walk recorded automatically.</strong> Daily totals, a goal you set, a
30-day history and a streak, without pressing record.</li>
<li><strong>Your data stays yours.</strong> Your walks are saved in your own browser and
never uploaded. You can export them as a backup or as a spreadsheet whenever you
like.</li>
</ul>

<h2>How it works</h2>
<p>
Chrome, Edge, Opera and Samsung Internet can talk to Bluetooth devices directly from a
web page. Belt Control uses that to talk to the treadmill the same way the phone app does.
When you press <strong>Connect</strong>, your browser shows a list of nearby treadmills.
Pick yours, and Belt Control asks it what it can do and works from there, so it doesn't
need to know your model in advance.
</p>
<p>
The connection goes straight from your browser to the treadmill over the radio. The
website only delivers the page itself. It never sees your walks or anything about your
treadmill. Once it has loaded, it works offline too.
</p>

<h2>What you need</h2>
<ul>
<li><strong>A KingSmith or WalkingPad treadmill</strong>, or a pad that speaks the same
language. Most models from the last several years work — see
<a href="/compatible-treadmills">the full list</a>.</li>
<li><strong>Chrome, Edge, Opera or Samsung Internet</strong>, on a computer or an Android
device. Firefox and Safari can't connect to Bluetooth devices, and on an iPhone or iPad
every browser is Safari underneath.</li>
<li><strong>The phone app closed.</strong> These treadmills only talk to one device at a
time.</li>
</ul>

<div class="cta">
<p><strong>Try it with your treadmill.</strong> Switch the pad on, open the app and press
Connect. Nothing to install and no account to create.</p>
<p><a class="btn" href="/">Open Belt Control</a></p>
</div>

<h2>Is it safe?</h2>
<p>
Belt Control runs a motorised treadmill, so treat it like the remote, not like a safety
device. Only start the belt when you can see it, and use the treadmill's own controls or
safety key to stop in a hurry: a Bluetooth connection can drop while the belt keeps
running. While the belt is moving, a Stop button stays on screen everywhere in the app and
the Esc key stops it.
</p>

<h2>Who makes it?</h2>
<p>
Belt Control is an independent, open-source project. It isn't made by KingSmith and isn't
affiliated with them. The <a href="https://github.com/morsela/beltcontrol">source code is
on GitHub</a>, and the app has a <strong>Send feedback</strong> link at the bottom of every
screen.
</p>

</main>

<footer>
<ul>
<li><a href="/">The app</a></li>
<li><a href="/compatible-treadmills">Compatible treadmills</a></li>
<li><a href="/walkingpad-without-the-app">Without the KS+Fit app</a></li>
<li><a href="/troubleshooting">Troubleshooting</a></li>
<li><a href="/walkingpad-bluetooth-protocol">How it works, technically</a></li>
<li><a href="https://github.com/morsela/beltcontrol">GitHub</a></li>
</ul>
<p class="fine">
Belt Control is an independent project, not affiliated with, endorsed by, or sponsored
by Beijing KingSmith Technology Co., Ltd. WalkingPad® and KingSmith® are trademarks of
that company, used here only to identify compatible treadmills.
</p>
</footer>

</div>
</body>
</html>
3 changes: 3 additions & 0 deletions apps/web/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AmbientView } from './components/AmbientView.js';
import { DesktopOnlyNotice } from './components/DesktopOnlyNotice.js';
import { Recovery } from './components/Recovery.js';
import { Disclaimer } from './components/Disclaimer.js';
import { WelcomeSheet } from './components/WelcomeSheet.js';
import { beltMayBeMoving, connected } from './state/connection.js';
import { isDesktop } from './lib/viewport.js';
import { trackEvent } from './lib/analytics.js';
Expand Down Expand Up @@ -106,6 +107,7 @@ export function App() {
{r !== 'legal' && <Disclaimer />}
</div>
</main>
<WelcomeSheet />
<Analytics />
</>
);
Expand All @@ -124,6 +126,7 @@ export function App() {

{showStop && <StopBar />}
<TabBar route={r} />
<WelcomeSheet />
<Analytics />
</>
);
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/components/Disclaimer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FeedbackLink } from './FeedbackSheet.js';
import { WelcomeLink } from './WelcomeSheet.js';

/**
* Sits at the bottom of every screen, not behind an About tab.
Expand All @@ -25,6 +26,9 @@ import { FeedbackLink } from './FeedbackSheet.js';
* anchor because it opens a dialog rather than a page — there is no hash for it to
* put in front of the router. Reports about the connection have a second, closer
* entry in the connection sheet, beside the log they need.
*
* "What is Belt Control?" is the way back to the welcome sheet a first visit opens on
* its own. It leads the row because it is the one a newcomer is looking for.
*/
export function Disclaimer() {
return (
Expand All @@ -42,6 +46,7 @@ export function Disclaimer() {
treadmills this app works with.
</p>
<p class="disclaimer-links">
<WelcomeLink />
<a href="#/legal">Safety, terms &amp; privacy</a>
<FeedbackLink />
</p>
Expand Down
112 changes: 112 additions & 0 deletions apps/web/src/components/WelcomeSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { signal } from '@preact/signals';
import { useEffect } from 'preact/hooks';
import { Sheet } from './Sheet.js';
import { sessions } from '../state/session.js';
import { STORAGE_KEYS, readRaw, writeRaw } from '../lib/storage.js';
import { trackEvent } from '../lib/analytics.js';

/** The written page this sheet summarises. A real path rather than a hash route, so
* the long version is something a crawler can read too — see docs/deploying.md. */
export const ABOUT_PATH = '/what-is-belt-control';

// Shown on its own once, on a first visit. A browser that already holds walks belongs
// to somebody who knows what this is, which covers everyone who used the app before
// the sheet existed. Unreadable storage reads as "not seen", the same safe direction
// DesktopOnlyNotice takes: the cost is seeing a short explanation twice.
const firstVisit = readRaw(STORAGE_KEYS.welcome) !== '1' && sessions.value.length === 0;

/** Open the sheet. Exported so the footer can bring it back on any screen. */
export const welcomeOpen = signal(firstVisit);

// The app mounts this in either layout, and crossing the desktop breakpoint swaps one
// for the other — so "first" is reported once per load, not once per mount.
let firstReported = false;

// Private mode may refuse the write, and then it simply comes back next load.
const markSeen = () => writeRaw(STORAGE_KEYS.welcome, '1');

function close() {
welcomeOpen.value = false;
markSeen();
}

/**
* What Belt Control is, for someone who has just landed on a page full of controls for
* a machine that is not connected yet.
*
* Deliberately short: three things it does, the one thing it needs, and the one safety
* rule. Everything longer lives on the written page it links to, which is also the
* copy search engines index — the hash-routed app has no URL of its own to give them.
*
* Rendered once at the top of the app rather than by the footer that reopens it, so a
* first visit that lands on the legal page (which has no footer) still gets it.
*/
export function WelcomeSheet() {
// From a mount effect rather than at module load: ES imports run before main.tsx
// installs the analytics provider, so an event fired at import goes nowhere.
useEffect(() => {
if (!firstVisit || firstReported) return;
firstReported = true;
trackEvent('welcome_shown', { first: true });
}, []);

if (!welcomeOpen.value) return null;

return (
<Sheet title="Welcome to Belt Control" onClose={close}>
<p class="welcome-lede">
A remote control for your walking pad, in a browser tab. It connects to a KingSmith
or WalkingPad treadmill over Bluetooth — no phone app, no account, nothing to
install.
</p>

<ul class="welcome-list">
<li>
<strong>Control the belt.</strong> Start, stop and set the speed from the computer
you're already working at.
</li>
<li>
<strong>See it as you walk.</strong> Time, distance, steps and calories, as far as
your treadmill reports them.
</li>
<li>
<strong>Keep a record.</strong> Every walk is saved automatically, with daily
goals and a 30-day history. It stays in this browser and is never uploaded.
</li>
</ul>

<p class="note">
You'll need Chrome, Edge or Opera on a computer, or Chrome on Android, and the
phone app closed — the treadmill talks to one device at a time. Only start the belt
when you can see it, and keep the treadmill's own stop within reach.
</p>

<div class="data-actions" style="margin-top:1rem">
<button class="btn primary" onClick={close}>
Get started
</button>
{/* Leaving for the long version counts as having seen it, so coming back
from that page does not open the short one on top of the app again. */}
<a class="btn" href={ABOUT_PATH} onClick={markSeen}>
Read more
</a>
</div>
</Sheet>
);
}

/** The footer's way back to the sheet. A button, not an anchor: it opens a dialog
* rather than a page, the same reasoning as `FeedbackLink` beside it. */
export function WelcomeLink() {
return (
<button
class="table-toggle"
onClick={() => {
trackEvent('welcome_shown', { first: false });
welcomeOpen.value = true;
}}
>
What is Belt Control?
</button>
);
}
4 changes: 4 additions & 0 deletions apps/web/src/lib/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export interface AnalyticsEvents {
* ordinary page view; this covers movement the URL bar alone cannot show. */
route_viewed: { route: string };
ambient_entered: undefined;
/** The welcome sheet came up. `first` separates the automatic first-visit showing
* from someone reopening it from the footer — the second is the signal that it is
* worth keeping a way back to. */
welcome_shown: { first: boolean };

// --- connection funnel: attempted → connected, or cancelled/failed ---------
connect_attempted: { filtered: boolean };
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/lib/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const STORAGE_KEYS = {
openSession: 'wp.session.open.v1',
settings: 'wp.settings.v1',
desktopNotice: 'wp.desktopNotice.dismissed.v1',
welcome: 'wp.welcome.seen.v1',
} as const;

export type StorageKey = (typeof STORAGE_KEYS)[keyof typeof STORAGE_KEYS];
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,11 @@ a { color: var(--accent); }
}
.disclaimer-links .table-toggle { font-size: inherit; }

/* The welcome sheet: the one dialog that is prose rather than a form. */
.welcome-lede { margin: 0 0 0.75rem; }
.welcome-list { margin: 0 0 0.75rem; padding-left: 1.1rem; }
.welcome-list li { margin-bottom: 0.4rem; }

/* --- legal page ------------------------------------------------------------
Long-form prose, which exists nowhere else in this app — every other screen is
numbers and controls. Measure is capped well under the desktop content width:
Expand Down
Loading
Loading