> ## Documentation Index
> Fetch the complete documentation index at: https://docs.acondawayuno.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Content Studio

> Edit bilingual content, media, cards, scene objects, and staged GLBs, then save deterministic project files for GitHub deployment.

Content Studio is the local visual authoring surface for The Living Index. It
previews changes immediately, preserves an authoring draft in the current
browser, and writes deterministic source-controlled files only when you select
**Save to project**.

Content Studio 是本地可视化编辑界面。实时预览、浏览器草稿和项目文件是三个不同层级，
只有点击 **保存到项目** 才会产生可提交的代码产物。

## Open the Studio / 打开工作台

```bash theme={null}
npm run dev
```

Open the loopback URL reported by Vite and add `?studio=1`, for example:

```text theme={null}
http://localhost:3000/?studio=1
```

The write and upload endpoints exist only in the local Vite server. They accept
loopback hosts (`localhost`, `127.0.0.0/8`, or `::1`) and are not included in a
production build. A deployed `?studio=1` page may preview or export, but it
cannot write repository files.

## The three editor sections / 三个编辑区域

### Profile / 个人资料

Edit localized identity and opening-screen copy, including display name, logo
initial, subtitle, eyebrow, title, description, quote, city, and author
timezone.

`profile.timezone` is the author's clock, not the visitor's device timezone.
Use an IANA zone such as `Asia/Shanghai` when daylight-saving behavior matters,
or a fixed offset such as `GMT+8`.

The mirror/About chapter also supports:

* a profile portrait uploaded by drag-and-drop or file picker;
* localized alternative text;
* the About chapter introduction as the personal biography; and
* up to 12 social links with platform-specific icons and optional localized
  labels.

### Digital assets / 数字资产

Choose an enabled or disabled built-in chapter and edit its bilingual label,
title, trait, teaser, introduction, status, last-updated value, note, metrics,
and content cards.

Cards share one schema:

| Kind  | Contents                                                                 |
| ----- | ------------------------------------------------------------------------ |
| Text  | Eyebrow, title, body, and metadata                                       |
| Media | Text fields plus an uploaded image and alt text                          |
| Links | Text fields plus up to four labeled `http`, `https`, or `mailto` buttons |

Each card may be standard, wide, or full width and can be reordered. A chapter
may contain up to 24 cards.

For photography, every configured photo remains visible. Selecting a photo
opens its instant-photo dialog with description text. **Spotlight** does not
hide the other photos; it additionally gives the selected photo a large feature
position and dedicated editorial introduction column.

### Scene layout / 场景布局

The scene editor can:

* move, rotate, and scale built-in objects;
* disable and later reactivate a built-in object;
* attach or remove a GLB visual replacement while preserving the native object;
* create a decorative custom asset with no content page;
* create an interactive custom asset with localized cards and a deep link; and
* upload and place a custom GLB.

Open scene preview, select **Drag placement**, then switch between:

* **Plane** for X/Z;
* **Height** for Y; and
* **Heading** for rotation around Y. Hold Shift to snap to 15°.

Scale remains a numeric three-axis control.

<Warning>
  A drag is only temporary `scenePlacementEdit` state. Select **Confirm all
  placement** to merge it into the Studio document. Save, import, export, and
  copy are blocked while a placement is active.
</Warning>

## Draft, preview, and project files / 草稿、预览与项目文件

The word “draft” refers to:

```text theme={null}
localStorage["living-index.content-draft.v1"]
```

It contains the current editable content document and persists across refreshes
in the same browser. It is read and updated only on `?studio=1`. Normal `/`
pages ignore it and always render source-controlled published content.

Use **Reset draft** when you want the Studio to discard this browser copy and
return to the project version. Reset does not alter Git history.

Text fields keep the raw value while you type, including a trailing space.
Import and project save validate and normalize the complete document.

## Media and GLB lifecycle / 媒体与 GLB 生命周期

Images are validated and written immediately:

| Media            | Directory                     |
| ---------------- | ----------------------------- |
| Profile portrait | `public/uploads/profile/`     |
| Photography      | `public/uploads/photography/` |
| Card image       | `public/uploads/cards/`       |

The JSON stores only same-origin public paths, never base64 data. Image uploads
do not use the GLB staging transaction, so inspect `git status` and remove
unwanted, unreferenced image files before committing.

GLBs use a safer two-phase lifecycle:

1. Upload validation writes the file to
   `.content-studio-cache/models/<uuid>.glb`.
2. Preview uses a future-looking `/uploads/models/<uuid>.glb` URL. The local
   server resolves it from the cache, and an in-memory prepared buffer can make
   the first preview faster.
3. Removing or replacing the model requests immediate staged-file cleanup.
4. **Save to project** promotes only models referenced by the final document to
   `public/uploads/models/`.
5. The save removes previously published GLBs no longer referenced and clears
   all remaining staged GLBs.

The staging directory is Git-ignored. A staged model is not a deployable
artifact.

## What Save to project writes / 保存产生什么

The browser normalizes the document and sends a local-only `PUT` request to
`/__content-studio/save`. The development plugin validates it, resolves the
final model-reference set, and performs one save transaction:

```text theme={null}
public/content/site-content.json   complete content document
app/generated/scene-config.ts      generated production scene snapshot
public/uploads/models/*.glb        only final referenced GLBs
```

The two text files use atomic writes. If model promotion or either write fails,
the plugin restores the previous files and returns promoted models to staging.
Cleanup happens only after the new source files are safely written.

`site-content.json` contains profile overrides, chapter overrides, media,
social links, and the scene document. `scene-config.ts` contains only the
normalized `scene` branch and is bundled as the production scene authority.
Tests require the two scene values to be identical.

<Note>
  JSON **Export**, **Copy**, and **Import** operate on the document only. They
  neither transport binary uploads nor generate `app/generated/scene-config.ts`.
  They are not substitutes for a successful project save.
</Note>

## Runtime load order / 页面如何读取

On a normal route:

1. built-in defaults come from `app/portfolio-data.ts`,
   `app/portfolio-data-en.ts`, and `app/content-config.ts`;
2. the application fetches `public/content/site-content.json`;
3. profile, chapter, and media overrides merge with the defaults;
4. the bundled `app/generated/scene-config.ts` replaces the JSON scene branch;
5. active built-ins and custom assets are rendered.

On `?studio=1`, the same published baseline loads first. A valid browser draft
may then replace it for authoring preview.

## Commit and publish / 提交与发布

Saving changes local files; it does not create a Git commit or deploy.

```bash theme={null}
git status --short
npm run typecheck
npm run lint
npm test
git add public/content/site-content.json app/generated/scene-config.ts public/uploads
git commit -m "Update portfolio content and scene"
git push origin main
```

Commit only the uploads reported by `git status`. The push starts the repository
GitHub Actions workflow. It installs locked dependencies, typechecks, lints,
builds, runs the smoke tests—including scene JSON/generated-source
consistency—and deploys the Cloudflare Worker only after quality succeeds.

See [Scene layout](/content/scene-layout), [GLB
models](/customization/glb-models), and [GitHub
Actions](/deployment/github-actions).
