{switches.state?.settings.bindings.length ?? 0} configured. Open Switches to assign Select, then open Scanning to adjust movement.
Optional mobile connection
{bluetoothLabels[state.bluetooth]}. Open Mobile connection for pairing and forwarding profiles.
> :
Bluetooth connection
{bluetoothLabels[state.bluetooth]}
-
Input access
{state.accessibility === "required" ? : }
+
Input access
{state.accessibility === "required" ? : }
Application update
Switchify PC {state.version}
Diagnostics
Export sanitized health, capability, and recent event data
Recent Bluetooth changes
{state.diagnostics.recentBluetooth.length > 0 ? state.diagnostics.recentBluetooth.map((event) => event.status).join(" → ") : "No Bluetooth changes recorded yet"}
Focus the application you want to use, then press and release Select to start scanning. Select chooses a point and opens the action menu.
Automatic scanning moves the highlight for you. Use Auto scan rate for grid and menu timing, and Line speed for scanning lines. Holding a switch freezes movement; release runs the action shown.
Escape disables switch control. Assign Stop scanning or Pause / resume in Switches. Holding any switch for {switches.state ? switches.state.escapeHoldMs / 1000 : 4}s disables switch control.
Open Scanning after setup to adjust movement. Mobile connection is optional.
}
- {step === 3 &&
Choose startup behavior
Switchify can start quietly when you sign in, ready for your switches.
}
+ {step === 2 &&
Use your switches
Focus the application you want to use, then press and release Select to start scanning. Select chooses a point and opens the action menu.
Automatic scanning moves the highlight for you. Use Auto scan rate for grid and menu timing, and Line speed for scanning lines. Holding a switch freezes movement; release runs the action shown.
Escape disables switch control. Assign Stop scanning or Pause / resume in Switches. Holding any switch for {switches.state ? switches.state.escapeHoldMs / 1000 : 4}s disables switch control.
Open Scanning after setup to adjust movement. Mobile connection is optional.
}
+ {step === 3 &&
Choose startup behavior
Switchify can start quietly when you sign in, ready for your switches.
}
{step === 4 &&
Choose whether to share diagnostics
Optional anonymous app health and sanitized errors help improve Switchify. Typed text, commands, pairing secrets, device names, and full paths are never included.
;
+}
+
+export function SwitchDemonstrations() {
+ return <>>;
+}
diff --git a/src/help/PrototypeArtwork.tsx b/src/help/PrototypeArtwork.tsx
new file mode 100644
index 00000000..09984aea
--- /dev/null
+++ b/src/help/PrototypeArtwork.tsx
@@ -0,0 +1,212 @@
+// SVG artwork adapted from the local switch-interface-animation prototypes.
+import { useId } from "react";
+import type { DemonstrationKind, DemoPlatform } from "./demonstrations";
+
+export function PrototypeArtwork({ kind, step, platform }: { kind: DemonstrationKind; step: number; platform: DemoPlatform }) {
+ const prefix = useId();
+ if (kind === "jack") return (
+
+ );
+ if (kind === "usb") return (
+
+ );
+ if (kind === "learn") return (
+
+ );
+ if (kind === "select") return (
+
+ );
+ if (kind === "access") return (
+
+ );
+ if (kind === "pair") return (
+
+ );
+ if (kind === "startup") return (
+
+ );
+ return null;
+}
diff --git a/src/help/TeachingArtwork.tsx b/src/help/TeachingArtwork.tsx
new file mode 100644
index 00000000..574614cd
--- /dev/null
+++ b/src/help/TeachingArtwork.tsx
@@ -0,0 +1,39 @@
+import type { DemonstrationKind } from "./demonstrations";
+
+function Switch({ down, x, y }: { down: boolean; x: number; y: number }) {
+ return ;
+}
+
+export function TeachingArtwork({ kind, step, progress }: { kind: DemonstrationKind; step: number; progress: number }) {
+ if (kind === "hold") return ;
+
+ const grid = kind === "grid";
+ const phase = grid ? step : step === 0 ? 0 : step + 2;
+ const selected = progress < 0.75 ? Math.min(2, Math.floor(progress * 4)) : 1;
+ const column = phase === 2 ? selected : 1;
+ const row = phase === 1 ? selected : 1;
+ const travel = Math.min(1, progress / 0.8);
+ const lineX = phase === 3 ? (grid ? 187 : 40) + travel * (grid ? 103 : 250) : 290;
+ const lineY = phase === 4 ? (grid ? 143 : 50) + travel * (grid ? 37 : 130) : 180;
+ return ;
+}
diff --git a/src/help/demonstrations.css b/src/help/demonstrations.css
new file mode 100644
index 00000000..34211840
--- /dev/null
+++ b/src/help/demonstrations.css
@@ -0,0 +1,16 @@
+.teaching-disclosure { margin: 12px 0; }
+.teaching-disclosure > button { text-align: left; white-space: normal; }
+.teaching-demo { margin-top: 12px; padding: 16px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); max-width: 760px; }
+.teaching-art { width: 100%; margin: 12px 0; }
+.teaching-art svg { display: block; width: 100%; height: auto; font-family: inherit; }
+.teaching-art svg text { fill: var(--text); }
+.teaching-art .prototype-artwork text { fill: #1c1b1f; }
+.teaching-art svg text[fill="#fff"] { fill: #fff; }
+.teaching-art svg g, .teaching-art svg circle, .teaching-art svg rect { transition: transform 500ms ease, opacity 500ms ease, cy 500ms ease, cx 500ms ease, fill 500ms ease; }
+.teaching-caption { line-height: 1.5; }
+.teaching-controls { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; }
+.teaching-steps { padding-left: 24px; font-size: 13px; line-height: 1.6; }
+.teaching-steps li { margin: 8px 0; }
+.teaching-steps li[aria-current="step"] { font-weight: 600; }
+.teaching-demo[data-moving="false"] svg *, .teaching-demo[data-reduced="true"] svg * { transition: none; }
+@media (prefers-reduced-motion: reduce) { .teaching-art svg * { transition: none; } }
diff --git a/src/help/demonstrations.ts b/src/help/demonstrations.ts
new file mode 100644
index 00000000..ae7ab1de
--- /dev/null
+++ b/src/help/demonstrations.ts
@@ -0,0 +1,19 @@
+export type DemonstrationKind = "jack" | "usb" | "learn" | "select" | "grid" | "line" | "hold" | "access" | "pair" | "startup";
+export type DemoPlatform = "windows" | "macos" | "linux";
+
+export function demonstration(kind: DemonstrationKind, platform: DemoPlatform) {
+ const location = platform === "macos" ? "menu bar" : "system tray";
+ const examples = {
+ jack: { title: "Connect a jack switch", steps: ["Check which connector and socket your switch interface requires. This illustration uses socket 1 as an example.", "Insert the compatible switch plug fully into the interface.", "Connect the interface to your computer, then use Add switch to learn the key it sends."] },
+ usb: { title: "Connect a USB switch", steps: ["A USB switch or keyboard interface sends a key when you press the switch.", "Connect it to a compatible USB port on your computer.", "Choose Add switch and learn its key. Connecting the cable alone does not configure Switchify."] },
+ learn: { title: "Learn a switch key", steps: ["Choose Add switch, then Learn key to open the capture dialog.", "Press and release your physical switch once.", "The key it sends appears in the editor. F13 is only an example; your switch may send a different key."] },
+ select: { title: "Assign Select", steps: ["After learning a key, open its Press and release action list.", "Choose Select so a short press and release chooses the highlighted item.", "Save the switch. Automatic scanning needs Select; manual scanning also needs Next and Previous."] },
+ grid: { title: "Automatic grid then line scanning", steps: ["Focus the application you want to control. Press and release Select to start automatic scanning.", "Rows highlight in turn. Press and release Select to choose a row.", "Cells in that row highlight in turn. Press and release Select to choose a cell.", "Choose the horizontal position with Select as the vertical line moves across the cell.", "Choose the vertical position with Select as the horizontal line moves down the cell.", "With Auto select enabled, a countdown starts: wait for a left click, or press a switch to open the action menu.", "With Auto select disabled, the action menu opens immediately. Choose an action with Select. After a click, Select starts a new scan."] },
+ line: { title: "Automatic line-only scanning", steps: ["Focus the application you want to control. Press and release Select to start automatic scanning.", "Choose the horizontal position with Select as the vertical line moves across the display.", "Choose the vertical position with Select as the horizontal line moves down the display.", "With Auto select enabled, wait through the countdown for a left click, or press a switch to open the action menu.", "With Auto select disabled, the action menu opens immediately. Choose an action with Select. After a click, Select starts a new scan."] },
+ hold: { title: "Press, hold and release", steps: ["Press your switch briefly. Movement freezes while the switch is down.", "Release it to run its Press and release action, such as Select.", "Hold the switch longer to move through its configured hold actions. Scanning movement stays frozen.", "Keep holding until the action you want is offered. The actions shown here are examples, not your assignments.", "Release to execute the offered hold action. Holding never executes the offered action by itself; the emergency hold resets scanning."] },
+ access: { title: "Allow Accessibility on macOS", steps: ["Choose Open Accessibility Settings in Switchify PC.", "In System Settings, open Privacy & Security, then Accessibility, and find Switchify PC.", "Enable Switchify PC, then return to the app so it can check access. This illustration does not grant permission."] },
+ pair: { title: "Pair a mobile device", steps: ["Open Switchify on your mobile device and choose this computer while it is advertising.", "Compare the six-digit codes in both apps. These are example codes, not a live pairing request.", "Approve on this computer only if the codes match. If they differ or you did not request pairing, reject the request.", "A successful pairing trusts the mobile device. Check the real connection status in the app."] },
+ startup: { title: "Understand startup behavior", steps: ["Start with system is optional. Choose whether Switchify should launch when you sign in.", `When enabled, Switchify starts quietly in the ${location}.`, `Open Switchify from the ${location} when you need its window. Start manually means opening the app yourself.`] },
+ };
+ return examples[kind];
+}
diff --git a/src/settings/GeneralSection.tsx b/src/settings/GeneralSection.tsx
index e4620cc6..9facce38 100644
--- a/src/settings/GeneralSection.tsx
+++ b/src/settings/GeneralSection.tsx
@@ -1,6 +1,7 @@
+import { Demonstration } from "../help/Demonstration";
import type { AppSettings } from "../types";
import { SettingGroup, Toggle, type SettingsUpdate } from "./controls";
export function GeneralSection({ settings, update }: { settings: AppSettings; update: SettingsUpdate }) {
- return update("startWithSystem", value)} />;
+ return update("startWithSystem", value)} />;
}
diff --git a/src/settings/ScanningSection.tsx b/src/settings/ScanningSection.tsx
index 4b635ece..686953d0 100644
--- a/src/settings/ScanningSection.tsx
+++ b/src/settings/ScanningSection.tsx
@@ -1,3 +1,4 @@
+import { Demonstration } from "../help/Demonstration";
import { type ScanningController, validSwitches } from "../scanning/useScanning";
import { ScannerPreferences } from "./ScannerPreferences";
@@ -35,6 +36,7 @@ export function ScanningSection({ controller }: { controller: ScanningController
Assigned keys stay reserved while Switchify runs. Escape resets the scan. Mobile connections pause local scanning until they end.
On Windows, assigned keys remain switches with Shift, Ctrl, Alt or Windows held, even in the background. Other keys and Switchify-generated shortcuts still work normally.
+
;
}
diff --git a/src/settings/SwitchesSection.tsx b/src/settings/SwitchesSection.tsx
index faae3cc4..61a1a14a 100644
--- a/src/settings/SwitchesSection.tsx
+++ b/src/settings/SwitchesSection.tsx
@@ -1,3 +1,4 @@
+import { Demonstration, SwitchDemonstrations } from "../help/Demonstration";
import { SwitchPractice } from "../scanning/SwitchPractice";
import { createPortal } from "react-dom";
import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
@@ -126,6 +127,7 @@ function SwitchEditor({
};
return (