diff --git a/BUILD_GUIDE.md b/BUILD_GUIDE.md
index de619ea5..e2c4a4fe 100644
--- a/BUILD_GUIDE.md
+++ b/BUILD_GUIDE.md
@@ -4,6 +4,8 @@
This guide walks you through building a Patternflow on the v3 board from scratch. v3.9 is the current revision; a v3.0 board follows the same steps (its USB-C footprint stays unpopulated, see ยง2). No prior soldering experience needed โ every joint is big, forgiving through-hole, and the board was deliberately kept that simple so a first-time solderer can finish it.
+> ๐งญ **The same build, in 3D.** [patternflow.work/guide/build](https://patternflow.work/guide/build) walks through this guide step by step on a 3D Patternflow: the parts list, the soldering order with each part going into its real holes, the case, the wiring, first light. This document stays the written reference; follow whichever suits you, or both.
+
**Estimated build time:** about 1 hour of hands-on work (~30 min soldering + ~30 min assembly), plus ~10 hours of 3D printing. Parts shipping typically takes ~2 weeks โ order first, build later.
**What's new in v3** (vs. v2.x):
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 205bd59e..0a051000 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,7 +33,7 @@ All notable changes to Patternflow will be documented in this file, newest first
### Web
-- **A guide you can play along with, in three parts, at [/guide](https://patternflow.work/guide)** (work in progress, marked WIP). `/guide` is where you pick one by where your Patternflow is, each with its chapters, in order. **Build**, at [/guide/build](https://patternflow.work/guide/build), is for soldering one from bare parts: 01 Gather (the parts list is `bom_v3.9.csv` itself), 02 Order & print, 03 Solder, 04 Into the case, 05 Wire & power, 06 Firmware (which hands over to Play's 01 Flash) and 07 Check & close, each step played on the real v3.9 board and case in 3D, in the order the build happens; the board's known issues stay in `BUILD_GUIDE.md` ยง10, linked. **Play**, at [/guide/play](https://patternflow.work/guide/play), is the first hour with a built one, 01 Flash, 02 Knobs, 03 Patterns, 04 Console, on the real v3.9 hardware in 3D, running a port of the firmware's input logic and screens, with the real flasher dialogs and a live copy of the device console. **Make**, at [/guide/make](https://patternflow.work/guide/make), is 01 Community and 02 Pattern Lab, done by hand: on a big enough screen your own Pattern Lab, a practice community (placeholder patterns, connected to nothing) and a practice AI with set answers sit beside the steps, and a pointer shows each move and waits for yours; phones get the real screens. Sound, MIDI & OSC, MQTT, Clock, Performance and Editions join Make as they are written. Each guide numbers its own chapters from 01. Links into the first version, when Play was `/guide` itself (`/guide#flash`, `/guide#knobs-3`), land on the same step under `/guide/play`. English and Korean. Every step has a "Stuck here?" link to a GitHub issue that already says which guide, chapter and step (`guide_stuck.yml`).
+- **A guide you can play along with, in three parts, at [/guide](https://patternflow.work/guide)** (work in progress, marked WIP). `/guide` is where you pick one by where your Patternflow is, each with its chapters, in order. **Build**, at [/guide/build](https://patternflow.work/guide/build), is for soldering one from bare parts: 01 Gather (the parts list is `bom_v3.9.csv` itself), 02 Order & print, 03 Solder, 04 Into the case, 05 Wire & power, 06 Firmware (which hands over to Play's 01 Flash) and 07 Check & close, each step played on the real v3.9 board and case in 3D, in the order the build happens; the board's known issues stay in `BUILD_GUIDE.md` ยง10, linked. **Play**, at [/guide/play](https://patternflow.work/guide/play), is the first hour with a built one, 01 Flash, 02 Knobs, 03 Patterns, 04 Console, on the real v3.9 hardware in 3D, running a port of the firmware's input logic and screens, with the real flasher dialogs and a live copy of the device console. **Make**, at [/guide/make](https://patternflow.work/guide/make), is 01 Community and 02 Pattern Lab, done by hand: on a big enough screen your own Pattern Lab, a practice community (placeholder patterns, connected to nothing) and a practice AI with set answers sit beside the steps, and a pointer shows each move and waits for yours; phones get the real screens. Sound, MIDI & OSC, MQTT, Clock, Performance and Editions join Make as they are written. Each guide numbers its own chapters from 01. Links into the first version, when Play was `/guide` itself (`/guide#flash`, `/guide#knobs-3`), land on the same step under `/guide/play`. English and Korean. Every step has a "Stuck here?" link to a GitHub issue that already says which guide, chapter and step (`guide_stuck.yml`). The 3D stage is one continuous world: moving between the hub, Build and Play is a scene rather than a page load, and pointing at a guide on the hub previews it on the device (Build comes apart, Play turns a knob, Make redraws the panel). The LED panel lights the room with the pattern it is playing, and the page's accent follows that colour. Pretendard is now served in four script-range files, so an English page no longer downloads the Hangul glyphs (a Korean page renders identically). README, BUILD_GUIDE, PATTERN_GUIDE and the assembly map link to the guide.
- **The community's Performances note says .pfs**, which is what the Director saves, instead of "Save-JSON".
- **A moderator can take a pattern or deck off the wall without deleting it.** Somebody else's public pattern or deck now offers moderators *Make private*, with an optional reason: the softer removal, for things like the same pattern posted a fifth time. Nothing about the work changes and its author keeps it โ they can still open it, edit it, take it into the lab โ it just stops being shown to anyone else. On a pattern the reason is posted underneath as the moderator's comment, where the author can answer it; either way the author is told in their alerts. While the take-down stands the author cannot make it public again (a take-down its subject can undo in one click is a request); *Restore to public* puts it back exactly as it was. A moderator still cannot publish what an author made private themselves. A deck slot left empty by a take-down reads "made private by a moderator", not "by its author". Moderators can now comment on private patterns and keep their alerts about them, so the thread under a take-down works both ways. Migration `0025` adds `hidden_at` to patterns and decks; `check:hidemod` drives it all through the real routes.
- **One press of Publish is one post.** A double-click on *Publish to the wall*, or a mouse switch that bounces, could put the same pattern on the wall twice in the same second ([#455](https://github.com/engmung/Patternflow/issues/455)). The button's disabled state was React state, a render too late for the second click, and after a successful publish it came back enabled while the page changed. The publish, thread, reply, deck, header, performance and report forms now refuse a second press in the same tick (`useSubmitLatch`), and a published pattern keeps its button down until the page changes; a thread whose files failed to attach offers *Open the thread* instead of posting it again. Behind them the server answers an identical submission from the same account within a minute with the post it already made, looked up and inserted in one synchronous transaction so two requests arriving together cannot both get through. `check:publish` fires two identical publishes into the route at the same instant; a version that awaits between the lookup and the insert fails it.
diff --git a/PATTERN_GUIDE.md b/PATTERN_GUIDE.md
index 5f85f14b..ca4cf717 100644
--- a/PATTERN_GUIDE.md
+++ b/PATTERN_GUIDE.md
@@ -7,6 +7,10 @@ Lab, verify them on real hardware, and share them back.**
Assembly and the first flash are [BUILD_GUIDE.md](BUILD_GUIDE.md). This guide
is what comes after.
+Rather do it than read it? [patternflow.work/guide/make](https://patternflow.work/guide/make) is the
+same loop as a hands-on tutorial: a practice community, your real Pattern Lab
+in a window, and a practice AI to take the two prompts.
+
- [0. Before you start](#0-before-you-start)
- [1. One concept: a pattern is a small file](#1-one-concept-a-pattern-is-a-small-file)
- [2. A look around the community](#2-a-look-around-the-community)
diff --git a/README.md b/README.md
index ad61ecaa..638929be 100644
--- a/README.md
+++ b/README.md
@@ -143,7 +143,7 @@ What a knob does to a running pattern is the pattern's own decision, so the same
The device functions themselves are fixed. Encoder 1 is brightness. Encoder 4 opens the pattern list, where you turn to browse the names and long-press again to load one, choosing from whatever is installed at the time. Encoder 2 puts the board's IP address on the panel, and typing that into a browser opens the web console, which carries every feature the device has and works from a phone.
-[patternflow.work/guide/play](https://patternflow.work/guide/play) has all of this on a Patternflow you can turn, press and hold, from flashing the ESP32 to the console.
+[patternflow.work/guide/play](https://patternflow.work/guide/play) has all of this on a Patternflow you can turn, press and hold, from flashing the ESP32 to the console. The same guide builds one with you ([Build](https://patternflow.work/guide/build)) and takes you through the community and Pattern Lab ([Make](https://patternflow.work/guide/make)); [patternflow.work/guide](https://patternflow.work/guide) is where to start.
## On the device
@@ -187,6 +187,7 @@ Patternflow is built around a standalone ESP32-S3 driving a HUB75 RGB LED matrix
| `integrations/` | Host-software bridges: Ableton Live / Max for Live (OSC knob mapping) |
| `.github/` | Issue and PR templates, and the CI that runs on every pull request (web, every firmware edition, doc links, console pages) |
+**Guide (interactive):** [Start here](https://patternflow.work/guide) ยท [Build it](https://patternflow.work/guide/build) ยท [Play it](https://patternflow.work/guide/play) ยท [Make patterns](https://patternflow.work/guide/make): each step shown on a 3D Patternflow you can turn, in English and Korean
**Build:** [Full Build Guide](BUILD_GUIDE.md) ยท [Assembly Map](docs/assembly/README.md) ยท [Panel Compatibility](docs/panel-compatibility.md) ยท [Hardware files](hardware/README.md)
**Play:** [Pattern Guide](PATTERN_GUIDE.md) ยท [Custom Patterns](firmware/CUSTOM_PATTERNS.md) ยท [Audio Guide](AUDIO_GUIDE.md) ยท [MIDI in Ableton](docs/midi-ableton.md)
**Extend:** [Feature Guide](FEATURE_GUIDE.md) ยท [Editions](docs/EDITIONS.md) ยท [Firmware build](firmware/README.md) ยท [Web architecture](web/ARCHITECTURE.md) ยท [HTTP API](docs/rest-api.md) ยท [OSC Spec](docs/osc-spec.md) ยท [MIDI Spec](docs/midi-spec.md) ยท [Director โ MIDI](docs/director-midi.md)
diff --git a/docs/assembly/README.md b/docs/assembly/README.md
index 5f1e0b6c..776e7cc0 100644
--- a/docs/assembly/README.md
+++ b/docs/assembly/README.md
@@ -18,7 +18,7 @@ Build those two, flash the firmware, and your Patternflow is alive.
| --- | --- | --- | --- |
| [3D printed enclosure](../../hardware/case/README.md) | [Custom PCB, hand-soldered](../../hardware/pcb/README.md) | [Browser flash](../../BUILD_GUIDE.md#8-firmware) ยท [your own patterns](../../PATTERN_GUIDE.md) | **Current โ fully documented** |
-This is the route [BUILD_GUIDE.md](../../BUILD_GUIDE.md) walks start to finish: PLA parts on any 256 mm-bed FDM printer, the hand-soldered v3.9 board (all through-hole โ deliberately kept first-timer easy), and firmware flashed from the browser. Two ordering shortcuts are wired straight to it:
+This is the route [BUILD_GUIDE.md](../../BUILD_GUIDE.md) walks start to finish, and the one the [interactive build guide](https://patternflow.work/guide/build) shows in 3D: PLA parts on any 256 mm-bed FDM printer, the hand-soldered v3.9 board (all through-hole โ deliberately kept first-timer easy), and firmware flashed from the browser. Two ordering shortcuts are wired straight to it:
- **PCB** โ the [PCBWay shared project](https://www.pcbway.com/project/shareproject/Patternflow_An_LED_synthesizer_776d796c.html): no Gerber upload, and ordering there supports Patternflow development.
- **Case** โ the [MakerWorld listing](https://makerworld.com/en/models/3072492-patternflow-open-source-led-synthesizer-case#profileId-3459015): tuned one-click print profiles for Bambu printers (STLs in `hardware/case/` for everyone else).
diff --git a/web/public/fonts/Pretendard-Bold.hangul.woff2 b/web/public/fonts/Pretendard-Bold.hangul.woff2
new file mode 100644
index 00000000..84626af4
Binary files /dev/null and b/web/public/fonts/Pretendard-Bold.hangul.woff2 differ
diff --git a/web/public/fonts/Pretendard-Bold.latin.woff2 b/web/public/fonts/Pretendard-Bold.latin.woff2
new file mode 100644
index 00000000..d045d82f
Binary files /dev/null and b/web/public/fonts/Pretendard-Bold.latin.woff2 differ
diff --git a/web/public/fonts/Pretendard-Bold.other.woff2 b/web/public/fonts/Pretendard-Bold.other.woff2
new file mode 100644
index 00000000..ed5e315a
Binary files /dev/null and b/web/public/fonts/Pretendard-Bold.other.woff2 differ
diff --git a/web/public/fonts/Pretendard-Bold.symbols.woff2 b/web/public/fonts/Pretendard-Bold.symbols.woff2
new file mode 100644
index 00000000..03b653fe
Binary files /dev/null and b/web/public/fonts/Pretendard-Bold.symbols.woff2 differ
diff --git a/web/public/fonts/Pretendard-Regular.hangul.woff2 b/web/public/fonts/Pretendard-Regular.hangul.woff2
new file mode 100644
index 00000000..cb744201
Binary files /dev/null and b/web/public/fonts/Pretendard-Regular.hangul.woff2 differ
diff --git a/web/public/fonts/Pretendard-Regular.latin.woff2 b/web/public/fonts/Pretendard-Regular.latin.woff2
new file mode 100644
index 00000000..c70d4dd3
Binary files /dev/null and b/web/public/fonts/Pretendard-Regular.latin.woff2 differ
diff --git a/web/public/fonts/Pretendard-Regular.other.woff2 b/web/public/fonts/Pretendard-Regular.other.woff2
new file mode 100644
index 00000000..63e55ef9
Binary files /dev/null and b/web/public/fonts/Pretendard-Regular.other.woff2 differ
diff --git a/web/public/fonts/Pretendard-Regular.symbols.woff2 b/web/public/fonts/Pretendard-Regular.symbols.woff2
new file mode 100644
index 00000000..6123d287
Binary files /dev/null and b/web/public/fonts/Pretendard-Regular.symbols.woff2 differ
diff --git a/web/public/guide/look/pcb-v39-copper.webp b/web/public/guide/look/pcb-v39-copper.webp
new file mode 100644
index 00000000..fd5f2c4a
Binary files /dev/null and b/web/public/guide/look/pcb-v39-copper.webp differ
diff --git a/web/src/app/globals.css b/web/src/app/globals.css
index f5e24c49..a1360c8c 100644
--- a/web/src/app/globals.css
+++ b/web/src/app/globals.css
@@ -1,18 +1,76 @@
@import "tailwindcss";
+ /* Pretendard, each weight in four parts. The whole file is 770โ790 KB, most
+ of it Hangul, and a page fetched all of it for one glyph Inter lacks โ
+ an English page for the arrow in a link. The parts are the same glyphs,
+ outlines and metrics, cut by script (pyftsubset on the files beside
+ them); their ranges cover all of Unicode between them, so whatever the
+ whole file drew, one part still draws. A page now fetches only the parts
+ it has characters for: an arrow costs 38 KB, and the Hangul comes with
+ the first Korean word. */
@font-face {
font-family: 'Pretendard';
font-weight: 400;
font-style: normal;
font-display: swap;
- src: url('/fonts/Pretendard-Regular.woff2') format('woff2');
+ src: url('/fonts/Pretendard-Regular.latin.woff2') format('woff2');
+ unicode-range: U+0000-052F, U+1E00-1EFF;
+ }
+ @font-face {
+ font-family: 'Pretendard';
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+ src: url('/fonts/Pretendard-Regular.hangul.woff2') format('woff2');
+ unicode-range: U+1100-11FF, U+3130-318F, U+A960-A97F, U+AC00-D7FF;
+ }
+ @font-face {
+ font-family: 'Pretendard';
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+ src: url('/fonts/Pretendard-Regular.symbols.woff2') format('woff2');
+ unicode-range: U+2000-2BFF;
+ }
+ @font-face {
+ font-family: 'Pretendard';
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+ src: url('/fonts/Pretendard-Regular.other.woff2') format('woff2');
+ unicode-range: U+0530-10FF, U+1200-1DFF, U+1F00-1FFF, U+2C00-312F, U+3190-A95F, U+A980-ABFF, U+D800-10FFFF;
+ }
+ @font-face {
+ font-family: 'Pretendard';
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+ src: url('/fonts/Pretendard-Bold.latin.woff2') format('woff2');
+ unicode-range: U+0000-052F, U+1E00-1EFF;
+ }
+ @font-face {
+ font-family: 'Pretendard';
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+ src: url('/fonts/Pretendard-Bold.hangul.woff2') format('woff2');
+ unicode-range: U+1100-11FF, U+3130-318F, U+A960-A97F, U+AC00-D7FF;
+ }
+ @font-face {
+ font-family: 'Pretendard';
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+ src: url('/fonts/Pretendard-Bold.symbols.woff2') format('woff2');
+ unicode-range: U+2000-2BFF;
}
@font-face {
font-family: 'Pretendard';
font-weight: 700;
font-style: normal;
font-display: swap;
- src: url('/fonts/Pretendard-Bold.woff2') format('woff2');
+ src: url('/fonts/Pretendard-Bold.other.woff2') format('woff2');
+ unicode-range: U+0530-10FF, U+1200-1DFF, U+1F00-1FFF, U+2C00-312F, U+3190-A95F, U+A980-ABFF, U+D800-10FFFF;
}
:root {
diff --git a/web/src/app/guide/layout.tsx b/web/src/app/guide/layout.tsx
new file mode 100644
index 00000000..3a48a119
--- /dev/null
+++ b/web/src/app/guide/layout.tsx
@@ -0,0 +1,13 @@
+import type { ReactNode } from "react";
+import GuideWorld from "@/components/guide/world/GuideWorld";
+
+// Everything under /guide โ the hub, Build, Play and Make, in both languages
+// โ is one world: this layout mounts the guide's stage once (GuideWorld), and
+// it stays up while the reader moves between the pages. A layout is not
+// rendered again on a navigation inside it, so the 3D canvas, the camera, the
+// simulated board and the loaded models carry over; each page only says
+// which one it is (components/guide/world/usePage.ts).
+
+export default function GuideLayout({ children }: { children: ReactNode }) {
+ return