DESIGN.md · git:20261010.5472c69 · 2026-10-10 · sha256 2bfa4a7dbc731a0c
DESIGN.md git:20261010.5472c69A
Immutable. This exact content never changes and is served at /api/v1/blob/2bfa4a7dbc731a0c.
# 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); warm orange `#b3542e` / text `#9a4523` for eyebrows, actions and link hover. 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. 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). The workbench docs are the one exception: 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. Other pages keep the editorial system below.
Body: Plus Jakarta Sans with a system CJK sans stack for both languages, 16px/1.65 (ZH 1.8, +.01em); headings: Source Serif 4 with system CJK serif fallback (h1 clamp 34–50px, h2 26px with a hairline rule and 64px top margin, h3 19px); mono: JetBrains Mono for commands. Google Fonts load non-render-blocking (preload + print-swap + noscript); Noto Serif SC is not loaded. Measure 720px shared by both languages; page wrap 1120px; 24px gutters; cards 1px borders, 8–10px radius, no heavy shadows. 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 and "Start in 3 steps" rail on project pages, 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.
## 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 in-app docs search is not available on these pages; the React `DocsPage` remains for development builds.
- `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 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.
## 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 `apps/web/docs/ui-async-state-standard.md`.
- **Goal:** preserve the writing workflow and supported capabilities while fixing demonstrated interaction, accessibility, and state-management defects. No visual redesign or new design-system layer.
- **Visual language:** retain the existing light/dark HSL semantic tokens, serif writing typography, sans-serif controls, spacing, icons, and elevation. Reuse existing primitives; do not import the organization site's palette into the workbench.
- **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, 8px 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 unlayered `* { border-color }` in `index.css` beats any non-important Tailwind border colour). `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 the mobile menu use the sidebar's selected state (accent 15% background, 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; 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 H1 20px 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.