Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/HNU-himematsu/HNU-TimeLetter/llms.txt

Use this file to discover all available pages before exploring further.

HNU-TimeLetter (与她的海大时光笺) is a Galgame-style interactive visual narrative built on a modern TypeScript full-stack. Every layer of the stack was chosen to serve the project’s two core constraints: a rich, ceremony-driven animation experience on desktop and a lightweight stamp-collection waterfall on mobile, both fed by a single Feishu Bitable CMS with zero manual file deployments.

Framework Layer

Next.js 16.2.4 — App Router

Server Components, nested layouts, and file-system routing via the app/ directory. Turbopack is the default bundler for next dev, cutting cold-start HMR to under 300 ms on a mid-range machine.

TypeScript 5+ Strict Mode

strict: true throughout tsconfig.json. All domain types live in src/lib/types.ts as a single source of truth; the compiler enforces every import boundary at build time.

React 19

The project targets React 19.2.3 and opts into the React Compiler via babel-plugin-react-compiler, reducing manual useMemo / useCallback boilerplate across animation-heavy components.

Turbopack

Enabled as the default Next.js dev bundler. Production builds still use Webpack via next build for maximum compatibility with the self-hosted server environment.

Styling

Tailwind CSS v4

The v4 PostCSS plugin (@tailwindcss/postcss) is used instead of the legacy tailwind.config.js approach. Utility classes are composed with tailwind-merge to prevent specificity conflicts in dynamic components.

Radix UI / Shadcn-style Components

Primitive headless components from radix-ui underpin all accessible interactive elements. Components under src/components/ui/ follow Shadcn conventions and must not have their logic modified directly — extend via wrapper components instead.
Design tokens (set in global CSS):
TokenValueUsage
Background#ece9e4Warm-grey paper surface across all routes
Accent#c23643Wax-seal red — CTAs, active states, highlights
Title fontChillDINGothic_SemiBoldLocal OTF, registered in app/layout.tsx
Body fontZouLDFXKAJLocal TTF, registered in app/layout.tsx
Viewport frame5 px white border + 16 px radiusApplied site-wide via layout.tsx wrapper

Animation

Framer Motion v12

Drives the envelope-open ceremony, full-screen transition overlay, story card stack entrance / exit, and the magnetic button physics. All variants are defined as named AnimationConfig objects co-located with their component.

@waaark/luge 0.6.20-beta

Scroll-driven progressive animation engine. The Luge smooth-scroll plugin is explicitly disabled (useLuge.ts) to prevent conflicts with Lenis. Only the stagger, reveal, and parallax plugins are active.

Lenis 1.3.23

Smooth-scroll runtime active on desktop only, during the post-envelope scroll-section group. Built-in Snap is used for footer anchoring. Wrapped in useVirtualScroll.ts; paused automatically when the envelope overlay is visible.

State Management

Global UI state is managed by a single Zustand v5 store defined in src/lib/store.ts.
// src/lib/store.ts — simplified shape
interface AppState {
  // Envelope ceremony
  isEnvelopeOpened: boolean;
  setEnvelopeOpened: (v: boolean) => void;

  // Full-screen transition overlay
  isTransitioning: boolean;
  setTransitioning: (v: boolean) => void;

  // Map experience
  selectedLocationId: string | null;
  setSelectedLocationId: (id: string | null) => void;

  // Story reading
  currentStoryIndex: number;
  setCurrentStoryIndex: (i: number) => void;
}
Zustand v5 uses the useSyncExternalStore API internally. Slices are not split into separate files — all state lives in one flat store to keep cross-slice transitions (e.g. isEnvelopeOpened → isTransitioning) a single atomic update.

External Services

Feishu (Lark) Bitable

The sole CMS data source. All story, location, creation-board, and contributor records are authored in Feishu multi-dimensional tables. No headless CMS, no markdown files.

Aliyun OSS

All image assets are stored in an Aliyun Object Storage bucket. The sync pipeline downloads Feishu attachments, uploads them to OSS, and back-fills the OSS URL into the originating Feishu record.

node-schedule

Runs inside the long-lived Node.js process (managed by PM2). Schedules automatic Feishu → JSON syncs without an external cron daemon. Initialized at app startup in src/lib/admin/.

Directory Structure

src/
├── app/
│   ├── admin/              # /admin back-office (login + protected group)
│   ├── api/admin/          # Admin APIs (auth, manual sync, scheduler)
│   ├── creation/           # /creation creation board route
│   ├── map/                # /map interactive map route
│   ├── layout.tsx          # Global layout, local font registration, viewport frame
│   ├── page.tsx            # Home page (envelope ceremony + scroll sections)
│   ├── loading.tsx
│   └── not-found.tsx
├── components/
│   ├── ui/                 # Shadcn-style primitives — DO NOT modify logic directly
│   ├── shared/             # EnvelopeIntro · GuideLine · CustomScrollbar
│   │                       # GlobalNav · TransitionOverlay
│   ├── motion/             # MagneticButton · StickyCursor · useMousePosition
│   ├── sections/           # AboutProject · AboutUs · Credits · Footer · ScrollSections
│   ├── desktop/            # Desktop-only map components (Dev B)
│   ├── mobile/             # Mobile stamp-book components (Dev C)
│   └── creation/           # Creation board sticky-note waterfall
├── lib/
│   ├── admin/              # Back-office auth · scheduler · config read/write
│   ├── sync/               # Three-layer sync architecture (see Sync System docs)
│   ├── sync-service.ts     # Legacy single-file sync entry (CLI + API reuse)
│   ├── types.ts            # Domain types — Single Source of Truth
│   ├── store.ts            # Zustand global state
│   ├── hooks.ts            # Shared React hooks
│   ├── useVirtualScroll.ts # Lenis wrapper
│   ├── useFooterSnap.ts    # Footer snap (Lenis scroll + scrollTo)
│   ├── useLuge.ts          # Luge initializer (smooth-scroll plugin disabled)
│   └── content.ts / utils.ts
├── types/                  # Third-party ambient type declarations (@waaark/luge, etc.)
├── config/                 # admin.json, locations.json — local runtime config
├── data/                   # Sync artifacts: content.json · creation-board.json · contributors.json
└── scripts/                # sync-feishu.ts · Feishu / OSS debug scripts
The repository root also contains public/ (static assets including local fonts), tests/e2e/ (Playwright suites), and the collaboration asset directories described below.

Compile Boundary Rules

The directories agents/, .opencode/agents/, and .trae/skills/ are excluded from tsconfig.json via the exclude array. Adding TypeScript files to those paths will not trigger compiler errors — they are invisible to next build and npm run typecheck.
PathPurposeIn tsconfig?
src/All application runtime code✅ Included
agents/AI agent collaboration assets (skills, knowledge bases)❌ Excluded
.opencode/agents/OpenCode agent definitions❌ Excluded
.trae/skills/Trae IDE skill files❌ Excluded
opencode.jsonLocal Playwright MCP server registration❌ Runtime-only
This separation guarantees that collaboration scaffolding never introduces spurious type errors or accidental runtime imports into the production bundle.

Build docs developers (and LLMs) love