diff --git a/web/src/lib/docs/PlatformChoice.svelte b/web/src/lib/docs/PlatformChoice.svelte
new file mode 100644
index 0000000..6e087b4
--- /dev/null
+++ b/web/src/lib/docs/PlatformChoice.svelte
@@ -0,0 +1,47 @@
+
+
+
diff --git a/web/src/lib/docs/PlatformChooser.svelte b/web/src/lib/docs/PlatformChooser.svelte
new file mode 100644
index 0000000..8f13646
--- /dev/null
+++ b/web/src/lib/docs/PlatformChooser.svelte
@@ -0,0 +1,12 @@
+
+
+
+ {@render children()}
+
diff --git a/web/src/lib/docs/PlatformToggle.svelte b/web/src/lib/docs/PlatformToggle.svelte
index dcea226..e1c9c22 100644
--- a/web/src/lib/docs/PlatformToggle.svelte
+++ b/web/src/lib/docs/PlatformToggle.svelte
@@ -1,25 +1,16 @@
diff --git a/web/src/lib/docs/platformPref.ts b/web/src/lib/docs/platformPref.ts
new file mode 100644
index 0000000..6690b8d
--- /dev/null
+++ b/web/src/lib/docs/platformPref.ts
@@ -0,0 +1,16 @@
+// The single source of truth for which platform is showing is the
+// `data-platform-pref` attribute on — set once, before paint, by the
+// blocking inline script in app.html (which also honors a `?sbc` / `?docker`
+// link, see CLAUDE.md's docs conventions). Every interactive piece that can
+// change the preference (PlatformToggle, PlatformChoice) goes through this
+// one place, so they can never disagree on how a choice is written.
+export type Platform = "sbc" | "docker";
+
+export function readPlatformPref(): Platform {
+ return document.documentElement.getAttribute("data-platform-pref") === "docker" ? "docker" : "sbc";
+}
+
+export function setPlatformPref(next: Platform) {
+ document.documentElement.setAttribute("data-platform-pref", next);
+ localStorage.setItem("atfsDocsPlatform", next);
+}
diff --git a/web/src/routes/docs/getting-started/+page.md b/web/src/routes/docs/getting-started/+page.md
index c3ecbfb..e52a98e 100644
--- a/web/src/routes/docs/getting-started/+page.md
+++ b/web/src/routes/docs/getting-started/+page.md
@@ -1,7 +1,6 @@
---
type: how-to
title: Getting started
-description: How are you running atfs?
intro: Pick how you run atfs.
topic: getting-started
importance: 10
@@ -9,31 +8,37 @@ index: true
---
-
+Atfs can be run in two ways. Choose which way is best for you here, and these docs will adjust themselves to your needs.
-You flash an image to an SD card and boot a physical board. See
-[Set up an SD-card device](/docs/getting-started/sbc/).
+
-
+
-
+You have a Single Board Computer, like a [Raspberry Pi](https://raspberrypi.org), and run atfs on it from a microSD card.
-You run atfs as a container. See
-[Set up a Docker container](/docs/getting-started/docker/).
+
-
+
-
+You run atfs as a [Docker container](https://docs.docker.com/get-started/docker-overview/) on a server.
-Not sure? If you bought a preflashed card or built one with balena
-Etcher, choose the SD-card path.
+
+
+
+
+
-
+See [Set up an SD-card device](/docs/getting-started/sbc/).
-```bash
-docker run -d -p 2837:2837 -e ATFS_OWNER_DID=did:plc:example jphastings/atfs
-```
+
+
+
+
+See [Set up a Docker container](/docs/getting-started/docker/).
+
+
diff --git a/web/static/atfs.css b/web/static/atfs.css
index c632018..30c9b1b 100644
--- a/web/static/atfs.css
+++ b/web/static/atfs.css
@@ -317,6 +317,72 @@ pre.shiki code {
display: block;
}
+/* PlatformChooser/PlatformChoice — an in-page way to change data-platform-
+ pref (getting-started/+page.md is the first user), rather than only ever
+ reading it the way .platform-block and the relevance note do. */
+.platform-chooser {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+ margin: 0 0 1.25rem;
+}
+@media (max-width: 30rem) {
+ .platform-chooser {
+ grid-template-columns: 1fr;
+ }
+}
+
+.platform-choice {
+ border: 1px solid var(--border);
+ border-radius: 0.5em;
+ padding: 1rem 1.1rem;
+ cursor: pointer;
+}
+.platform-choice:hover {
+ border-color: var(--accent-strong);
+}
+.platform-choice:focus-visible {
+ outline: 2px solid var(--accent-strong);
+ outline-offset: 2px;
+}
+
+.platform-choice-label {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-weight: 700;
+ margin: 0 0 0.5rem;
+}
+
+/* A radio-style dot, not just a border/background color change, so the
+ selected choice reads clearly without relying on color alone. */
+.platform-choice-radio {
+ flex: none;
+ width: 0.9rem;
+ height: 0.9rem;
+ border-radius: 50%;
+ border: 2px solid var(--border);
+}
+
+.platform-choice-body :global(p:last-child) {
+ margin-bottom: 0;
+}
+
+/* Selection is purely attribute-driven, exactly like .platform-block's own
+ visibility rule above — correct from first paint, no onMount correction
+ needed. Deliberately never dims or fades the UNSELECTED choice: both
+ stay fully readable, so either one is easy to pick. */
+[data-platform-pref="sbc"] .platform-choice[data-platform="sbc"],
+[data-platform-pref="docker"] .platform-choice[data-platform="docker"] {
+ border-color: var(--accent);
+ background: var(--bg-raised);
+}
+[data-platform-pref="sbc"] .platform-choice[data-platform="sbc"] .platform-choice-radio,
+[data-platform-pref="docker"] .platform-choice[data-platform="docker"] .platform-choice-radio {
+ border-color: var(--accent);
+ background: radial-gradient(circle, var(--accent) 45%, transparent 50%);
+}
+
/* A small brand mark linking home — used by any page whose own layout
doesn't already put the logo front and center the way the home page's
hero does (the docs page and raw-downloads). */