diff --git a/.gitignore b/.gitignore
index 0db830df..b70df630 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,9 @@ packages/upstream-tests/core/
*.log
.DS_Store
website/docs/guide/api/
+# generated zh mirrors from generate-api.ts (hand-written zh index.mdx
+# files for vue-lynx/testing-library are already tracked and unaffected)
+website/docs/zh/guide/api/
website/api-sidebar.json
.vercel
.claude/
diff --git a/docs/superpowers/plans/2026-07-14-elk-sheet-gesture-refinement.md b/docs/superpowers/plans/2026-07-14-elk-sheet-gesture-refinement.md
new file mode 100644
index 00000000..4e340137
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-14-elk-sheet-gesture-refinement.md
@@ -0,0 +1,280 @@
+# Elk Sheet Gesture Refinement Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make the Elk navigation Sheet feel native on iOS by porting the current `lynx-ui` direction lock, velocity projection, rubber resistance, progress-linked backdrop, and spring settle behavior into Vue Lynx.
+
+**Architecture:** Keep gesture state and rendering mutations in main-thread worklets inside `Sheet.vue`. Keep angle, physics, progress, and dismissal decisions as pure functions in `gesture.ts` so Node tests exercise the same calculations. Use a hybrid input policy: the dedicated handle always owns vertical drags, while the scrollable panel claims only downward drags that begin at `scrollTop === 0`.
+
+**Tech Stack:** Vue Lynx SFCs, Main Thread Script worklets, `useMainThreadRef`, requestAnimationFrame, Node test runner, Rspeedy, Lynx DevTool.
+
+---
+
+## File map
+
+- `examples/elk/src/components/sheet/gesture.ts`: pure gesture ownership, rubber resistance, velocity projection, progress, and spring-step calculations.
+- `examples/elk/src/components/sheet/Sheet.vue`: hybrid touch surfaces, main-thread state machine, direct manipulation, spring settle, and visual structure.
+- `examples/elk/src/components/NavBottom.vue`: spacing adjustment for the new grabber without changing navigation behavior.
+- `examples/elk/scripts/native-compat.test.mjs`: behavioral and source-contract regression coverage.
+
+### Task 1: Specify gesture physics with failing tests
+
+**Files:**
+
+- Modify: `examples/elk/scripts/native-compat.test.mjs`
+- Test: `examples/elk/scripts/native-compat.test.mjs`
+
+- [ ] **Step 1: Replace the legacy fixed-distance test with behavior tests**
+
+Add assertions that demonstrate the desired pure API:
+
+```js
+test('sheet claims handle drags vertically and content drags only downward at scroll top', () => {
+ assert.equal(sheetGesture.shouldClaimSheetGesture?.('handle', 2, -12, 40), true);
+ assert.equal(sheetGesture.shouldClaimSheetGesture?.('handle', 20, 10, 0), false);
+ assert.equal(sheetGesture.shouldClaimSheetGesture?.('content', 2, 12, 0), true);
+ assert.equal(sheetGesture.shouldClaimSheetGesture?.('content', 2, -12, 0), false);
+ assert.equal(sheetGesture.shouldClaimSheetGesture?.('content', 2, 12, 1), false);
+});
+
+test('sheet uses bounded rubber resistance above the open position', () => {
+ assert.equal(sheetGesture.resolveSheetDrag?.(30), 30);
+ assert.equal(sheetGesture.resolveSheetDrag?.(-40, 80, 0.5), -16);
+ assert.ok(sheetGesture.resolveSheetDrag?.(-10000, 80, 0.5) > -80);
+});
+
+test('sheet release uses distance or projected fling travel to dismiss', () => {
+ assert.equal(sheetGesture.shouldDismissSheet?.(120, 0, 120), true);
+ assert.equal(sheetGesture.shouldDismissSheet?.(119, 0, 120), false);
+ assert.equal(sheetGesture.shouldDismissSheet?.(24, 900, 120), true);
+ assert.equal(sheetGesture.shouldDismissSheet?.(10, 1200, 120), false);
+ assert.equal(sheetGesture.shouldDismissSheet?.(60, -900, 120), false);
+});
+
+test('sheet backdrop progress follows downward travel', () => {
+ assert.equal(sheetGesture.sheetOpenProgress?.(-20, 600), 1);
+ assert.equal(sheetGesture.sheetOpenProgress?.(150, 600), 0.75);
+ assert.equal(sheetGesture.sheetOpenProgress?.(800, 600), 0);
+});
+
+test('sheet filters noisy velocity samples and integrates a stable spring step', () => {
+ assert.equal(sheetGesture.smoothSheetVelocity?.(0, 9, 10, 0.25), 225);
+ const next = sheetGesture.stepSheetSpring?.(100, 0, 0, 1 / 60);
+ assert.ok(next.value < 100);
+ assert.ok(next.velocity < 0);
+});
+```
+
+- [ ] **Step 2: Add a source-contract test for the hybrid surfaces**
+
+Require `.sheet-handle`, separate handle/content start handlers, backdrop and surface refs, layout measurement, and requestAnimationFrame settling. Also require that drag mutation remains main-thread-bound and does not use `transition: all`.
+
+- [ ] **Step 3: Run the native compatibility test and verify RED**
+
+Run:
+
+```bash
+pnpm --dir examples/elk test:native-compat
+```
+
+Expected: the new gesture tests fail because the new pure helpers and hybrid Sheet structure do not exist.
+
+### Task 2: Implement the pure gesture model
+
+**Files:**
+
+- Modify: `examples/elk/src/components/sheet/gesture.ts`
+- Test: `examples/elk/scripts/native-compat.test.mjs`
+
+- [ ] **Step 1: Implement angle and ownership classification**
+
+Keep the 8 px lock threshold and use vertical angle ranges equivalent to `lynx-ui`:
+
+```ts
+export type SheetGestureSource = 'handle' | 'content';
+
+export function shouldClaimSheetGesture(
+ source: SheetGestureSource,
+ deltaX: number,
+ deltaY: number,
+ scrollTop: number,
+): boolean {
+ 'main thread';
+ const displacement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ if (displacement < SHEET_GESTURE_LOCK_DISTANCE)
+ return false;
+ const vertical = Math.abs(deltaY) > Math.abs(deltaX);
+ if (!vertical)
+ return false;
+ return source === 'handle' || (deltaY > 0 && scrollTop <= 0);
+}
+```
+
+- [ ] **Step 2: Implement upstream-compatible rubber resistance**
+
+```ts
+export function rubberEffect(original: number, max: number, coeff = 0.5): number {
+ 'main thread';
+ if (original === 0 || max === 0)
+ return 0;
+ return (1 - 1 / ((Math.abs(original) * coeff) / max + 1)) * max;
+}
+
+export function resolveSheetDrag(deltaY: number, rubberMax = 80, coeff = 0.5): number {
+ 'main thread';
+ return deltaY >= 0 ? deltaY : -rubberEffect(-deltaY, rubberMax, coeff);
+}
+```
+
+- [ ] **Step 3: Implement progress, velocity smoothing, projection, and release decisions**
+
+Use `travel = velocity² / (2 × 2000)` from `lynx-ui`, require 24 px of intentional downward travel for a fling dismissal, and retain the existing 120 px distance threshold as the non-fling path.
+
+- [ ] **Step 4: Implement one deterministic spring integration step**
+
+Expose `stepSheetSpring(value, velocity, target, deltaSeconds, stiffness = 300, damping = 30)` returning the next `{ value, velocity }`. Clamp `deltaSeconds` to `1 / 30` so a delayed frame cannot explode the simulation.
+
+- [ ] **Step 5: Run tests and verify the pure behavior is GREEN**
+
+Run:
+
+```bash
+pnpm --dir examples/elk test:native-compat
+```
+
+Expected: physics assertions pass; the source-contract test remains red until `Sheet.vue` is updated.
+
+### Task 3: Implement hybrid main-thread dragging and motion
+
+**Files:**
+
+- Modify: `examples/elk/src/components/sheet/Sheet.vue`
+- Modify: `examples/elk/src/components/NavBottom.vue`
+- Test: `examples/elk/scripts/native-compat.test.mjs`
+
+- [ ] **Step 1: Replace the single panel ref with coordinated visual refs**
+
+Create main-thread refs for the surface, backdrop, measured surface height, gesture source, direction state, last touch sample, smoothed velocity, animation frame, and current translation. Keep all writes inside `'main thread'` functions.
+
+- [ ] **Step 2: Add the hybrid touch surfaces**
+
+Render this structure:
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+The surface height remains capped by `topInset`; the fixed layer remains inset above the bottom bar and Sparkling safe area.
+
+- [ ] **Step 3: Implement direction locking and direct manipulation**
+
+On touch start, record coordinates/source without stopping an existing animation. On the first move beyond 8 px, call `shouldClaimSheetGesture`; reject horizontal/content-scroll gestures permanently, or cancel the current spring and capture the valid vertical gesture. During captured moves, use `resolveSheetDrag`, set `translateY`, and update backdrop opacity from `sheetOpenProgress`.
+
+- [ ] **Step 4: Implement spring settle and dismissal**
+
+Drive requestAnimationFrame with `stepSheetSpring`. Snap-back targets `0`; dismissal targets the measured surface height plus 32 px. Stop when distance and velocity are both below `0.5`, then call `runOnBackground(requestClose)()` only for dismissal. Cancelled gestures always spring to `0`.
+
+- [ ] **Step 5: Reset inline motion after leave**
+
+Add an `after-leave` handler that invokes a main-thread reset, restoring translation `0`, backdrop opacity `1`, and idle gesture state so reopening never inherits an off-screen inline transform.
+
+- [ ] **Step 6: Style the grabber and connected rubber fill**
+
+Use a 28 px hit area, a centered 36 × 4 px pill, the existing themed surface color, and a surface-attached 80 px fill below the panel so upward rubber travel never reveals the backdrop between the Sheet and bottom bar. Reduce `nav-sheet-content` top padding to preserve the current first-row rhythm.
+
+- [ ] **Step 7: Run the focused suite and verify GREEN**
+
+Run:
+
+```bash
+pnpm --dir examples/elk test:native-compat
+```
+
+Expected: every native compatibility test passes.
+
+### Task 4: Verify native behavior and publish the updated bundle
+
+**Files:**
+
+- Verify: `examples/elk/dist/main.lynx.bundle`
+- Verify: PR #196 checks and Vercel artifacts
+
+- [ ] **Step 1: Clear the Elk cache and restart the dev server**
+
+Run:
+
+```bash
+rm -rf examples/elk/node_modules/.cache
+pnpm --dir examples/elk dev
+```
+
+- [ ] **Step 2: Build both targets**
+
+Run:
+
+```bash
+pnpm --dir examples/elk build
+```
+
+Expected: `dist/main.lynx.bundle` and `dist/main.web.bundle` both build successfully.
+
+- [ ] **Step 3: Verify three native gesture paths**
+
+Using Lynx DevTool on the Sparkling iOS simulator:
+
+1. Slow downward drag under the threshold springs open and restores the backdrop.
+2. Short fast downward fling closes the Sheet.
+3. Upward handle pull resists asymptotically and returns without exposing a gap; upward content swipe still scrolls.
+
+Capture the final open and post-settle states with `take-screenshot`.
+
+- [ ] **Step 4: Run repository-level validation**
+
+Run:
+
+```bash
+pnpm lint
+pnpm test:dev-smoke
+```
+
+Expected: both commands exit successfully.
+
+- [ ] **Step 5: Commit and push PR #196**
+
+Stage only the Sheet refinement files, commit with `feat(examples/elk): refine native sheet gestures`, and push `HEAD` to `origin/claude/elk-vue-lynx-port-rh9gpd`.
+
+- [ ] **Step 6: Confirm deployment artifacts**
+
+Wait for every PR check and Vercel deployment to pass, then confirm HTTP 200 for:
+
+```text
+https://vue-lynx-git-claude-elk-vue-lynx-port-rh9gpd-huxpros-projects.vercel.app/examples/elk/dist/main.lynx.bundle
+```
diff --git a/docs/superpowers/plans/2026-07-14-example-benchmark-navigation.md b/docs/superpowers/plans/2026-07-14-example-benchmark-navigation.md
new file mode 100644
index 00000000..02412c35
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-14-example-benchmark-navigation.md
@@ -0,0 +1,126 @@
+# Example Benchmark Navigation Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Move Elk under the website Benchmark section after AI Chat and relocate both examples' AI design context into their owning example directories.
+
+**Architecture:** Integrate current `origin/main` into the published PR #196 branch with a merge commit, preserving AI Chat while keeping the PR history stable. A small Node source-contract test owns the bilingual sidebar ordering and example-local `.impeccable.md` invariants; Rspress and Elk production builds provide integration coverage.
+
+**Tech Stack:** Git, Node.js test runner, TypeScript Rspress config, pnpm, Rspeedy.
+
+---
+
+## File Map
+
+- `website/scripts/benchmark-navigation.test.mjs`: regression contract for bilingual ordering, Showcase removal, and example-local design files.
+- `website/package.json`: exposes the focused navigation test command.
+- `website/rspress.config.ts`: bilingual Benchmark sidebar entries.
+- `examples/elk/.impeccable.md`: Elk-specific design context moved from the repository root.
+- `examples/ai-chat/.impeccable.md`: AI Chat-specific design context moved from the repository root after merging `main`.
+- `docs/superpowers/plans/2026-07-14-example-benchmark-navigation.md`: this execution record.
+
+### Task 1: Lock the ownership and navigation contract
+
+**Files:**
+- Create: `website/scripts/benchmark-navigation.test.mjs`
+- Modify: `website/package.json`
+
+- [ ] **Step 1: Add the focused test command**
+
+Add `"test:navigation": "node --test scripts/benchmark-navigation.test.mjs"` to `website/package.json`.
+
+- [ ] **Step 2: Write the failing source-contract test**
+
+The test reads `website/rspress.config.ts`, asserts the English and Chinese sequences contain `HackerNews`, `AI Chat`, then Elk; asserts no Showcase section remains; and verifies `examples/elk/.impeccable.md` plus `examples/ai-chat/.impeccable.md` exist while the root `.impeccable.md` does not.
+
+- [ ] **Step 3: Run the test and verify RED**
+
+Run: `pnpm --dir website test:navigation`
+
+Expected: FAIL because both example-local context paths and the merged AI Chat + Elk sidebar order do not yet exist.
+
+### Task 2: Move Elk context before integrating main
+
+**Files:**
+- Delete: `.impeccable.md`
+- Create: `examples/elk/.impeccable.md`
+
+- [ ] **Step 1: Move the Elk design context without changing its contents**
+
+Use `apply_patch` to add the existing content at `examples/elk/.impeccable.md` and delete the root file.
+
+- [ ] **Step 2: Commit the conflict-prevention move and test harness**
+
+```bash
+git add website/package.json website/scripts/benchmark-navigation.test.mjs .impeccable.md examples/elk/.impeccable.md docs/superpowers/plans/2026-07-14-example-benchmark-navigation.md
+git commit -m "test(website): lock benchmark navigation ownership"
+```
+
+### Task 3: Integrate main and localize AI Chat context
+
+**Files:**
+- Merge: `origin/main`
+- Delete: `.impeccable.md`
+- Create: `examples/ai-chat/.impeccable.md`
+
+- [ ] **Step 1: Merge the current main branch**
+
+Run: `git merge --no-edit origin/main`
+
+Expected: merge succeeds because Elk's conflicting root context was moved first; AI Chat and current website dependencies enter the PR branch.
+
+- [ ] **Step 2: Move AI Chat's design context without changing its contents**
+
+Use `apply_patch` to add the merged root content at `examples/ai-chat/.impeccable.md` and delete the root file.
+
+### Task 4: Put Elk after AI Chat in both sidebars
+
+**Files:**
+- Modify: `website/rspress.config.ts`
+
+- [ ] **Step 1: Update the English Benchmark list**
+
+Keep `AI Chat` and add `{ text: 'Elk (Mastodon Client)', link: '/guide/elk' }` immediately after it. Remove the Showcase divider/header block.
+
+- [ ] **Step 2: Update the Chinese Benchmark list**
+
+Keep `AI Chat` and add `{ text: 'Elk(Mastodon 客户端)', link: '/zh/guide/elk' }` immediately after it. Remove the 案例展示 divider/header block.
+
+- [ ] **Step 3: Run the focused test and verify GREEN**
+
+Run: `pnpm --dir website test:navigation`
+
+Expected: all navigation and ownership assertions pass.
+
+- [ ] **Step 4: Commit the integrated result**
+
+```bash
+git add . website/rspress.config.ts examples/ai-chat/.impeccable.md
+git commit -m "feat(website): classify Elk as a benchmark"
+```
+
+### Task 5: Verify and publish PR #196
+
+**Files:**
+- Verify: `website/dist/`
+- Verify: `examples/elk/dist/main.lynx.bundle`
+
+- [ ] **Step 1: Run focused and production verification**
+
+```bash
+pnpm --dir website test:navigation
+pnpm --dir website build
+pnpm --dir examples/elk test:native-compat
+pnpm --dir examples/elk build
+git diff --check
+```
+
+Expected: all commands exit 0; the website and Lynx bundle build successfully.
+
+- [ ] **Step 2: Push the existing PR head**
+
+Run: `git push origin HEAD:claude/elk-vue-lynx-port-rh9gpd`
+
+- [ ] **Step 3: Verify remote deployment**
+
+Wait for `gh pr checks 196 --repo Huxpro/vue-lynx --watch --interval 10`, then request the Vercel `/guide/elk` page and `/examples/elk/dist/main.lynx.bundle`. Confirm both return HTTP 200 and the PR is mergeable against `main`.
diff --git a/docs/superpowers/specs/2026-07-13-elk-navigation-sheet-design.md b/docs/superpowers/specs/2026-07-13-elk-navigation-sheet-design.md
new file mode 100644
index 00000000..aaeb9423
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-13-elk-navigation-sheet-design.md
@@ -0,0 +1,102 @@
+# Elk Navigation Sheet Design
+
+## Goal
+
+Bring the Vue Lynx Elk mobile navigation to behavioral and visual parity with
+the upstream `elk-zone/elk` bottom navigation and More sheet on Lynx Web and
+native iOS Lynx Explorer.
+
+## Reference behavior
+
+The reference is upstream Elk commit `d444a59`, run locally with the mocked
+development data at a 390 × 844 viewport. Guest navigation contains Explore,
+Local, Federated, and More. Opening More leaves the bottom bar fixed, changes
+the More icon to the primary-colored close icon, dims the timeline, and raises
+a rounded sheet above the bar. The sheet height is capped so roughly 200 px of
+the viewport remains outside the panel. Its content scrolls independently and
+can be dismissed through the backdrop, the close button, navigation, or a
+downward drag.
+
+The gesture refinement also follows the current `lynx-ui` Sheet implementation
+from `origin/main` (package version 3.134.0). Its relevant behaviors are an 8 px
+direction-lock threshold, angle-based gesture claiming, a nonlinear rubber-band
+at the fully-open boundary, velocity-projected dismissal, motion that stays on
+the main thread, and backdrop opacity derived from sheet progress.
+
+## Considered approaches
+
+1. Copy only the upstream CSS transition into `NavBottom.vue`. This is small,
+ but couples panel state, navigation, and drag behavior into one component
+ and makes native gesture work happen on the background thread.
+2. Depend directly on `@lynx-js/lynx-ui-sheet`. This preserves the official
+ primitive, but it is a ReactLynx package whose hooks and context cannot be
+ consumed by Vue Lynx.
+3. Port the official primitive's state and gesture model into a reusable Vue
+ Lynx component, then style its content for Elk. This keeps the sheet generic,
+ gives native drag work to main-thread worklets, and lets Elk own its exact
+ navigation appearance. This is the selected approach.
+
+## Architecture
+
+`components/sheet/Sheet.vue` owns controlled visibility, backdrop dismissal,
+mount/unmount transitions, and drag-to-dismiss. It exposes a default slot and
+keeps visual surface choices configurable through classes and CSS. A dedicated
+28 px handle is always draggable. The scrollable content also accepts a
+downward drag only while its scroll position is at the top; upward gestures and
+content gestures that start while scrolled remain owned by the scroll view.
+
+Main-thread touch handlers direction-lock after 8 px, sample drag velocity,
+update the panel, handle, rubber fill, and backdrop together, and settle using
+a short requestAnimationFrame spring. The background thread is contacted only
+after a dismiss animation finishes. A small pure helper module owns angle
+classification, nonlinear rubber resistance, velocity filtering, progress,
+and dismissal decisions so the behavior can be covered by Node tests without
+rendering Lynx.
+
+`NavBottom.vue` owns Elk-specific destinations, active/disabled state, menu
+rows, theme and Zen Mode actions, and route navigation. It renders the sheet as
+a root-level fixed overlay whose bottom inset includes the persistent bar and
+the Sparkling safe area. Guest and authenticated tab sets mirror upstream Elk
+where the current port has matching routes.
+
+## Interaction and visual details
+
+- Backdrop opacity enters with the sheet and uses Elk's black 50% scrim.
+- Panel entry is 250 ms ease-out; direct backdrop/route exit is 188 ms ease-in.
+- A visible 36 × 4 px grabber sits inside a 28 px touch target at the top edge.
+- The panel has Elk's 8 px top radius, 1 px top border, translucent themed
+ surface, and independent vertical scrolling.
+- Rows use 20 px horizontal padding, 40 px minimum height, 20 px icons, and the
+ upstream active, disabled, and primary colors.
+- Pressed rows and bottom tabs use short transform/opacity feedback.
+- The handle claims vertical drags in either direction after 8 px. Content only
+ claims a downward drag when `scrollTop` is zero, preserving natural scrolling.
+- Pulling upward past the fully-open position uses the same asymptotic
+ rubber-band equation as `lynx-ui` with coefficient 0.5 and an 80 px cap.
+- Pulling downward follows the finger. Release dismisses after the distance
+ threshold or after a deliberate minimum-distance fast fling; otherwise it
+ springs back to rest without a background-thread round trip.
+- Backdrop opacity follows open progress during drag and settle, rather than
+ remaining fully dark until dismissal completes.
+- A cancelled gesture always springs to the open position and clears captured
+ direction/velocity state.
+- Route changes, backdrop taps, and enabled row taps close the sheet. Disabled
+ items do not navigate or close it.
+- The existing Sparkling safe-area spacers remain outside the overlay, so the
+ fixed bar and panel share the same safe content on iOS.
+
+## Testing and verification
+
+Tests first cover direction locking, hybrid content/handle ownership,
+rubber-band limits, velocity filtering, distance/fling dismissal, backdrop
+progress, menu structure, main-thread bindings, and the persistent More/close
+state. Verification then runs the Elk native compatibility suite, the example
+build, the repository checks affected by Vue SFC worklets, the Vercel mobile
+preview, and real slow/fast/cancelled drags on the native bundle in iOS Lynx
+Explorer with cache and app restarts.
+
+## Scope
+
+This change ports the Sheet primitive locally for the Elk example. It does not
+publish a new public `vue-lynx` UI package or add unsupported Elk pages merely
+to make disabled menu entries clickable.
diff --git a/docs/superpowers/specs/2026-07-14-example-benchmark-navigation-design.md b/docs/superpowers/specs/2026-07-14-example-benchmark-navigation-design.md
new file mode 100644
index 00000000..9a71e516
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-14-example-benchmark-navigation-design.md
@@ -0,0 +1,38 @@
+# Example Benchmark Navigation Design
+
+## Goal
+
+Classify Elk as a Vue Lynx benchmark beside the other production-scale ports,
+and keep AI-facing design context owned by the example it describes.
+
+## Website Navigation
+
+- Keep the existing Benchmark order: TodoMVC, 7GUIs, HackerNews, AI Chat.
+- Add Elk immediately after AI Chat in both English and Chinese sidebars.
+- Remove the now-empty Showcase sidebar section.
+- Keep the Elk card in the home-page “Try it for yourself” area because that
+ area is an interactive entry point rather than the sidebar classification.
+
+## Design Context Ownership
+
+- Move Elk's root `.impeccable.md` to `examples/elk/.impeccable.md`.
+- Move AI Chat's root `.impeccable.md` from current `main` to
+ `examples/ai-chat/.impeccable.md` while integrating `main` into PR #196.
+- Leave no example-specific `.impeccable.md` at the repository root.
+- Preserve each file's contents; only its ownership and location change.
+
+## Integration Strategy
+
+Merge current `origin/main` into the PR #196 branch instead of rebasing its
+published history. Resolve the add/add root `.impeccable.md` conflict by placing
+both versions in their owning example directories. Preserve AI Chat and its
+website entry from `main`, then place Elk after it.
+
+## Verification
+
+- Add a source-level regression check for both localized sidebar orders and the
+ absence of a Showcase section.
+- Assert that each example owns its `.impeccable.md` and the root file is gone.
+- Build the website and Elk native bundle.
+- Push the merge and navigation changes to PR #196, then verify Vercel Preview
+ and the deployed native bundle.
diff --git a/.impeccable.md b/examples/ai-chat/.impeccable.md
similarity index 100%
rename from .impeccable.md
rename to examples/ai-chat/.impeccable.md
diff --git a/examples/elk/.impeccable.md b/examples/elk/.impeccable.md
new file mode 100644
index 00000000..daa07b02
--- /dev/null
+++ b/examples/elk/.impeccable.md
@@ -0,0 +1,21 @@
+## Design Context
+
+### Users
+
+Vue Lynx contributors and mobile developers use the Elk example to evaluate whether a Vue application can feel credible on both Lynx Web and native Lynx hosts. The interface should support familiar Mastodon navigation without asking users to relearn the upstream Elk interaction model.
+
+### Brand Personality
+
+Faithful, calm, and finely crafted. The example should feel like Elk itself rather than a generic demo, while native interactions should feel direct and physically coherent on iOS.
+
+### Aesthetic Direction
+
+Treat upstream `elk-zone/elk` as the visual and interaction reference for mobile web, and current Lynx UI primitives as the native-behavior reference. Support both light and dark themes. Prefer restrained surfaces, precise spacing, clear iconography, and subtle state transitions over decorative effects.
+
+### Design Principles
+
+- Preserve upstream Elk hierarchy, navigation semantics, spacing, and theme behavior wherever Vue Lynx supports them.
+- Keep direct manipulation responsive on the main thread; motion should explain state changes rather than decorate them.
+- Adapt deliberately for native safe areas, scrolling, and touch ownership without changing the recognizable Elk experience.
+- Favor small, testable primitives and measurable interaction rules over one-off visual approximations.
+- Verify fidelity on mobile web and real Lynx native hosts, including intermediate gesture states—not only final screenshots.
diff --git a/examples/elk/PORTING.md b/examples/elk/PORTING.md
new file mode 100644
index 00000000..04c16c1e
--- /dev/null
+++ b/examples/elk/PORTING.md
@@ -0,0 +1,160 @@
+# PORTING.md — how Elk became Elk on Lynx
+
+This example ports [Elk](https://github.com/elk-zone/elk) (a Nuxt 3 Mastodon
+web client, ~196 components / 55 pages / 50 composables) to **Vue Lynx**.
+Elk was **not forked**: Vue Lynx has no Nuxt (no SSR, file routing, Nitro,
+auto-imports), no DOM, and different primitives. Instead, Elk's
+framework-agnostic layers were extracted and its UI rebuilt on Lynx
+elements. The feature-level status lives in [PRD.md](./PRD.md); this file
+explains *what* was reused vs rewritten and *why*.
+
+## Architecture
+
+```
+Elk Shared (reused) Elk Lynx (rebuilt)
+├── masto.js API client ←→ src/composables/masto.ts (thin de-Nuxt wrapper)
+├── content parse pipeline ←→ src/composables/content-parse.ts (near-verbatim)
+├── content render walk ←→ src/composables/content-render.ts (Lynx retarget)
+├── paginator state machine ←→ src/composables/paginator.ts (Lynx trigger)
+├── timeline filters/reorder ←→ src/composables/timeline.ts (verbatim)
+├── status action optimistic logic ←→ src/composables/status-actions.ts (verbatim)
+├── search composable ←→ src/composables/search.ts (near-verbatim)
+├── LRU cache ←→ src/composables/cache.ts (Map-based LRU)
+├── account helpers ←→ src/composables/account.ts (verbatim)
+└── theme palette (vars.css) ←→ src/styles/theme.css (verbatim values)
+
+Elk Web (Nuxt + DOM components) → not forked; templates rebuilt in src/components|pages
+```
+
+## Reused from Elk (and how much changed)
+
+| Elk source | Ported to | Changes |
+| --- | --- | --- |
+| `masto` npm package (REST client) | dependency | none — same `createRestAPIClient` |
+| `app/composables/content-parse.ts` | `src/composables/content-parse.ts` | ~95% verbatim: sanitizer allow-list, custom-emoji/markdown/named-mention/collapse-mention transforms, `treeToText`. Dropped: twemoji unicode-emoji transforms (native glyphs render already), ``/`dir=auto` (no bidi element), `new URL` protocol check → regex (no URL in Lynx runtime) |
+| `app/composables/content-render.ts` | `src/composables/content-render.ts` | same AST walk + mention/hashtag/code special-casing; **output retargeted** (see below) |
+| `app/composables/paginator.ts` | `src/composables/paginator.ts` | masto `Paginator` iteration, buffering, state machine kept; DOM trigger (`useElementBounding` + `window.innerHeight` polling) → native `` `scrolltolower`; streaming-prepend removed (no WebSocket) |
+| `app/composables/timeline.ts` | `src/composables/timeline.ts` | verbatim (filters, `reorderTimeline`) |
+| `app/composables/masto/status.ts` | `src/composables/status-actions.ts` | verbatim optimistic-update logic incl. the cancel-count API-quirk workaround; `navigateTo` → vue-router |
+| `app/composables/masto/search.ts` | `src/composables/search.ts` | VueUse `debouncedWatch` inlined; `isHydrated` SSR guard dropped |
+| `app/composables/cache.ts` | `src/composables/cache.ts` | `lru-cache` dep → 20-line Map LRU; same keys/API |
+| `app/composables/masto/account.ts` | `src/composables/account.ts` | verbatim handle/display-name helpers |
+| `app/composables/users.ts` | `src/composables/users.ts` | Elk's guest-mode (`publicServer`) design kept; multi-account persistence dropped (browser storage) |
+| `app/styles/{vars,default-theme}.css` | `src/styles/theme.css` | same palette values as Lynx CSS vars on `page` |
+| RemixIcon set (Elk's UnoCSS `i-ri:*`) | `src/composables/icons.ts` | same glyphs, inlined as SVG data-URIs for `` (no icon-font/currentColor in Lynx) |
+
+## Rebuilt for Lynx (and why)
+
+### Templates: every one of them
+
+DOM elements don't exist in Lynx. `
`→``, `/
`→``,
+``→``, `@click`→`@tap`, CSS via Lynx's subset (flexbox, no
+`:hover`). ~20 components in `src/components` + 8 pages in `src/pages`
+reimplement Elk's surfaces following the originals' layout
+(`StatusCard` ≈ Elk `status/StatusCard.vue` + `StatusBody` + `StatusContent`,
+`TimelinePaginator` ≈ `timeline/TimelinePaginator.vue` + `common/CommonPaginator.vue`, …).
+
+### The content renderer (the crown jewel)
+
+Elk's pipeline is: Mastodon HTML → `ultrahtml` AST → sanitize → transforms →
+Vue vnodes. The **parse half is reused intact**. The **render half changes
+its output targets**:
+
+| Elk emits | Lynx port emits |
+| --- | --- |
+| `h('p', …)` | `h('text', { class: 'content-p' })` block |
+| `h('a', …)` / `RouterLink` | nested `` run with `onTap` → router |
+| `AccountHoverWrapper` (floating-vue hover card) | tap → profile navigation (no hover on touch) |
+| `` custom emoji | inline `` (static variant) |
+| `ContentCode` (Shiki highlight) | plain mono `` block (Shiki's WASM/regex engine unsuited to the Lynx runtime) |
+| ``, `dir="auto"`, `` | dropped (no equivalents) |
+
+Inline styling (bold/italic/del/code…) becomes nested `` runs with
+classes — Lynx supports inline text nesting natively.
+
+### Virtualized timeline
+
+Elk uses `virtua`'s DOM `WindowVirtualizer`. Lynx's native `` is
+already a recycling virtualized scroller, so the port's timeline is a
+`` with `estimated-main-axis-size-px` hints and
+`lower-threshold-item-count` + `scrolltolower` driving `loadNext()` —
+*less* code than the DOM version.
+
+### Navigation
+
+Nuxt file routing → explicit `vue-router` table on `createMemoryHistory`
+(no browser History API). Elk's route shapes are preserved
+(`/:server/@:account`, `/:server/status/:id`, `/:server/tags/:tag`, …) so
+the content renderer's mention/hashtag rewrites work unchanged.
+
+### Sessions
+
+Elk's OAuth needs its Nitro server (`server/api/[server]/login.ts`
+registers an OAuth app, browser redirects to `/oauth/authorize`). A Lynx
+app has neither a bundled server nor browser redirects, so the port
+supports Elk's **anonymous guest mode** (default) plus **manual
+access-token sign-in** in Settings. Multi-account storage
+(localStorage/IndexedDB) is out — one session per launch.
+
+## Lynx-specific landmines (worth knowing)
+
+1. **`fetch` lives in different scopes across targets.** Native Lynx injects
+ it into RuntimeWrapperWebpackPlugin's outer wrapper, while Lynx for Web
+ exposes `globalThis.fetch`. `src/polyfills.ts` selects the callable one
+ and mirrors it to both scopes before masto runs. Other free-identifier web
+ constructors use targeted `source.define` rewrites (no masto patches).
+2. **No DOMParser anywhere** (worker or native). Elk's `tiny-decode`
+ entity decoder uses DOMParser in its browser build — replaced with a
+ 30-line table+numeric decoder (`html-entities.ts`), sufficient for
+ Mastodon's sanitized output.
+3. **The native runtime is missing more than `URL`.** Per
+ `@lynx-js/types`, the native background thread guarantees only `fetch`,
+ `Request`, `Response` and timers. masto.js additionally calls
+ `AbortSignal.any` (unconditionally, on every request — this alone
+ turned every native request into "Failed to load timeline"),
+ `new Headers(...)`, `new URL(path, base)`, and `DOMException` for its
+ error path. `src/polyfills.ts`
+ installs fill-if-missing implementations before anything else runs;
+ on Lynx for Web the real worker APIs win and the native-only shims are
+ inert.
+4. **PrimJS cannot parse Unicode-property regexes.** `change-case@5`, pulled
+ by masto.js, contains `\p{L}`/`\p{Lu}`/`\p{Ll}` expressions that abort the
+ entire bundle before Vue mounts. The build aliases it to an ASCII adapter;
+ Mastodon action names and response keys are ASCII by definition.
+5. **The native image element has no SVG decoder.** Icons as
+ `data:image/svg+xml` `` sources render blank on device; the
+ built-in `