DESIGN.md · git:20261013.a44a134 · 2026-10-13 · sha256 98ca10664d1664e1

DESIGN.md git:20261013.a44a134A

Immutable. This exact content never changes and is served at /api/v1/blob/98ca10664d1664e1.

# Design
## Source of truth
Status: Active for the local homepage upgrade, 2026-10-03. Reference: public desktop/mobile captures of https://hypit.ai/, https://hypit.ai/quickstart/ and https://hypit.ai/docs/; current zenstory.ai captures; four project READMEs and their immutable video sources. Scope: the organization site (zenstory.ai): home, /projects, /guides, six project pages, practical guides, /compare, glossary, each in English at the root and in 中文 under `/zh`, plus the workbench docs at `/docs`. Implementation: `apps/web/scripts/site-shell.mjs` (head metadata, header, footer, language model shared by every static page), `apps/web/scripts/build-org-pages.mjs` (organization pages, run once per language), `apps/web/scripts/build-docs-pages.mjs` (workbench docs on the same shell, sidebar from `src/data/docsNavigation.ts`) and `apps/web/scripts/org-pages.css` (plain CSS, copied to `/org/org.css`). Content is read from `apps/web/content/*.json` and `apps/web/docs/**/*.md`; article and guide content is preserved while homepage curation is maintained separately. The React workbench and admin are governed by the scoped decisions below, updated 2026-10-07.
## Brand
An open-source story studio that reads as edited and credible. The palette comes from the brand mark: navy `#081431` is the ink for headings, primary buttons and the footer band; cream paper `#faf7f0` with `#f3ecdb` chapter bands; cyan `#22D3EE` only for tiny accents (star glyph, active-nav underline, dark-mode focus ring); teal `#0d6b7c` (dark `#67e3f5`) for link hover and small accents. Since 2026-10-10 every page uses the homepage's tokens (paper `#f5f1e7`, ink `#0a0f1f`, the same dark palette); the earlier orange accent is retired. Dark scheme maps the same roles. Reuse `public/brand/zenstory-ai-mark.svg`; social preview is `public/brand/og-zenstory-ai.png` (1200×630). No stock imagery, gradients, badges, testimonials or invented metrics.
## Product goals
Bring the organization homepage’s design, product demonstration and knowledge structure up to the presentation quality of Hypit, while keeping ZenStory’s own brand and factual story-creation scope. Real results appear before a catalog of tools. A visitor can see a demo, understand its input/output, choose a task and follow a practical method. SEO and GEO are acceptance criteria, not promises of ranking or AI citations. No article-count target, invented trust logos, promotional statistics, copied Hypit art or misleading cross-project continuity.
Success signals: real artifact visible at 1440×900 and begins inside 390×844; four task families covered by sourced examples; three input-based paths; four knowledge layers; complete bilingual static HTML; no broken key links or serious/critical axe violations. Search traffic and citation effects require external measurement and are not inferred from screenshots.
## Personas and jobs
Chinese web-fiction authors arriving from an AI answer on a phone; short-drama, game and video creators; English Claude Code users looking for non-coding skills; people who want the browser workbench. They compare formats and install surface before choosing.
## Information architecture
Home → real examples → input-based creative paths → tools and working environments → four knowledge layers → project relationships. Each section answers one question: what a result looks like, what to do with the material you have, which tool and where to run it, where to look something up, how the projects connect. Four skill packs create or adapt; DSH and the browser workbench are separate working surfaces, not extra stages of the same output. The showcase does not repeat the hero's case: four independent example cards cover the Oh Story product video, short-drama sample, finished recap and a desktop 3D game capture (the playable RPG is the hero stage; its overworld screenshot illustrates the game branch of the manuscript path). They do not represent one story moving through all four tools.
Each example exposes input, output, a same-language method, immutable README/source and a direct playback/prototype link. The hero follows one verified case from source to result — Journey to the West chapter 59 (the public-domain source file), a rule from the Novel to Game source bible, and the playable prototype (browser QA capture plus the game's own character art), all pinned to the owner repository — so a real result is complete inside 1440×900 and visible inside 390×844. The largest showcase demonstrates the writing workflow (poster from 23 s of the real session). Creative tools and working environments have separate visual groups. The homepage carries three action-labeled paths (idea / manuscript / footage) and four layers (get started / whole workflows / techniques and templates / revision and reference). `/guides` retains the complete ten-task taxonomy, optional search and paginated corpus, and reads in the same four layers in order (get started → complete workflows → go deeper by task → revise and look up) with a layer jump row under its search. Its four complete workflows are grouped by starting material (idea; novel → short drama; novel → game; footage); each step page says “workflow · step n/4” at the top and links the next step at the end. Every guide and article opens with the skill its method comes from and its first method files at the pinned commit; workflow steps and list rows name the skill. Search ranks title matches above descriptions, counts topic names only for words nothing else matches, folds in question phrasing and a few writer synonyms, and indexes the glossary terms. A glossary term links the article that teaches it (`how_to` in `glossary.json`). Project pages lead into their craft articles through the project's own complete workflow and task tiles (article count per task, linking the task page) instead of listing every article; task pages and the `/guides` search index list them all. Task pages keep “Start here” with descriptions; below it rows are compact (title and method files), and a task whose articles span at least two skills with three or more each groups them under the skill's reader-facing name (`content/skill-labels.json`, written from each skill's own description) with its pinned SKILL.md, step-by-step guides first and smaller skills under “other methods”. A source labelled only “method used in this article” is shown by its file name. Do not print all articles on home.
Main nav is Projects · Guides · GitHub · EN/中文 · Open app (the glossary left it on 2026-10-09 and is reached from the footer, the homepage's revision-and-reference layer, the end of `/guides` and each term's own article); on home the one primary hero action goes to the creative paths (`#start-h`), the hero caption links the stage case to its own project setup (`/novel-to-game#start-h`) and to the examples, the showcase ends with a pill on the dashed story thread that leads on to the paths, and each section opens with a star-marked eyebrow. Each creative path names the tool it starts with and links to that tool's row; a path the browser workbench can start (the idea path) also ends with one direct signup action for visitors with no agent to install, and the manuscript path's two adaptation tiles show their tool's first showcase result (the same local poster or screenshot); each tool row links to its project page's install-and-entry steps; each knowledge layer's more-link is labelled with where it lands. Existing project, article, glossary, guide and comparison URLs are retained. Workbench docs remain a separate product’s `/docs` corpus, reached from `/workbench` and footer.
## Language delivery
One language per URL. The organization generator runs once per language; `t(en, zh)`, `pick(field)`, `pair()` and `heading()` render the current language only, and `<html lang data-lang>` is set at build time, so nothing depends on a script or on `localStorage`. Each page carries `<link rel="alternate" hreflang>` for en, zh-CN and x-default (English), `og:locale` with its alternate, JSON-LD `inLanguage` for its own language, and the sitemap repeats the pair on both entries. The header switch is a link to the counterpart page; clicking it also sets a `zs_lang` cookie (one year, host-only), and only the organization home reads it back: Vercel redirects a returning visitor from `/` to `/zh` (or `/zh` to `/`) with a temporary redirect when the cookie names the other language. No other URL redirects, there is no Accept-Language guessing, and crawlers, which carry no cookie, always get the page they asked for. Title, description, headline and breadcrumb names are in the page language so Chinese engines match Chinese query vocabulary (网文写作 skill 包, 去AI味, 剪映草稿) in the title itself. On Chinese pages internal links to organization routes are re-pointed at `/zh/...` and per-element `lang="zh-CN"` markers are dropped; English pages keep them on Chinese-only fragments (glossary terms). Workbench docs and legal pages are exceptions: a single URL with the 中文 article first and the English article below, both anchored, and the switch points at the anchors.
## Design principles
Preserve crawler metadata, language separation, sources and public URL contracts. Homepage headings and layouts may change for the new goal; update obsolete text assertions while strengthening behavior/SEO contracts. Preserve assertions for unrelated page behavior. One type system, one measure, disciplined color. Reduce link density: prose links are ink with a hairline underline; navigational lists and card titles are ink 600 without underline; orange text only for eyebrows and buttons. Show the same object the same way on every page (one card component, one guide list, one proof chip).
## Visual language
Homepage (`<body data-page="home">`, scoped block at the end of org-pages.css): "manuscript to world". The page is manuscript paper (`#f5f1e7`), the stage and the showcase/closing bands are the ink-navy world, a dashed cream story thread connects things and the mark's cyan star is the only node or bullet. One family — Geist with the system CJK sans, Geist Mono for paths and commands (loaded only on home via `HOME_FONTS`); the quoted source text is set vertically in a CJK serif. Media share one frame recipe (12px radius, hairline, shadow); a vertical video sits over a blurred copy of its poster, never in a device mock. Rhythm: paper hero → navy showcase → paper tools table / uneven path grid / library → navy closing into the footer. Every other page shares the same tokens, type, header, buttons, star-led eyebrows and radius family (2026-10-10); only the homepage's own layouts (stage, navy bands) stay home-only.
Body and headings: Geist with the system CJK sans stack for both languages, 16px/1.65 (ZH body 1.75), headings 600 with tight negative tracking (h1 clamp 34–52px, h2 26px with 56px top margin and no rule, h3 19px); eyebrows are 12px Geist Mono muted labels led by the cyan star; mono: Geist Mono for paths and commands. One Google Fonts stylesheet (Geist + Geist Mono) loads non-render-blocking (preload + print-swap + noscript) on every page. Measure 720px shared by both languages; page wrap 1120px; 24px gutters; cards 1px borders, 16px radius (boxes, tables and notes 12px), no heavy shadows; buttons 48px tall with 12px radius; page heroes sit on paper with a hairline below, like the homepage hero. Alternating paper/cream bands mark home chapters.
## Components
Header (sticky, translucent with a plain fallback, `--top-h` drives `scroll-margin-top`), skip link, language switch, proof chips, copy-able install block, task cards, project cards, guide cards and guide list rows (flex, arrow never wraps), "What you need" table (hosts as wrapping chips; the install command lives in the hero and step 1, not here) and "Start in 3 steps" rail on project pages, numbered workflow steps on `/guides` and project pages (outlined mono numbers, as on the homepage paths), numbered Q&A list, compact two-column source notes, answer block on guides (`.pair.answer`), side-rail table of contents ≥960px, manuscript example panel, glossary two-column index, compact roster band, four-column navy footer. Content comes from JSON. Homepage showcase media is indexed by content/showcases.json, with local source-derived posters, native video controls and directly readable text. Complete project repositories and catalog routes remain accessible; old homepage duplication is not a preservation requirement.
## Accessibility
One `<h1>` per page, ordered headings, `nav[aria-label]`, `main#main`, `lang` on every Chinese node, visible `:focus-visible` on links and buttons (ink ring in light, cyan in dark), AA contrast for text including muted text (#626779) and dark-mode buttons, header controls ≥40px on phones, tables readable on mobile, no motion required. Verified with axe-core on home, project, guide, comparison and glossary pages in both schemes.
## Responsive behavior
Desktop 1440 and phone 390 are the reference widths; 640px and 960px breakpoints. Cards go 3 → 2 → 1 columns; the guide TOC becomes a closed one-row disclosure in flow on phones (≤960px; an inline script opens it as the side rail on wider screens, and without JavaScript it opens on tap); the header stacks to brand + switch + CTA over a link row within 96px down to 360px wide (below 400px the CTA drops its decorative arrow; below 360px the controls wrap to a third row and `--top-h` follows, so anchor jumps always land below the header); nothing scrolls horizontally except code and tables inside their own containers, and — by owner decision on 2026-10-10 — the homepage showcase at ≤720px, where the five cases scroll sideways with scroll-snap (each card 84% wide so the next one peeks in) instead of stacking ~3300px.
## Interaction states
No JavaScript is required to read, see cases or switch language. Videos ship with native controls, no autoplay, preload="none", local posters and always-visible direct-watch fallbacks; with JavaScript one play button stands in for the native bar until the visitor starts playback. GitHub CDN availability is external; posters, descriptions and method links remain usable if playback fails. Recap has a source-derived Chinese captions track; captions for the other videos are not yet established. Guide search progressively enhances a complete static browse index. Only the copy buttons wait for the classic script. Missing paths remain platform 404s. The workbench CTA explains that sign-in may be needed; no token transfer. Links into the workbench (`appHref` in `site-shell.mjs`) carry the page language and the sending slot (`?lang=en|zh&source=org_<slot>`): “Open app” goes to `/login` (a signed-in visitor continues to the dashboard), workbench actions go to `/register`; none lands on the app’s own landing page. The app adopts `lang` and remembers it, and the first `source` in a tab reaches the signup event.
## Content voice
Specific, modest, bilingual. New UI copy is neutral labels or derived from JSON fields (install command, entry command, host list, dates); no ranking, citation, profit or quality guarantees. On home, say a thing once, in the section that owns it, and let the next section pick up from there: the hero caption is one case line plus short actions; hosts, licences and environment rules live in the tool section, project pages and the footer (the home closing carries no star/licence/cost sentence); case cards carry one new sentence each, not their media type again; the tool table shows a short “what you get” note (`home-reading.json` `tools`) rather than the project tagline that restates the goal; the closing keeps the diagram and actions, not the product catalogue again. The same rule holds beyond home (2026-10-10): a guide or article names its project and skill once, in the head; the end keeps only the next step, any method files the head did not show and the install action; a workflow step line at the top gives the place, the next-step link sits at the end; a guide's head points to its own Sources section instead of repeating those links; `/guides` layers skip guides a workflow on the page already lists; project pages list only guides that “How it works” does not already cite; a card or row links a page once (title), with a second action only when it lands elsewhere (install steps). Shorten visible copy, never by shrinking type, lowering contrast or hiding text with CSS (one owner-approved exception, 2026-10-10: the phone-only “swipe to see all N demos” hint above the sideways showcase), and keep crawlable titles, links and no-JS media fallbacks. A novel adapts into drama or a game; a recap starts from footage — copy and diagrams keep that distinction.
## Decisions recorded 2026-09-13
- Install commands wrap onto a second line inside cards and tables (tokens never split; the Copy button has its own column) instead of the earlier nowrap-and-scroll rule, because a clipped command reads as broken.
- The Open Graph image is a 1200×630 PNG card (`/brand/og-zenstory-ai.png`) instead of the 32px SVG mark; this is the one intentional metadata change versus the previous pages, and the GEO diff allow-lists it.
- Bilingual inline pairs carry a real " · " separator text node (hidden in single-language modes) so text extractors never see glued tokens.
- The visible "as of <date>" appears once per hero; per-card copies are visually hidden but kept in the HTML, and the org-wide proof sentence keeps its own date because it is content. `/projects` (2026-10-10) shows star counts on its cards without a separate "as of" line, at the owner's request; each project page still dates its sources.
## Decisions recorded 2026-09-14
- Split languages onto separate URLs (`/zh` prefix, hreflang, x-default = English) instead of the toggle-on-one-URL model: engines index one title, description and body per URL, and the panel prompts are two-thirds Chinese while every title used to be English. Existing English URLs did not change.
- The workbench docs moved off the React app shell onto the organization shell (static, no app bundle) with a sidebar generated from `docsNavigation.ts`, resolved markdown links (a link to a missing page fails the build) and a "Docs" entry on the `/workbench` project page instead of the site navigation. The organization reader is static; App `/docs` retains the React `DocsPage`, its search and language setting. Both read the same Markdown source. Generated organization docs live under `_site/docs` in the Vercel output and use a site-host rewrite so they cannot shadow the App reader. Canonical public documentation URLs remain on the organization host. On phones the static contents menu is collapsed by default; English document links preserve the English article anchor.
- `llms.txt` lists guides and workbench docs in separate sections and points at the `/zh` corpus. A glossary section (added 2026-10-10) lists each term with its English bridge and, where one exists, the guide that teaches it.

## Implementation constraints
Generators share `site-shell.mjs`, plain CSS, no new dependencies; isolated generator fixtures copy the complete content directory and generator scripts, while full builds copy public media, no new routes without updating `content/site-routing.json`, `vercel.json` (`--write-config`), `llms.txt` and the sitemap count. Every change is verified with the generator tests, a GEO diff against a baseline build (metadata, JSON-LD, links and text tokens must survive), 390px overflow checks, and axe.
## Open questions
- [x] `/docs` now sits on the organization shell (2026-09-14). Open: whether the app's own docs entry should link to `/docs` with a language hint.
- [ ] Palette moved from terracotta-on-paper to the mark's navy/cream/cyan with orange accents; owner sign-off recorded when the branch is merged.
- [ ] Search Console access is still not established; indexing remains unverified.

## Homepage decisions recorded 2026-10-03
- Keep the navy/cream/orange editorial identity; do not clone Hypit’s black/pink identity or machinery.
- A real public RPG screenshot replaces the abstract hero diagram. The conceptual project diagram moves down to the relationship section.
- Existing README-hosted videos are first-class evidence: writing-product demo, 68.5 s short-drama sample, 58.96 s finished recap and 14.96 s 3D gameplay capture. Original media remains CDN-hosted; local posters are extracted from those exact videos. Source-derived recap captions are retained. No placeholders or synthetic demo footage.
- Native controls and direct-watch links, with no startup video download, are preferred over carousel/player dependencies; an inline script replaces the native bar with one play button until the first play. The hero carries no install command: a single command next to one case read as “install this to reproduce it”; install commands live on project pages, reached from the tool rows.
- The complete hero stage and its short caption fit the 1440×900 first screen. Use a flat navy stage, fine frame borders and restrained shadows; the manuscript and actual game art provide the texture. Mobile play, language, app-entry and search controls have 44px targets. The phone showcase keeps native scroll-snap with next-card preview; progressive previous/next buttons and a position counter follow native scrolling and respect reduced motion. Without JavaScript, the cases and native video controls remain usable. Desktop showcase introductions split the heading and explanation, while subsequent sections keep a quieter heading scale and spacing.
- The full ten-topic directory stays on `/guides`; homepage establishes learning layers instead of ten equal topic cards.
- Local acceptance is based on independent screenshot review, native playback, static contract tests and build evidence. Public release verification is separate. Only recap has a confirmed caption track; public embedding permissions are not independently established by repository licenses. No search-ranking or AI-citation gains are claimed.

## Legal pages
`/privacy-policy` and `/terms-of-service` use the static reading shell with blocking local CSS, without app JavaScript or replacement of prerendered text. Like workbench docs, each keeps one URL with Chinese first and complete English below, explicit language markers and language links to the corresponding article anchors. The legal text comes unchanged from the two `privacy.json` locale files. The header brand on these two pages returns to `app.zenstory.ai`; organization pages keep their organization-home brand link.

## React workbench scope — module review, 2026-10-04
This section governs `app.zenstory.ai` only; it does not change the organization-site contract above. Evidence: `apps/web/src/index.css`, `components/ui/Modal.tsx`, `ConfirmDialog.tsx`, `Toast.tsx`, `BottomTabs.tsx`, `Layout.tsx`, and `docs/ui-async-state-standard.md`.

- **Goal:** preserve the writing workflow and supported capabilities while fixing demonstrated interaction, accessibility, and state-management defects. Refine visual consistency using the existing primitives and HSL roles.
- **Visual language:** retain the light/dark HSL roles and user accent choices, with white surfaces over a pale neutral canvas in light mode and neutral ink surfaces in dark mode. CSS owns surface/text/border colors; ThemeContext owns the theme class and mode-adjusted user accent. Accent text and pale selected states are separate from the white-on-color primary action. Page and control headings are sans; manuscript typography stays serif. Use fine borders, restrained elevation and the existing icons; the organization site retains its own paper/ink identity.
- **Navigation:** keep dashboard, project editor, materials, skills, billing, settings, and admin boundaries. Inspirations and complex material-analysis stages stay disabled by default; historical data remains readable.
- **Dialogs:** maintain accessible names, initial focus, Tab/Shift+Tab containment, Escape/backdrop policies, scroll locking, and focus restoration. Loading confirmations must not allow cancellation. Nested dialogs must not release the outer dialog's scroll lock.
- **Mobile workspace:** preserve mounted editor/chat/file state across switches. Tabs must reference real labelled panels; inactive panels must be unavailable to keyboard interaction. Retain the existing 768/1024 px layout boundaries and 44 px touch targets.
- **Feedback:** success/info notifications use polite announcements; errors use alert semantics. Keep current deduplication, maximum visible count, and expiry. Loading, empty, error/retry, and disabled states follow the existing async-state standard, not ad-hoc placeholder screens.
- **Motion:** honor reduced-motion preferences without changing normal animations or removing functional progress indicators.
- **Content:** reuse existing bilingual translation keys, task-oriented labels, and terminology. Do not expose inert actions as working controls.
- **Verification:** regression tests precede behavioral changes; run targeted component/page tests, TypeScript/lint, and keyboard/mobile browser checks. DOM semantics alone do not establish physical focus behavior or visual quality.
- **Open question:** email-change re-verification policy is not established by existing product contracts; preserve it rather than silently introducing a new access policy.

## React workbench buttons and type
Applies to the dashboard pages, settings dialog and dashboard dialogs. Evidence: `components/ui/Button.tsx`, `IconButton.tsx`, `components/dashboard/*`, `index.css` (`.ui-sans-headings`, status text tokens).

- **Buttons:** one primitive, `ui/Button`. Page-header and dialog actions are `md` (40px, 12px radius, 14px/500, 16px icon, 8px gap); phones use `touch` (same type, 44px). Every variant has a 1px border so a primary next to a secondary shares one height; primary, ghost and danger keep it invisible with `border-transparent!`. The default border-color rule now lives in `@layer base`, so explicit border colors and selected states can take effect. `primary` is the single solid accent action of a view; `secondary` (hairline) for the alternative or a persistent header entry when the body already carries the primary; `ghost` for tool-like header actions (skills import/statistics); `danger` only for destruction. Page-header actions keep their short text on phones — no unlabelled `+` or icon-only stand-ins.
- **Icon-only buttons:** `ui/IconButton` with a required label (accessible name and tooltip); 32px on desktop, 44px below 768px; `danger` tone for delete, `strong` for a bar's main control, `plain` (no text colour) when the caller needs its own colour in `className` — never add a text colour in `className` on the other tones. A card or row with more than two actions (skill cards, API-key rows) moves them to their own row under the content on phones, so the title keeps the full width.
- **Selected state:** filters, type chips, settings tabs and navigation use a pale accent selected state (8% for the navigation rail, 15% for format/filter controls, readable accent text). Solid accent is reserved for the primary action, so a selected filter never competes with it.
- **Type:** the page H1 and writing content are serif (display/body fonts); section, card and dialog titles, labels and controls are sans. `.ui-sans-headings` carries this on the dashboard `<main>`, the settings dialog and every `Modal`; `.prose` / `.editor-content` inside them keep serif headings. These heading rules live in `@layer base`, so a font set explicitly on a heading (a Tailwind `font-*` utility or `.heading-display`) always wins. Section title 16/600; card title 14/600 and card description 12/400 at every width; settings-dialog section labels are the 12/500 secondary label of the General tab; empty states use `DashboardEmptyState` (16/600 title, 14/400 description). Status badges use `--success-text` / `--warning-text` / `--error-text` so small text stays AA on its own tint in both themes.
- **Cards:** one project card (`dashboard/ProjectCard`) on home and the project list, with a 16px radius, one fine border, two-line titles and a native full-card opening button. Delete and count-explanation controls are siblings of that button. Its progress line and type/time row sit together in one `mt-auto` footer.
- **Intentional differences kept:** destructive actions (red solid, default focus on cancel, red logout text); compact workspace toolbars (28px, 12px) and chat suggestion chips; sidebar navigation (40px ghost rows); the home type chips enlarged to 44px pills; onboarding H1 30px and the empty workspace's 24/600 heading; mobile dashboard H1 24px and 16px inputs; in-page modal footers keep 44px buttons.

## React responsive decisions — 2026-10-05
- Dashboard and admin own viewport-height shells with independent main scrolling; public/docs long pages remain document-scrolled. Fixed workbench/admin shells use inset sizing, not100vw alongside document scrollbar gutters.
- Workbench panel units are explicit: desktop20/48/32%, sidebar minimum180px/chat300px; tablet25/40/35%, chat280px. Keep existing768/1024 breakpoints, draggable panes, mobile mounted/inert state and keyboard viewport handling.
- Desktop chat textarea minimum64px, saved input-panel floor144px; mobile44px controls remain. Empty placeholder is a concise idea prompt; long typed text/accessories remain scrollable. Tool status stays together; filenames wrap fully rather than losing text.
- All reachable React routes and generated static template families are checked at small laptop, tablet and mobile viewports. A scrollbar alone is not a defect: preserve legitimate file/message/table/code/long-page scroll, repair clipping and competing outer scroll boundaries.

## Admin growth decisions — 2026-10-07
Evidence: `AdminDashboard.tsx`, `StatsCard.tsx`, `AdminPageState.tsx`, admin HSL tokens, existing usage and payment endpoints. Active scope: authenticated `app.zenstory.ai/admin`; operator job is to compare acquisition, actual AI activity, registration-cohort activation, actual payment and operating costs.

- Put the selected 7/14/30-day growth period first; compare with the prior equally long period, including the same partial-day progress. Beijing boundaries and the observation cutoff must be visible.
- AI active means distinct non-admin customers with live metered AI calls. Activation and signup-to-paid rates use only the same registration cohort; zero denominator is unavailable, not zero. Independent telemetry event counts are never displayed as a conversion funnel.
- Revenue comes from paid orders in integer cents. Paid customers, orders and free grants are separate. Subscription-history attribution is supporting evidence, not the payment ledger.
- Reuse admin surfaces, StatsCard, existing light/dark tokens, Lucide icons and query states. No new theme, chart dependency, organization palette or promotional graphics. Daily data uses an accessible table, with bounded horizontal scrolling on phones.
- Cards adapt from two columns on phones to four on desktop; controls have 44px touch targets, selected state, visible keyboard focus. Loading/error/retry are independent of legacy operational statistics.
- Copy is bilingual and factual, explicitly labels incomplete days, cohort observation windows, estimates and event counts. No invented visitors, retention, profit or attribution. Local component, API contract, type/lint and build checks precede publication.
- [ ] Embedding account funding is external; provider balance must be restored before reindexing can recover missing search entries. Owner: operator.

## Admin controls
Keep the six task groups, real route links and existing metric definitions. Only the selected navigation row carries the accent tint; ordinary rows have no visible border or elevation. Mobile navigation has an internal close control and an app-return link, isolates the background while open, and returns focus to the trigger. A new pathname resets the independent main scroll; query-filter changes do not.

Admin page/section/card/dialog headings are sans. Panels use a fine border and restrained shadow; non-interactive metric cards have no pointer cursor. Table headings and status badges stay on one line; long user names and emails wrap in their own columns/cards. Secondary and accent text remain legible in both themes and follow the selected accent hue. Mobile form text is at least 16px and primary navigation/retry controls are at least 44px.

Legacy admin overlays reuse `ui/Modal` with their original content and pending rules. A custom visible header supplies the dialog's accessible name; body spacing can be overridden when the caller owns the header/body/footer layout. Keep focus containment, Escape, return focus and scroll locking; block Escape during submission and preserve each caller's backdrop policy. Visible field labels must identify their controls, including wrapped selects.

## Control surfaces

Use a light navigation rail, compact controls, restrained borders and clear hierarchy. Keep the task-oriented writing entry and the organization site's paper/ink identity. Implementation details are recorded in `.agents/notes/implemented/feature/2026-10-13-app-visual-quality.md`.

Dashboard creation is one surface, at most 900px wide, with the idea above and format/action below. On phones the action gets a full row; its placeholder is legible and the recent-project section begins within 390×844. IME confirmation never creates a project. Side navigation uses real links, the current page is named, pathname changes reset main scroll, and the phone menu has its own scroll with the page content inert. Closing a menu preserves the page position.

Shared controls use CSS-owned theme surfaces, 12px button/search radii, 16px project/material/skill panels and restrained dialog elevation. Status text has separate light/dark contrast tokens. Check the six accent choices in both schemes, native project opening/deletion targets, viewport bounds, settings/dialog focus and unchanged writing-panel geometry. Static organization, legal and documentation pages continue using their independent reading shell.

## UI copy and punctuation

Short page/section headings, button labels, navigation items and search placeholders have no terminal period. A headline expressing one idea keeps its clauses connected with a comma or a line break, rather than splitting them with repeated full stops. Full sentences in descriptions and guidance keep normal punctuation. Chinese UI prose uses full-width punctuation; English prose uses English punctuation and spacing. Loading/progress text uses one ellipsis character (`…`), while direct input hints do not add decorative ellipses. Commands, URLs, delimiter examples, legal text and authors' writing keep their original syntax. The app landing footer contains legal links and copyright. Organization project, glossary and source links belong to the organization site.